Count_People/python/people_count.py

187 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 导入相关依赖
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
import cv2
import numpy as np
from ultralytics import YOLO
import torch
import argparse
def exec_inference_video(file_path,line):
if torch.cuda.is_available():
model = YOLO("weights/yolov8s.pt") # gpu
print('gpu推理')
else:
model = YOLO('weights/yolov8s.onnx')
print('cpu推理')
# 基本逻辑:行人从视频顶部往下部行走并进触碰线,视为 下行 low;从视频画面下部往上部行走并进触碰线,视为 上行 up。
# 其实我们只在视频中画了一条线,实际上在程序判断的时候,线的上下都有一段区域(四边形,以我们之前画的线为一边高度20,宽度是线的宽度)
# 如果行人先出现在线的上边区域再出现线的下边区域,证明是从上往下,反之,从下往上
## 加载要使用的视频
kamera = cv2.VideoCapture(file_path)
font = cv2.FONT_HERSHEY_DUPLEX
# 获取视频的高度与宽度
width, height = kamera.get(3), kamera.get(4)
fps = kamera.get(cv2.CAP_PROP_FPS)
# 注意opencv的坐标系是从左上方开始的,向下为y轴,向右为x轴
end_x = int(width)
# 注意线的位置应该根据视频而定,不是一成不变的
l_x1,l_y1,l_x2,l_y2=line[0][0],line[0][1],line[1][0],line[1][1]
#防止线的位置太靠边缘而使线上下区域超出画面
if l_x1-5<0:
l_x1=6
if l_x2-5<0:
l_x2=6
if l_x1+5>width:
l_x1=width-6
if l_x2+5>width:
l_x2=width-6
if l_y1-20<0:
l_y1=21
if l_y1+20>height:
l_y1=height-21
if l_y2-20<0:
l_y2=21
if l_y2+20>height:
l_y2=height-21
l_x1,l_y1,l_x2,l_y2=int(l_x1),int(l_y1),int(l_x2),int(l_y2)
#线的上面区域
region_1=[(l_x1,l_y1-50),(l_x2,l_y2-50),(l_x2,l_y2),(l_x1,l_y1)]
#线的下面的区域
region_2=[(l_x1,l_y1),(l_x2,l_y2),(l_x2,l_y2+50),(l_x1,l_y1+50)]
region1 = np.array(region_1)
region1 = region1.reshape((-1, 1, 2))
region2 = np.array(region_2)
region2 = region2.reshape((-1, 1, 2))
# 下行的行人id保存在这个变量中
low_id = set()
# 上行行人的id保存在这个变量中
up_id = set()
#进入上面(region1)区域的行人id保存在这个变量中
total_region1=set()
# 进入下面(region2)区域的行人id保存到这里
total_region2= set()
# output_path = file_path.split(".")[0]+ "_output.mp4"
# print(output_path)
# fourcc = cv2.VideoWriter_fourcc(*'MJPG')
# out = cv2.VideoWriter(output_path, fourcc, 30, (int(width), int(height)))
up_cnt = 0
down_cnt = 0
while True:
# 逐渐帧读取视频内容
ret, frame = kamera.read()
if not ret:
break
# opnecv读取的图片色彩空间为BGR,我们把它转化成RGB
rgb_img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
cv2.line(frame, (l_x1,l_y1),(l_x2,l_y2), (255, 0, 255), 3)
# 使用yolo8的追踪模式(采用byte-tracker多目标跟踪算法)
# 遍历yolo的处理结果结果全部保存在返回的results(list类型)变量中,里面保存着结果信息,可以打印一下看一看(打印results[0])
results = model.track(rgb_img, conf=0.5, classes=[0],tracker="bytetrack.yaml", persist=True, verbose=False)
for i in range(len(results[0].boxes)):
# 框的四个坐标位置
x1, y1, x2, y2 = results[0].boxes.xyxy[i]
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
# 将跟踪成功的物体 用框标记出来(跟踪失败的不显示)
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
# 识别对象的id,可能为None(目标跟踪失败)
if results[0].boxes.id == None:
continue
else:
ids = results[0].boxes.id[i]
if ids == None:
continue
# float转化int
ids = int(ids)
cv2.putText(frame, str(ids), (x1+10, y1), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 2)
# 计算物体中心位置的坐标
cx = int(x1 / 2 + x2 / 2)
# cy = int(y1 / 2 + y2 / 2)
cy = y2
# 判断从视频顶部进入画面的物体的中心位置与区域1的关系(在内部还是外部)
# 判断视频中行人的中心位置与区域1的关系(在内部还是外部)
inside_region1 = cv2.pointPolygonTest(region1, (cx, cy), False)
# inside_region1为1表示在区域内部
if inside_region1 > 0:
#判断当前行人id是否进入过region2区域
if ids in total_region2:
#如果当前行人id进入过region2区域,现在又在region1区域内
#证明其是从视频下方到视频上方(region2区域->region1区域),视为上行,保存到
up_id.add(ids)
# 行人越线,线的颜色改变
cv2.line(frame, (l_x1,l_y1),(l_x2,l_y2), (255, 255, 255), 3)
#进入region1区域的行人id保存在这个变量中
total_region1.add(ids)
# 保存行人图片
# img_name = file_path.split(".")[0] + "down_" + str(down_cnt) +".jpg"
# cv2.imwrite(img_name, frame[y1:y2,x1:x2])
# down_cnt = down_cnt + 1
inside_region2 = cv2.pointPolygonTest(region2, (cx, cy), False)
if inside_region2 > 0:
if ids in total_region1:
cv2.line(frame, (l_x1,l_y1),(l_x2,l_y2), (255, 255, 255), 3)
low_id.add(ids)
total_region2.add(ids)
# 保存行人图片
# img_name = file_path.split(".")[0] + "up_" + str(up_cnt) +".jpg"
# cv2.imwrite(img_name, frame[y1:y2,x1:x2])
# up_cnt = up_cnt + 1
# 统计进出物体,并设置为字符串,下一步准备在画面上显示
first_low = 'LOW:' + str(len(low_id))
first_up = 'UP:' + str(len(up_id))
# 设置画面左右上角的背景色
frame[0:40, 0:120] = (102, 0, 153)
frame[0:40, end_x - 120:end_x] = (102, 0, 153)
# 将进出物体统计的个数输出到图片,展示
cv2.putText(frame, first_low, (0, 30), font, 1, (255, 255, 255), 1)
cv2.putText(frame, first_up, (end_x - 120, 30), font, 1, (255, 255, 255), 1)
# 显示两个区域
zeros = np.zeros((frame.shape), dtype=np.uint8)
mask1 = cv2.fillPoly(zeros, [region1], color=(255, 0, 0))
mask2 = cv2.fillPoly(zeros, [region2], color=(0, 255, 0))
frame1 = cv2.addWeighted(frame, 1, mask1, 0.3, 0)
frame2 = cv2.addWeighted(frame1, 1, mask2, 0.05, 0)
# 逐帧显示处理后的画面
# out.write(frame2)
cv2.namedWindow('frame',cv2.WINDOW_NORMAL)
cv2.imshow("frame", frame2)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
if cv2.getWindowProperty('frame', cv2.WND_PROP_VISIBLE) < 1:
break
kamera.release()
# out.release()
cv2.destroyAllWindows()
print('最终统计结果如下:')
print('-----------------------------------------------------------------')
print(f'LOW--->{len(low_id)}')
print(f'UP--->{len(up_id)}')
print('-----------------------------------------------------------------')
def parse_option():
parser = argparse.ArgumentParser()
parser.add_argument('--file_path', type=str)
parser.add_argument('--line', type=str)
opt = parser.parse_args()
return opt
if __name__=='__main__':
opt=parse_option()
file_path=opt.file_path
line=eval(opt.line)
exec_inference_video(file_path,line)