叉乘:基于虚拟线圈法的车流量统计

日萌社

人工智能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

计算交并比IOU、候选框不同表示方式之间的转换

卡尔曼滤波器

卡尔曼滤波器实践

目标估计模型-卡尔曼滤波

匈牙利算法

数据关联:利用匈牙利算法对目标框和检测框进行关联

SORT、DeepSORT

多目标追踪

yoloV3模型

基于yoloV3的目标检测

叉乘:基于虚拟线圈法的车流量统计

视频中的车流量统计


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])
  1. 遍历跟踪框判断其与检测线是否相交,并进行车辆计数
# 遍历跟踪框
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.虚拟线圈车辆计数法的原理是在采集到的交通流视频中,在需要进行车辆计数的道路或路段上设置一条或一条以上的检测线对通过车辆进行检测,
      从而完成计数工作。检测线的设置原则一般是在检测车道上设置一条垂直于车道线,居中的虚拟线段,通过判断其与通过车辆的相对位置的变化,
      完成车流量统计的工作。如下图所示,绿色的线就是虚拟检测线:
"""

"""
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('两线段不相交')

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
C#中可以使用第三方库MathNet.Numerics来进行矩阵运算。这个库提供了丰富的线性代数算,包括叉乘、除和转置等。 以下是使用MathNet.Numerics进行矩阵运算的示例代码: ```csharp using MathNet.Numerics.LinearAlgebra; // 创建矩阵 Matrix<double> matrixA = Matrix<double>.Build.DenseOfArray(new double[,] { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }); Matrix<double> matrixB = Matrix<double>.Build.DenseOfArray(new double[,] { {9, 8, 7}, {6, 5, 4}, {3, 2, 1} }); // 矩阵乘 Matrix<double> matrixC = matrixA * matrixB; // 矩阵除 Matrix<double> matrixD = matrixA / matrixB; // 矩阵转置 Matrix<double> matrixE = matrixA.Transpose(); // 矩阵叉乘 Vector<double> vectorA = Vector<double>.Build.Dense(new double[] {1, 2, 3}); Vector<double> vectorB = Vector<double>.Build.Dense(new double[] {4, 5, 6}); Vector<double> vectorC = vectorA.CrossProduct(vectorB); ``` 以上代码中,我们使用了Matrix和Vector类来表示矩阵和向量,这些类提供了丰富的线性代数算。在创建矩阵时,我们使用Build类提供的DenseOfArray方来从数组中创建矩阵。在进行矩阵乘、除和转置等操作时,我们直接使用矩阵的运算符重载。在进行矩阵叉乘时,我们使用向量的CrossProduct方来计算叉乘结果。 需要注意的是,MathNet.Numerics库中提供了多种矩阵和向量类型,如DenseMatrix、SparseMatrix、DenseVector、SparseVector等,不同类型的矩阵和向量适用于不同的场景。在实际使用中,需要根据具体情况选择合适的类型来进行矩阵运算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值