日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)
CNN:RCNN、SPPNet、Fast RCNN、Faster RCNN、YOLO V1 V2 V3、SSD、FCN、SegNet、U-Net、DeepLab V1 V2 V3、Mask RCNN
自动驾驶:车道线检测、车速检测、实时通行跟踪、基于视频的车辆跟踪及流量统计
车流量检测实现:多目标追踪、卡尔曼滤波器、匈牙利算法、SORT/DeepSORT、yoloV3、虚拟线圈法、交并比IOU计算
多目标追踪:DBT、DFT、基于Kalman和KM算法的后端优化算法、SORT/DeepSORT、基于多线程的单目标跟踪的多目标跟踪算法KCF
4.9. 车流量统计
学习目标
- 了解车流量统计的方法
- 实现车流量检测
1.基于虚拟线圈法的车辆统计
基于虚拟线圈的车流量统计算法原理与交通道路上的常见的传统的物理线圈类似,由于物理线圈需要埋设在路面之下,因此会有安装、维护费用高,造成路面破坏等问题,而采用基于视频的虚拟线圈的车辆计数方法完全避免了以上问题,且可以针对多个感兴趣区域进行检测。
虚拟线圈车辆计数法的原理是在采集到的交通流视频中,在需要进行车辆计数的道路或路段上设置一条或一条以上的检测线对通过车辆进行检测,从而完成计数工作。检测线的设置原则一般是在检测车道上设置一条垂直于车道线,居中的虚拟线段,通过判断其与通过车辆的相对位置的变化,完成车流量统计的工作。如下图所示,绿色的线就是虚拟检测线:
在该项目中我们进行检测的方法是,计算前后两帧图像的车辆检测框的中心点连线,若该连线与检测线相交,则计数加一,否则计数不变。
那怎么判断两条线段是否相交呢?
假设有两条线段AB,CD,若AB,CD相交,我们可以确定:
1.线段AB与CD所在的直线相交,即点A和点B分别在直线CD的两边;
2.线段CD与AB所在的直线相交,即点C和点D分别在直线AB的两边;
上面两个条件同时满足是两线段相交的充要条件,所以我们只需要证明点A和点B分别在直线CD的两边,点C和点D分别在直线AB的两边,这样便可以证明线段AB与CD相交了。
在上图中,线段AB与线段CD相交,于是我们可以得到两个向量AC,AD,C和D分别在AB的两边,向量AC在向量AB的逆时针方向,AB×AC > 0;向量AD在向量AB的顺时针方向,AB×AD < 0,两叉乘结果异号。
这样,方法就出来了:如果线段CD的两个端点C和D,与另一条线段的一个端点(A或B,只能是其中一个)连成的向量,与向量AB做叉乘,若结果异号,表示C和D分别在直线AB的两边,若结果同号,则表示CD两点都在AB的一边,则肯定不相交。
所以我们利用叉乘的方法来判断车辆是否经过检测线。
2.实现
实现车流量检测的代码如下:
1.检测AB和CD两条直线是否相交
# 检测AB和CD两条直线是否相交
def intersect(A, B, C, D):
return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
# 计算有A,B,C三点构成的向量CA,BA之间的关系,
def ccw(A, B, C):
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
- 遍历跟踪框判断其与检测线是否相交,并进行车辆计数
# 遍历跟踪框
for box in boxes:
(x, y) = (int(box[0]), int(box[1])) # 计算跟踪框的左上角坐标
(w, h) = (int(box[2]), int(box[3])) # 计算跟踪框的宽和高
color = [int(c) for c in COLORS[indexIDs[i] % len(COLORS)]] # 对方框的颜色进行设定
cv2.rectangle(frame, (x, y), (w, h), color, 2) # 将方框绘制在画面上
"""
根据当前帧的检测结果,与上一帧检测的检测结过,进行虚拟线圈完成车辆计数:车流量统计
"""
if indexIDs[i] in previous:
previous_box = previous[indexIDs[i]] # 获取上一帧识别的目标框
(x2, y2) = (int(previous_box[0]), int(previous_box[1])) # 获取上一帧画面追踪框的左上角坐标
(w2, h2) = (int(previous_box[2]), int(previous_box[3])) # 获取上一帧画面追踪框的宽和高
p0 = (int(x + (w - x) / 2), int(y + (h - y) / 2)) # 获取当前帧检测框的中心点
p1 = (int(x2 + (w2 - x2) / 2), int(y2 + (h2 - y2) / 2)) # 获取上一帧检测框的中心点
cv2.line(frame, p0, p1, color, 3) # 将前后两帧图像的检测结果中心连接起来
"""
进行碰撞检测-前后两帧检测框中心点的连线穿过基准线,则进行计数
"""
if intersect(p0, p1, line[0], line[1]):
# 总计数加1
counter += 1
# 判断行进的方向
if y2 < y:
counter_down += 1 # 逆向行驶+1
else:
counter_up += 1 # 正向行驶+1
总结
- 基于虚拟线圈的目标检测,是设置一个垂直于车道的检测线,判断跟踪车辆与检测线之间的关系,完成车辆计数
- 利用叉乘的方法来检验车辆是否与检测线相交
"""
基于虚拟线圈法的车辆统计
1.基于虚拟线圈的车流量统计算法原理与交通道路上的常见的传统的物理线圈类似,由于物理线圈需要埋设在路面之下,因此会有安装、维护费用高,
造成路面破坏等问题,而采用基于视频的虚拟线圈的车辆计数方法完全避免了以上问题,且可以针对多个感兴趣区域进行检测。
2.虚拟线圈车辆计数法的原理是在采集到的交通流视频中,在需要进行车辆计数的道路或路段上设置一条或一条以上的检测线对通过车辆进行检测,
从而完成计数工作。检测线的设置原则一般是在检测车道上设置一条垂直于车道线,居中的虚拟线段,通过判断其与通过车辆的相对位置的变化,
完成车流量统计的工作。如下图所示,绿色的线就是虚拟检测线:
"""
"""
1.虚拟线圈法检测的方法是,计算前后两帧图像的车辆检测框的中心点连线,若该连线与检测线相交,则计数加一,否则计数不变。
2.那怎么判断两条线段是否相交呢?
假设有两条线段AB,CD,若AB,CD相交,我们可以确定:
1.线段AB与CD相交,即点A和点B分别在线段CD的两边;
2.线段CD与AB相交,即点C和点D分别在线段AB的两边;
上面两个条件同时满足是两线段相交的充要条件,所以我们只需要证明点A和点B分别在线段CD的两边,点C和点D分别在线段AB的两边,
这样便可以证明线段AB与CD相交了。
3.在上图中,线段AB与线段CD相交,于是我们可以得到两个向量AC、AD,其中C和D分别在AB的两边。
1.向量AC在向量AB的逆时针方向,得AB×AC > 0。
AB×AC实际是以A点为时钟圆盘的中心点,AB和AC分别是时钟的两个时针。
向量AC在向量AB的逆时针方向的意思即为时针AB向时针AC进行逆时针移动,
也即为B点向C点进行逆时针移动,最终得出AB×AC > 0;
2.向量AD在向量AB的顺时针方向,得AB×AD < 0。
AB×AD实际是以A点为时钟圆盘的中心点,AB和AD分别是时钟的两个时针。
向量AD在向量AB的顺时针方向的意思即为时针AB向时针AD进行顺时针移动,
也即为B点向D点进行顺时针移动,最终得出AB×AD < 0;
最终得出 AB×AC > 0 和 AB×AD < 0 两个向量叉乘的结果为异号。
3.这样,方法就出来了:
如果线段CD的两个端点C和D,与另一条线段AB中的一个端点(A或B,只能是其中一个)连成的向量(比如AC/AD),然后AC/AD与向量AB做叉乘。
若结果异号,表示C和D分别在线段AB的两边;
若结果同号,则表示CD两点都在AB的其中一边,则肯定不相交。
所以我们利用叉乘的方法来判断车辆是否经过检测线。
4.此处的叉乘使用的是两个向量进行叉乘计算
1.向量AB(线段AB):可以是图像画面中的检测线,检测线的设置原则一般是在检测车道上设置一条垂直于车道的虚拟线段。
2.向量CD(线段CD):可以是前后两帧的目标框图像中的中心点所连成的一条线段。
3.那么当线段CD中的C、D两个点(前后两帧的两个中心点)分别位于线段AB(检测线)的两边时,
那么此时可以通过两个向量的叉乘计算,得出是否线段CD和线段AB是否相交。
"""
# 线与线的碰撞检测:叉乘的方法判断两条线是否相交
# 计算叉乘符号
def ccw(A, B, C):
return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
"""
(C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])
1.[0]:线段的其中一点的x坐标
[1]:线段的其中一点的y坐标
2.A点坐标(x1, y1)、B点坐标(x2, y2)、C点坐标(x3, y3)。
A点坐标(x1, y1) 和 B点坐标(x2, y2) 那么得 A×B = x1*y2 - x2*y1。
3.BA为(x2-x1, y2-y1),CA为(x3-x1, y3-y1)。
CA(x3-x1, y3-y1)中将x3-x1看作是w1,将y3-y1看做是h1;BA(x2-x1, y2-y1)中将x2-x1看作是w2,将y2-y1看做是h2;
得出CA为(w1, h1),BA为(w2, h2),那么CA*BA = w1*h2 - w2*h1。
4.可以把 (C[1] - A[1]) * (B[0] - A[0]) - (B[1] - A[1]) * (C[0] - A[0])
转换为 (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]) 来使用,两者等同。
5.BA为(x2-x1, y2-y1),BA还可以看作为(w2, h2)。CA为(x3-x1, y3-y1),CA还可以看作为(w1, h1)。
(C[1] - A[1]):y3-y1,也即h1
(B[0] - A[0]):x2-x1,也即w2
(B[1] - A[1]):y2-y1,也即h2
(C[0] - A[0]):x3-x1,也即w1
6.(C[1] - A[1]) * (B[0] - A[0]) - (B[1] - A[1]) * (C[0] - A[0]) 即可以看做 BA*CA = h1*w2 - h2*w1
7.如果线段CD的两个端点C和D,与另一条线段AB中的一个端点(A或B,只能是其中一个)连成的向量(比如AC/AD),然后AC/AD与向量AB做叉乘。
此处便使用BA*CA做叉乘,根据BA*CA = h1*w2 - h2*w1 得出 (C[1] - A[1]) * (B[0] - A[0]) - (B[1] - A[1]) * (C[0] - A[0])。
"""
# 检测AB和CD两条直线是否相交
def intersect(A, B, C, D):
return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
"""
CA(x3-x1, y3-y1)中将x3-x1看作是w1,将y3-y1看做是h1
BA(x2-x1, y2-y1)中将x2-x1看作是w2,将y2-y1看做是h2
DA(x4-x1, y4-y1)中将x4-x1看作是w3,将y4-y1看做是h3
CB(x3-x2, y3-y2)中将x3-x2看作是w4,将y3-y2看做是h4
DB(x4-x2, y4-y2)中将x4-x2看作是w5,将y4-y2看做是h5
ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)
1.ccw(A, C, D):(D[1] - A[1]) * (C[0] - A[0]) > (C[1] - A[1]) * (D[0] - A[0]) 即可以看做 CA*DA = h3*w1 - h1*w3
2.ccw(B, C, D):(D[1] - B[1]) * (C[0] - B[0]) > (C[1] - B[1]) * (D[0] - B[0]) 即可以看做 CB*DB = h5*w4 - h4*w5
3.ccw(A, B, C):(C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]) 即可以看做 BA*CA = h1*w2 - h2*w1
4.ccw(A, B, D):(D[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (D[0] - A[0]) 即可以看做 BA*DA = h3*w2 - h2*w3
5.ccw(A, C, D) != ccw(B, C, D):
AC×AD < 0 和 BC×BD > 0 两个向量叉乘的结果为异号。
向量AD在向量AC的顺时针方向。向量BD在向量BC的逆时针方向。
6.ccw(A, B, C) != ccw(A, B, D):
AB×AC > 0 和 AB×AD < 0 两个向量叉乘的结果为异号。
向量AC在向量AB的逆时针方向。向量AD在向量AB的顺时针方向。
7.ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D):同时符合上述两者关系则得到AB和CD相交。
"""
另外一种叉乘代码的写法
# -*- coding: utf-8 -*-
def cross_vector(A, B, C):
'''求向量 AB x AC 的结果,叉乘'''
AB = [B[0]-A[0], B[1]-A[1]]
AC = [C[0]-A[0], C[1]-A[1]]
return AB[0] * AC[1] - AB[1] * AC[0]
def intersection(A, B, C, D):
'''A B C D 为四个顶点坐标'''
if (cross_vector(A, B, C)*cross_vector(A, B, D)<0) and (cross_vector(C, D, A)*cross_vector(C, D, B)<0):
return True
else:
return False
# -*- coding: utf-8 -*-
def cross_vector(A, B, C):
'''求向量 AB x AC 的结果,叉乘'''
AB = [B[0]-A[0], B[1]-A[1]]
AC = [C[0]-A[0], C[1]-A[1]]
return AB[0] * AC[1] - AB[1] * AC[0]
def intersection(A, B, C, D):
'''A B C D 为四个顶点坐标'''
if (cross_vector(A, B, C)*cross_vector(A, B, D)<0) and (cross_vector(C, D, A)*cross_vector(C, D, B)<0):
return True
else:
return False
line1=[112, 120, 24, 360]
line2=[90, 180, 300, 60]
A=line1[:2]
B=line1[2:]
C=line2[:2]
D=line2[2:]
print('A:{} B:{} C:{} D:{}'.format(A, B, C, D))
if intersection(A, B, C, D):
print('两线段相交!')
else:
print('两线段不相交')