点击上方↑↑↑“OpenCV学堂”关注我
来源:公众号 小白学视觉 授权
各位小伙伴大家好,今天将会带领大家一起学习如何搭建一个违章停车检测系统。需要重点说明的是,今天使用的逻辑和判定条件比较难,尤其是他的编程实现。不过小伙伴不要怕,我们提供了项目的开源代码,具体链接如下:
https://github.com/hasantha-nirmal/Traffic_Violation_Detection_Yolov4_Deep-Sort
接下来我们将详细介绍如何实现这个系统
首先,我们需要简要了解一下这个项目中的停车违章检测是什么意思。为了介绍方便,我们将穿插使用学术术语和生活用语。想象一下,我们有一个感兴趣的区域也可以称为某个地方,车辆不应该停放在那里。但是,如果车辆只是停在那里一会儿,我们也不能将其是为为违章,例如马路边允许临时停车的区域。如果我们正在谈论的这个区域离道路太近,那么经过的车辆可能会与这个感兴趣的区域相交,并且会立即被视为违章。所以首先,我们必须消除这种情况。也就是首先解决临时停车不会判定为违章的情况。最简单的解决方案是为每辆单独的车辆引入计时器。由此,我们可以确保车辆已经在该限制区域内停留了多长时间。
采用感兴趣区域作为停车位,我们只需使用一个摄像头即可,同时这个摄像头不仅可以同时监控和检测特定车辆占用该停车位的时间,还可以计算出每辆车必须支付的费用停车场。当然了,这需要额外的开发,这里小白做过多的介绍
我们前面说过,我们需要建立一个计时器。这个任务的难点是需要考虑许多车辆肯定会同时出现,这使得任务变得比较复杂。不过也没有关系,我们只需要为不同的车辆设置不同的计时器即可。
接下来我们将详细介绍如何通过代码实现上述的功能。
# Parking space coordinates; #line 113
parking_co = []
#blanked = np.zeros((658,1024), dtype=np.uint8)
blanked = np.zeros((2048, 1024), dtype=np.uint8)
#pts = np.array(([156, 704], [2, 893], [476, 932], [270, 708]))
pts = np.array(([513, 716], [321, 943], [884, 979], [630, 701]))
#blanked = np.zeros((720,1280), dtype=np.uint8)
#pts = np.array(([38, 433], [95, 322], [1246, 570], [1065, 709]))
cv2.fillPoly(blanked, np.int32([pts]), 255)
上面的代码给出了我们如何选择停车位作为我们的感兴趣区域。我们首先定义了一个名为park_co的空白数组,之后创建了一个于图像分辨率具有相同高度或者相同宽度的一个权威零的数组。之后选择感兴趣区域的顶点坐标。在pts变量中存放我们选择的感兴趣区域的顶点坐标。之后我们使用OpenCV中的fillPoly函数将感兴趣区域填充上,以便于我们判断车辆是否与感兴趣区域相交。
感兴趣区域的选择如下图所示
现在,我们有了感兴趣的区域或禁止车辆停放的地方的像素的所有坐标点。然后我们选取车辆的边界框坐标(如何识别车辆呢,可以参考小白之前的文章)。但是,这又带来了一个问题。如果相机离这个感兴趣区域太近,当有车辆接近该区域时,它的边界框会占据非常多的坐标点,当同时有车辆时,必须对视频的每一帧重复这个过程,导致帧率急剧下降。所以,我对这个案例提出了一个假设:如果一个车辆/边界框与这个 ROI 相交,它肯定也与边界框的底线相交。所以就像在车道线违例中一样,而不是取车辆的所有边界框坐标
bbox_bottom_line_co = list(zip(*line(*(int(bbox[0])+50,int(bbox[3])), *(int(bbox[2])-50,int(bbox[3])))))
上面是提取底线坐标的代码。我们通过从线条的每一侧移除 50 个像素坐标来减少线条长度,以便更好地表示车辆。因此,该线始终停留在车辆区域内,并且不占用其周围的任何空闲空间。
if len(intersection(parking_co, bbox_bottom_line_co)) > 0:
frame_matrix.append((str(frame_num) + class_name + str(t),
(
str(int(bbox[0])).zfill(4), str(int(bbox[1])).zfill(4), str(int(bbox[2])).zfill(4),
str(int(bbox[3])).zfill(4))))
想象一辆车第一次与该地区相交。当它发生在特定帧内的那一刻,我们立即附加该事件的帧号frame_num车辆类型(class_name),车辆跟踪 ID(str(t))和该车辆在该帧的边界框坐标
chk_index = str(frame_matrix).find(str(frame_num — 1) + class_name + str(t))
然后立即检查前一帧是否为同一车辆发生了相同类型的相交点。如果这个相交点是第一次发生,则不满足这个条件,程序进入下一帧,没有任何进一步的交互。但是想象一下,如果这是车辆与感兴趣区域相交后的第二帧,那么会为这个chk_index变量赋一个值。
if bool(chk_index + 1) == True:
previous_bbox_co_str = str(frame_matrix)[
(chk_index — 1) + len(str(frame_num — 1)) + len(class_name + str(t)) + 5:(chk_index — 1) + len(str(frame_num — 1)) + len(class_name + str(t)) + 5 + 30]
我们首先对( chk_index+1 )的布尔值给出一个正值。设置为1的原因是,对于特定车辆,它的日志详细信息可能从数组的最开头开始,因此将其在数组中的放置值设置为 0。如果发生这种情况,bool(0)会使条件为 false即使它为真。如果没有这样的日志条目,则 chk_index仅返回 -1,当设为 +1 时,它会给出所需的 False 输出。
此外,当该条件为真时,将会有关于边界框的前一帧日志详细信息获取到另一个名为previous_bbox_co_str的变量中。
现在我们知道了车辆在当前帧和前一帧的边界框坐标。由于我们一直都知道车辆一直在 ROI 中,因此我们需要确定车辆是静止(不动)还是移动的。这里我们声明了一个名为immobile的新函数,用于实现这个功能。
# Check the immobility of vehicle #line 99
def immobile(bbox, previous_bbox_str):
previous_bbox0 = int(previous_bbox_str[1:5])
previous_bbox1 = int(previous_bbox_str[9:13])
previous_bbox2 = int(previous_bbox_str[17:21])
previous_bbox3 = int(previous_bbox_str[25:29])
total = abs(bbox[0] — previous_bbox0) + abs(bbox[1] — previous_bbox1) + abs(bbox[2] — previous_bbox2) + abs(
bbox[3] — previous_bbox3)
if total <= 4:
return True
else:
return False
这里我们使用了previous_bbox_co_str作为函数的属性,以及当前的边界框坐标。我们分别计算xmin、ymin、xmax 和 ymax 值的差值,如果绝对差值小于 4,输出 True,表示车辆是不动的,否则输出 False。
需要注意,即使车辆或任何物体完全停止,YOLO 也会给出波动的边界框坐标。为避免这个现象并使此过程稳健,我们在此处将判定变量设置为比较高的值。该值越高,程序对边界框的随机波动就越鲁棒。
因此,如果作为车辆的函数输出是不动的(静止的),那么我们需要立即检查 cache_matrix 中是否有任何先前记录的条目,如果没有,我们需要加上当前时间(t_start)和车辆类型(class_name)并跟踪标识(str(t))
if str((class_name + str(t))) not in str(cache_matrix): #line 310 t_start
= datetime.now()
cache_matrix.append((str(t_start), class_name + str(t)))
print(cache_matrix)
之后,我们检查车辆是否在cache_matrix中。同时还要检查同一辆车是否也在viol_matrix中。(因为我已经将违规车辆记录,这些车辆已经静止并超过了时间限制)。如果不是,我们应立即检索该特定车辆的t_start值并使用当前时间检查不同的时间。
index = (str(cache_matrix).find(str((class_name + str(t))))) #line 318
t_start_cm = str(cache_matrix)[index — 28:index — 11]
t_spending = (datetime.now() — datetime.strptime(t_start_cm,
‘%y-%m-%d %H:%M:%S’)).total_seconds()
如果时间差 (t_spending) 超过时间限制,那么我们将详细信息记录到电子表格中,其中包括车辆第一次进入时间 (t_start_cm)、车辆类型 (class_name) 和车辆跟踪 ID (str(t)) . 在这种情况下,出于演示目的,我们将计时器设置为 10 秒。示例如下
sheet.write(row_num, 0, str(t_start_cm), style) #line 328
# sheet.write(row_num, 1, str(round(t_spending, 2)), style)
sheet.write(row_num, 1, str(class_name ) + str(t), style)
row_num += 1
workbook.save('outputs/xlsx/parking/details.xls')
然后我们必须将这辆特定车辆作为日志条目放入viol_matrix数组中,因为该车辆现在已经违规了。这避免了进一步的重复和错误的日志条目。然后,我们可以拍摄该违规车辆的图像并将其保存为车辆类型,并将跟踪 ID 作为其名称。
viol_matrix.append((str((class_name + str(t))))) #line 334
# print(t_start_cm, t_spending, datetime.now())
cropped = image.crop((int(bbox[0]), int (bbox[1]), int(bbox[2]), int(bbox[3])))
cropped.save(
'outputs/caps_of_detections/parking/' + str(
class_name) + str(t) + str(' .jpg'))
以上基本上是我们设计的违章停车检测系统的想法。为避免frame_matrix的内存过载,每超过107 个条目使用以下函数刷新该数组。
#Avoid buffer overflow #line 353
if len(frame_matrix)>10⁷:
frame_matrix=[]
以上就是本文的全部内容啦,感兴趣的小伙伴们可以操练其来了!