序言
车流量在目前的交通系统中应该是非常普遍的,可以用于统计某条干道的车辆经过总数,与人流检测实现原理几乎是一样的,都是基于目标检测和跟踪进行,本例的实现是基于yolov5+deepsort,使用yolov5对车辆进行检测,再用deepsort对其跟踪,而后设计一系列的规则撞线进行两个方向的车流数量统计。网上实现的方式有很多种,效果大同小异,可以择优选择学习。
一、实现原理
基于之前的yolo+deepsort上,将person类别改为车辆类别,因为coco数据集中,车辆类别有几种【car,bus,truck】,所以都要保存下来。
首先来看一下yolov5+deepsort的车辆跟踪初始效果,看着密密麻麻的框和id,思考一下该如何去设计这些规则进行统计。
首先观察图中,需要构建一个区域对经过的车辆进行统计,因为有两个方向,所以这里构建了黄、蓝两个区域,因为考虑到路面并不是规则矩形,所以构建的是多边形区域。mask掩码代码如下:
def draw_mask(height,width):
# 根据视频尺寸,填充一个polygon,供撞线计算使用
mask_image_temp = np.zeros((height, width), dtype=np.uint8)
# 初始化2个撞线polygon 蓝色
list_pts_blue = [[277, 305], [926, 308], [983, 344], [220, 335]] # 蓝色多边形坐标,可根据自己的场景修改
ndarray_pts_blue = np.array(list_pts_blue, np.int32)
polygon_blue_value_1 = cv2.fillPoly(mask_image_temp, [ndarray_pts_blue], color=1) # 构建多边形
polygon_blue_value_1 = polygon_blue_value_1[:, :, np.newaxis]
# 填充第二个polygon 黄色
mask_image_temp = np.zeros((height, width), dtype=np.uint8)
list_pts_yellow = [[220, 335], [983, 344], [1030, 370], [170, 356]] # 黄色多边形坐标,可根据自己的场景修改
ndarray_pts_yellow = np.array(list_pts_yellow, np.int32)
polygon_yellow_value_2 = cv2.fillPoly(mask_image_temp, [ndarray_pts_yellow], color=2) # 构建多边形
polygon_yellow_value_2 = polygon_yellow_value_2[:, :, np.newaxis]
# 撞线检测用mask,包含2个polygon,(值范围 0、1、2),供撞线计算使用
polygon_mask_blue_and_yellow = polygon_blue_value_1 + polygon_yellow_value_2
# 缩小尺寸,1920x1080->960x540
polygon_mask_blue_and_yellow = cv2.resize(polygon_mask_blue_and_yellow, (width, height))
# 蓝 色盘 b,g,r
blue_color_plate = [255, 0, 0]
# 蓝 polygon图片
blue_image = np.array(polygon_blue_value_1 * blue_color_plate, np.uint8)
# 黄 色盘
yellow_color_plate = [0, 255, 255]
# 黄 polygon图片
yellow_image = np.array(polygon_yellow_value_2 * yellow_color_plate, np.uint8)
# 彩色图片(值范围 0-255)
color_polygons_image = blue_image + yellow_image
# 缩小尺寸,1920x1080->960x540
color_polygons_image = cv2.resize(color_polygons_image, (width, height))
return polygon_mask_blue_and_yellow,color_polygons_image
最后掩码图如下,后面我们会告诉这个掩码怎么使用。
为了构建出更好的可视化效果,得到的掩码我们使用opencv中的add函数,将deepsort输出的跟踪图与之相融合,得到如下效果图,就能够很清晰的看到车辆经过该区域后计数器开始工作。
好了,准备工作做完了,接下来该介绍如何使用掩码进行计数。首先yolov5检测后得到的框,需要设计一个撞线的点,这个点将代表着这辆车的坐标,而不是检测得到的框,可以取框的中心点,也可以取其他的点,例如我的例子中就取了图中的该点,这个点将代表着这辆汽车:
创建两个列表,分别用于记录经过黄色、蓝色区域的车辆id。所以我们就有如下计数规则:
向下记录规则:
- 当某个点在蓝色区域,并且不在黄色区域时,说明该点是从上往下经过蓝色区域,蓝色区域列表添加其id,不予处理。
- 当该点位于黄色区域后,检查其是否也在蓝色区域列表中,如果在,则说明其是向下的方向,向下计数器+1,同时在两个列表中将其id删除。
往上记录规则同理:
- 当某个点在黄色区域,并且不在蓝色区域时,说明该点是从下往上经过黄色区域,黄色区域列表添加其id,不予处理。
- 当该点位于蓝色区域后,检查其是否也在黄色区域列表中,如果在,则说明其是向上的方向,向上计数器+1,同时在两个列表中将其id删除。
光看文字的话可能会比较懵逼,可以结合效果图进行理解。
说了这么多,怎么判断车辆的点是否进入了颜色区域呢?回到上面的代码中,在创建mask掩码的时候,额外创建了一个图像矩阵polygon_mask_blue_and_yellow,蓝色区域在矩阵中的值为1,黄色的则为2。与检测的视频中的位置是对应的,所以我们可以很好的判断,如果某个点的坐标在这个矩阵中值为1,则在蓝色区域中,如果值为2,则在黄色区域。具体代码实现如下:
if len(list_bboxs) > 0: # 如果图上有目标
# ----------------------判断撞线----------------------
for item_bbox in list_bboxs:
x1, y1, x2, y2, _, track_id = item_bbox # 取出deepsort返回坐标和id
y1_offset = int(y1 + ((y2 - y1) * 0.6)) # 设置一个撞线检测点,(x1,y1),y方向偏移比例 0.0~1.0
# 撞线的点
x, y = x1, y1_offset
if polygon_mask_blue_and_yellow[y, x] == 1: # 如果撞线点进入蓝色区域
# 如果撞 蓝polygon
if track_id not in self.list_overlapping_blue_polygon:
self.list_overlapping_blue_polygon.append(track_id) # 用一个列表记录下车辆id
if track_id in self.list_overlapping_yellow_polygon: # 如果这个id也在黄色区域,那么则是外出方向
self.up_count += 1 # 外出计数+1
# print('up count:', self.up_count, ', up id:', track_id)
self.list_overlapping_yellow_polygon.remove(track_id) # 删除 黄polygon list 中的此id
else:
# 无此 track_id,不做其他操作
pass
elif polygon_mask_blue_and_yellow[y, x] == 2: # 如果车辆撞线点进入
# 如果撞 黄polygon
if track_id not in self.list_overlapping_yellow_polygon:
self.list_overlapping_yellow_polygon.append(track_id) # 用另一个列表记录下车辆id
if track_id in self.list_overlapping_blue_polygon: # 如果这个id也在蓝色区域,那么则是进入方向
self.down_count += 1 # 进入计数+1
# print('down count:', self.down_count, ', down id:', track_id)
self.list_overlapping_blue_polygon.remove(track_id) # 删除 蓝polygon list 中的此id
else:
# 无此 track_id,不做其他操作
pass
else:
pass
写的有些凌乱,过后如果发现表达不足的地方过后再修改。
另外本文代码实现是基于此行人计数的仓库,做了部分改进和优化,非常感谢他的开源工作。自己的代码有点凌乱还未整理,打算写完这一系列的文章后再进行上传。原理相对来说比较简单,可以自己尝试着去实现。