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

日萌社

人工智能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.6. 数据关联

学习目标

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

在这里我们对检测框和跟踪框进行匹配,整个流程是遍历检测框和跟踪框,并进行匹配,匹配成功的将其保留,未成功的将其删除。

def associate_detections_to_trackers(detections, trackers, iou_threshold=0.3):
    """
    将检测框bbox与卡尔曼滤波器的跟踪框进行关联匹配
    :param detections:检测框
    :param trackers:跟踪框,即跟踪目标
    :param iou_threshold:IOU阈值
    :return:跟踪成功目标的矩阵:matchs
            新增目标的矩阵:unmatched_detections
            跟踪失败即离开画面的目标矩阵:unmatched_trackers
    """
    # 跟踪目标数量为0,直接构造结果
    if (len(trackers) == 0) or (len(detections) == 0):
        return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)

    # iou 不支持数组计算。逐个计算两两间的交并比,调用 linear_assignment 进行匹配
    iou_matrix = np.zeros((len(detections), len(trackers)), dtype=np.float32)
    # 遍历目标检测的bbox集合,每个检测框的标识为d
    for d, det in enumerate(detections):
        # 遍历跟踪框(卡尔曼滤波器预测)bbox集合,每个跟踪框标识为t
        for t, trk in enumerate(trackers):
            iou_matrix[d, t] = iou(det, trk)
    # 通过匈牙利算法将跟踪框和检测框以[[d,t]...]的二维矩阵的形式存储在match_indices中
    result = linear_sum_assignment(-iou_matrix)
    matched_indices = np.array(list(zip(*result)))

    # 记录未匹配的检测框及跟踪框
    # 未匹配的检测框放入unmatched_detections中,表示有新的目标进入画面,要新增跟踪器跟踪目标
    unmatched_detections = []
    for d, det in enumerate(detections):
        if d not in matched_indices[:, 0]:
            unmatched_detections.append(d)
    # 未匹配的跟踪框放入unmatched_trackers中,表示目标离开之前的画面,应删除对应的跟踪器
    unmatched_trackers = []
    for t, trk in enumerate(trackers):
        if t not in matched_indices[:, 1]:
            unmatched_trackers.append(t)
    # 将匹配成功的跟踪框放入matches中
    matches = []
    for m in matched_indices:
        # 过滤掉IOU低的匹配,将其放入到unmatched_detections和unmatched_trackers
        if iou_matrix[m[0], m[1]] < iou_threshold:
            unmatched_detections.append(m[0])
            unmatched_trackers.append(m[1])
        # 满足条件的以[[d,t]...]的形式放入matches中
        else:
            matches.append(m.reshape(1, 2))
    # 初始化matches,以np.array的形式返回
    if len(matches) == 0:
        matches = np.empty((0, 2), dtype=int)
    else:
        matches = np.concatenate(matches, axis=0)

    return matches, np.array(unmatched_detections), np.array(unmatched_trackers)

总结:

利用匈牙利算法完成目标检测框和跟踪框的匹配


from scipy.optimize import linear_sum_assignment
import numpy as np
from numba import jit

@jit
def iou(bb_test, bb_gt):
    """
    在两个box间计算IOU
    :param bb_test: box1 = [x1y1x2y2] 即 [左上角的x坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标]
    :param bb_gt: box2 = [x1y1x2y2]
    :return: 交并比IOU
    """
    xx1 = np.maximum(bb_test[0], bb_gt[0]) #获取交集面积四边形的 左上角的x坐标
    yy1 = np.maximum(bb_test[1], bb_gt[1]) #获取交集面积四边形的 左上角的y坐标
    xx2 = np.minimum(bb_test[2], bb_gt[2]) #获取交集面积四边形的 右下角的x坐标
    yy2 = np.minimum(bb_test[3], bb_gt[3]) #获取交集面积四边形的 右下角的y坐标
    w = np.maximum(0., xx2 - xx1) #交集面积四边形的 右下角的x坐标 - 左上角的x坐标 = 交集面积四边形的宽
    h = np.maximum(0., yy2 - yy1) #交集面积四边形的 右下角的y坐标 - 左上角的y坐标 = 交集面积四边形的高
    wh = w * h #交集面积四边形的宽 * 交集面积四边形的高 = 交集面积
    """
    两者的交集面积,作为分子。
    两者的并集面积作为分母。
    一方box框的面积:(bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])
    另外一方box框的面积:(bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1]) 
    """
    o = wh / ( (bb_test[2] - bb_test[0]) * (bb_test[3] - bb_test[1])
               + (bb_gt[2] - bb_gt[0]) * (bb_gt[3] - bb_gt[1])
               - wh)
    return o

