目录
多目标跟踪(一)Sort —— YOLOV5为上游检测网络
前言
实现思路
零、Yolov5检测网络
一、卡尔曼跟踪器
1.状态变量
二、匈牙利KM算法实现
三、跟踪结果和检测结果融合
结果
总结
多目标跟踪发展到现在,已经有很多比Sort优秀的算法了(而且Sort算法的实际使用起来的性能确实比较差),但个人感觉Sort作为多目标跟踪的入门,还是有很多值得学习的地方的,这里稍微跟着源码复现一下Sort算法。
Sort算法实现起来比较简单,其实就是一个卡尔曼跟踪器+匈牙利算法的指派就可以实现了。卡尔曼跟踪器参考我下面这篇文章,再根据论文给的状态表达就可以很容易写出来。
卡尔曼滤波二维实例——跟踪sin正弦函数
下面只是说一下基本的思路,如果需要完整代码,可以在下面这个链接clone
YoloV5_Sort !!!!!!!!!!
要实现Sort算法,我们主要遵循以下几个步骤
这里我们只需要把yolov5检测每个帧中的boundingbox保存下来,然后传给跟踪器就好了,你也可以自己先把每帧检测出来的结果转为卡尔曼跟踪器的格式,也可以在卡尔曼滤波器里转,都是一样的,具体细节还是看完整代码里的吧。
我把一些需要修改的地方,放在下面
因为我们这里要预测一个框的变化,所以我们这里的状态变量有7个,分别为,检测框中心的横坐标,检测框中心的纵坐标,检测框的面积,长宽比,横坐标速度,纵坐标速度,面积速度。
(其实这里用xyxy的形式也可以,但是因为这里需要表达检测框的面积s,所以用了centerx,centery,s,r形式)
然后在作者对于横坐标速度,纵坐标速度,面积速度都是考虑的恒速模型,即是不变的(这也是Sort算法性能差的一个比较重要的原因吧)
当然我们的输入变量就变成了4个,即我们检测出来的boundingbox,xyxy的形式
然后后面卡尔曼跟踪器中的其他值也根据这个状态变量进行修改就好了。
这个就不需要自己实现了,不管是sklearn还是scipy的包里都有这个现成的函数,这里主要是用于KM算法分配框的costmatrix为IOU组成的matrix,即Sort是通过判断预测出来的框和检测出来的框之间的IOU来进行分配的(这就造成了Sort算法注定无法解决两个行人重叠路过的问题!!,所以其IDSW这个指标很差)
且其直接丢弃掉IOU小于阈值的作法也导致了无法解决遮挡的问题。
下面放出计算分配和IOU的代码,这里都是直接调用了现成的scipy中的KM算法,和yolov5源码中用于计算IOU的代码。(当然这里,你也可以用DIOU或者GIOU去计算,但其实没什么用)
from yolov5.utils.metrics import box_iou
from scipy.optimize import linear_sum_assignment
# 匈牙利匹配
def linear_assignment(cost_matrix):
x, y = linear_sum_assignment(cost_matrix)
return np.array(list(zip(x, y)))
# 用于计算det中的目标框和卡尔曼滤波追踪的目标框的iou
def iou_batch(det_box, track_box2):
# 将list转为numpy格式
det_box = np.array(det_box)
track_box2 = np.array(track_box2)
# 再将numpy转为tensor,速度更快
det_box = torch.from_numpy(det_box)
# 注意这里box2
track_box2 = torch.from_numpy(track_box2[:, 0:4])
# 调库算iou
iou = box_iou(det_box, track_box2)
return np.array(iou)
根据卡尔曼滤波的结果和目标检测器的结果,再利用上面的KM算法就可以去匹配出当前帧中的每个目标的ID。在这其中还有一些细节,如IOU小于一定阈值的框就视为丢失目标,丢失掉的框的初始速度设为很大的值。跟踪器的跟踪框只有一帧的寿命,即这一帧没有检测到的框,下一帧就会丢掉,换成一个新的跟踪目标。(IDSW很大的另一个原因,不过也是受限与其卡尔曼滤波构造的模型是恒速,第二是作者本身就没有考虑IDSW的问题)
def associate_detections_to_trackers(detections, trackers, iou_threshold=0.3):
# 初始化时,trackers为空,直接返回空匹配结果
if len(trackers) == 0:
return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)
# 这里trackers多一列ID列
iou_matrix = iou_batch(detections, trackers)
# [[0.73691421 0. 0. 0. ]
# [0. 0.89356082 0. 0. ]
# [0. 0. 0.76781823 0. ]]
if min(iou_matrix.shape) > 0:
a = (iou_matrix > iou_threshold).astype(np.int32)
# [[1 0 0 0]
# [0 1 0 0]
# [0 0 1 0]]
# print(a.sum(1)): [1 1 1]
# print(a.sum(0)): [1 1 1 0]
# a.sum(1)表示将矩阵所有列累加,a.sum(0)表示将矩阵所有行累加
# 如果大于0.3的位置恰好一一对应,可直接得到匹配结果,否则利用匈牙利算法进行匹配
if a.sum(1).max() == 1 and a.sum(0).max() == 1:
matched_indices = np.stack(np.where(a), axis=1)
# [[0 0]
# [1 1]
# [2 2]]
else:
matched_indices = linear_assignment(-iou_matrix)
else:
matched_indices = np.empty(shape=(0, 2))
unmatched_detections = []
for d, det in enumerate(detections):
if d not in matched_indices[:, 0]:
unmatched_detections.append(d)
unmatched_trackers = []
for t, trk in enumerate(trackers):
if t not in matched_indices[:, 1]:
unmatched_trackers.append(t)
# print(unmatched_detections) : []
# print(unmatched_trackers) : [3]
# 匈牙利算法匹配出的结果,未必符合IOU大于0.3,需要再进行一次筛选
# 如果匹配后的IOU数值依旧很小,则同样默认为未匹配成功
matches = []
for m in matched_indices:
if iou_matrix[m[0], m[1]] < iou_threshold:
unmatched_detections.append(m[0])
unmatched_trackers.append(m[1])
else:
matches.append(m.reshape(1, 2))
if len(matches) == 0:
matches = np.empty((0, 2), dtype=int)
else:
matches = np.concatenate(matches, axis=0)
# print(matches): [[0 0] [1 1] [2 2]]
# print(np.array(unmatched_detections)): []
# print(np.array(unmatched_trackers)): [3]
return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
整体算法是比较简单和直观的,具体的代码可以看一下上面链接中的源码
可以看到重叠的人对于这个算法是直接舍去的,然后IDSW的结果也很严重。
后面会出作者在一年后,也是现在用的比较多的deep_sort算法