"""
利用匈牙利算法对跟踪目标框和yoloV3检测结果框进行关联匹配,整个流程是遍历检测结果框和跟踪目标框,并进行两两的相似度最大的比对。
相似度最大的认为是同一个目标则匹配成功的将其保留,相似度低的未成功匹配的将其删除。
使用的是通过yoloV3得到的“并且和预测框相匹配的”检测框来更新卡尔曼滤波器得到的预测框。
    detections:通过yoloV3得到的检测结果框
    trackers:通过卡尔曼滤波器得到的预测结果跟踪目标框
    iou_threshold=0.3:大于IOU阈值则认为是同一个目标则匹配成功将其保留,小于IOU阈值则认为不是同一个目标则未成功匹配将其删除。
    return返回值:
        matches:跟踪成功目标的矩阵。即前后帧都存在的目标,并且匹配成功同时大于iou阈值。
        np.array(unmatched_detections):新增目标指的就是存在于detections检测结果框当中,但不存在于trackers预测结果跟踪目标框当中。
        np.array(unmatched_trackers):离开画面的目标指的就是存在于trackers预测结果跟踪目标框当中,但不存在于detections检测结果框当中。
"""
def associate_detections_to_trackers(detections, trackers, iou_threshold=0.3):
    """
    将检测框bbox与卡尔曼滤波器的跟踪框进行关联匹配
    :param detections:通过yoloV3得到的检测结果框
    :param trackers:通过卡尔曼滤波器得到的预测结果跟踪目标框
    :param iou_threshold:大于IOU阈值则认为是同一个目标则匹配成功将其保留,小于IOU阈值则认为不是同一个目标则未成功匹配将其删除。
    :return:跟踪成功目标的矩阵:matchs。即前后帧都存在的目标,并且匹配成功同时大于iou阈值。
            新增目标的矩阵:unmatched_detections。
                            新增目标指的就是存在于detections检测结果框当中,但不存在于trackers预测结果跟踪目标框当中。
            跟踪失败即离开画面的目标矩阵:unmatched_trackers。
                            离开画面的目标指的就是存在于trackers预测结果跟踪目标框当中,但不存在于detections检测结果框当中。
    """
    """
    1.跟踪器链(列表):
        实际就是多个的卡尔曼滤波KalmanBoxTracker自定义类的实例对象组成的列表。
        每个目标框都有对应的一个卡尔曼滤波器(KalmanBoxTracker实例对象),
        KalmanBoxTracker类中的实例属性专门负责记录其对应的一个目标框中各种统计参数,
        并且使用类属性负责记录卡尔曼滤波器的创建个数,增加一个目标框就增加一个卡尔曼滤波器(KalmanBoxTracker实例对象)。
        把每个卡尔曼滤波器(KalmanBoxTracker实例对象)都存储到跟踪器链(列表)中。
    2.unmatched_detections(列表):
        检测框中出现新目标,但此时预测框(跟踪框)中仍不不存在该目标,
        那么就需要在创建新目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象),
        然后把新目标对应的KalmanBoxTracker类的实例对象放到跟踪器链(列表)中。
    3.unmatched_trackers(列表):
        当跟踪目标失败或目标离开了画面时,也即目标从检测框中消失了,就应把目标对应的跟踪框(预测框)从跟踪器链中删除。
        unmatched_trackers列表中保存的正是跟踪失败即离开画面的目标,但该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)
        此时仍然存在于跟踪器链(列表)中,因此就需要把该目标对应的预测框/跟踪框(KalmanBoxTracker类的实例对象)从跟踪器链(列表)中删除出去。
    """
    # 跟踪目标数量为0,直接构造结果
    if (len(trackers) == 0) or (len(detections) == 0):
        """
        如果卡尔曼滤波器得到的预测结果跟踪目标框len(trackers)为0 或者 yoloV3得到的检测结果框len(detections)为0 的话,
        跟踪成功目标的矩阵:matchs 为 np.empty((0, 2), dtype=int)
        新增目标的矩阵:unmatched_detections 为 np.arange(len(detections))
        跟踪失败即离开画面的目标矩阵:unmatched_trackers 为 np.empty((0, 5), dtype=int)
        """
        return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)

    """ 因为要计算所有检测结果框中每个框 和 所有跟踪目标框中每个框 两两之间 的iou相似度计算,
        即所有检测结果框中每个框 都要和 所有跟踪目标框中每个框 进行两两之间 的iou相似度计算,
        所以iou_matrix需要初始化为len(detections检测结果框) * len(trackers跟踪目标框) 形状的0初始化的矩阵。 """
    # iou 不支持数组计算。逐个计算两两间的交并比,调用 linear_assignment 进行匹配
    iou_matrix = np.zeros((len(detections), len(trackers)), dtype=np.float32)
    # 遍历目标检测(yoloV3检测)的bbox集合,每个检测框的标识为d,det为检测结果框
    for d, det in enumerate(detections):
        # 遍历跟踪框(卡尔曼滤波器预测)bbox集合,每个跟踪框标识为t,trackers为跟踪目标框
        for t, trk in enumerate(trackers):
            """ 
            遍历每个检测结果框 和 遍历每个跟踪目标框 进行两两之间 的iou相似度计算。
            行索引值对应的是目标检测框。列索引值对应的是跟踪目标框。
            """
            iou_matrix[d, t] = iou(det, trk)

    """ 
    row_ind, col_ind=linear_sum_assignment(-iou_matrix矩阵) 
        通过匈牙利算法得到最优匹配度的“跟踪框和检测框之间的”两两组合。
        通过相同下标位置的行索引和列索引即可从iou_matrix矩阵得到“跟踪框和检测框之间的”两两组合最优匹配度的IOU值。
        -iou_matrix矩阵:linear_assignment的输入是cost成本矩阵,IOU越大对应的分配代价应越小,所以iou_matrix矩阵需要取负号。
        row_ind:行索引构建的一维数组。行索引值对应的是目标检测框。
        col_ind:列索引构建的一维数组。列索引值对应的是跟踪目标框。
        比如:
            row_ind:[0 1 2 3]。col_ind列索引:[3 2 1 0]。
            np.array(list(zip(*result))):[[0 3] [1 2] [2 1] [3 0]]
    """
    # 通过匈牙利算法将跟踪框和检测框以[[d,t]...]的二维矩阵的形式存储在match_indices中
    result = linear_sum_assignment(-iou_matrix)
    matched_indices = np.array(list(zip(*result)))

    """ np.array(unmatched_detections):新增目标指的就是存在于detections检测结果框当中,但不存在于trackers预测结果跟踪目标框当中 """
    # 记录未匹配的检测框及跟踪框
    # 未匹配的检测框放入unmatched_detections中,表示有新的目标进入画面,要新增跟踪器跟踪目标
    unmatched_detections = []
    for d, det in enumerate(detections):
        """ matched_indices[:, 0]:取出的是每行的第一列,代表的是目标检测框。
           如果目标检测框的索引d不存在于匹配成功的matched_indices中每行的第一列的话,代表目标检测框中有新的目标出现在画面中,
           则把未匹配的目标检测框放入到unmatched_detections中表示需要新增跟踪器进行跟踪目标。
        """
        if d not in matched_indices[:, 0]:
            unmatched_detections.append(d)

    """ np.array(unmatched_trackers):离开画面的目标指的就是存在于trackers预测结果跟踪目标框当中,但不存在于detections检测结果框当中 """
    # 未匹配的跟踪框放入unmatched_trackers中,表示目标离开之前的画面,应删除对应的跟踪器
    unmatched_trackers = []
    for t, trk in enumerate(trackers):
        """ matched_indices[:, 1]:取出的是每行的第二列,代表的是跟踪目标框。
           如果跟踪目标框的索引t不存在于匹配成功的matched_indices中每行的第二列的话,代表跟踪目标框中有目标离开了画面,
           则把未匹配的跟踪目标框放入到unmatched_trackers中表示需要删除对应的跟踪器。
        """
        if t not in matched_indices[:, 1]:
            unmatched_trackers.append(t)

    """ matches:跟踪成功目标的矩阵。即前后帧都存在的目标,并且匹配成功同时大于iou阈值。
        即把匹配成功的matched_indices中的并且小于iou阈值的[d,t]放到matches中。
    """
    # 将匹配成功的跟踪框放入matches中
    matches = []
    for m in matched_indices:
        """
        m[0]:每行的第一列,代表的是目标检测框。m[1]:每行的第二列,代表的是跟踪目标框。
        iou_matrix[m[0], m[1]] < iou_threshold:
            根据目标检测框的索引作为行索引,跟踪目标框的索引作为列索引,
            即能找到“跟踪框和检测框之间的”两两组合最优匹配度的IOU值,如果该IOU值小于iou阈值的话,
            则把目标检测框放到unmatched_detections中,把跟踪目标框放到unmatched_trackers中。
        """
        # 过滤掉IOU低的匹配,将其放入到unmatched_detections和unmatched_trackers
        if iou_matrix[m[0], m[1]] < iou_threshold:
            unmatched_detections.append(m[0]) #m[0]:每行的第一列,代表的是目标检测框。
            unmatched_trackers.append(m[1])   #m[1]:每行的第二列,代表的是跟踪目标框。
        # 满足条件的以[[d,t]...]的形式放入matches中
        else:
            """ 存储到列表中的每个元素的形状为(1, 2) """
            matches.append(m.reshape(1, 2))

    """
    如果矩阵matches中不存在任何跟踪成功的目标的话,则创建空数组返回。
    numpy.concatenate((a1,a2,...), axis=0):能够一次完成多个数组a1,a2,...的拼接。
    >>> a=np.array([1,2,3])
    >>> b=np.array([11,22,33])
    >>> c=np.array([44,55,66])
    >>> np.concatenate((a,b,c),axis=0)  # 默认情况下,axis=0可以不写
    array([ 1,  2,  3, 11, 22, 33, 44, 55, 66]) #对于一维数组拼接,axis的值不影响最后的结果
    """
    # 初始化matches,以np.array的形式返回
    if len(matches) == 0:
        matches = np.empty((0, 2), dtype=int)
    else:
        """ 
        np.concatenate(matches, axis=0):
            [array([[0, 0]], dtype=int64), array([[1, 1]], dtype=int64),  。。。] 转换为 [[0, 0] [1, 1] 。。。]
        """
        matches = np.concatenate(matches, axis=0) # 默认情况下,axis=0可以不写

    return matches, np.array(unmatched_detections), np.array(unmatched_trackers)

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

あずにゃん

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

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

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

打赏作者

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

抵扣说明:

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

余额充值