简介
谈到 MOT
,SORT
和 DEEP SORT
是两个知名度比较高的算法,当然这也是因为他们的效果比较好才出名的,鉴于是刚刚接触目标跟踪这块,所以就先把 SORT
的论文和源码看了下,毕竟像传统算法还是能看懂的,至于 DL
这种东西,随便看看就好,反正也是看不懂,就没有必要浪费太多时间;
一、基础知识介绍
提到 SORT
,几乎所有的博文都会提及匈牙利算法和卡尔曼滤波,而且上来就是一堆公式,各种推导,我有幸看过几篇博文,但是看完还是有点懵,因为看完后还是不知道该怎么用,在这里,不推导公式,不进行计算,只根据自己的理解讲讲计算逻辑和使用方法:
卡尔曼滤波
既然提到这是个 *
滤波算法,那肯定就是进行过滤的,通俗点,就是根据某种特定的计算方式将一堆数据划分成多堆,而每一堆都会有共同的特点;而卡尔曼滤波的作用就是将期望与现实维护一个平衡,使每次的期望都是站在现状的基础上做出的决定,从天,周,直至年度,时间从短到长,逐步实现,逐步修改,从而避免期望越大,失望越大的存在。
卡尔曼滤波主要分成两个过程:预测和更新,预测是指根据现有的数据预判下一步能到达哪里,而更新则是实际到达的位置对上次预测的位置进行纠偏处理。
匈牙利算法
匈牙利算法实现的是最优分配的问题,以 IOU
的值作为权重参数,求取最佳分配。
二、SORT基本逻辑
对于可解释性算法的了解,个人认为逻辑最为重要,其次是代码和论文,可以参考,但是为了更好的了解其中的计算逻辑,还是需要借助论文和代码的,本文会简单通俗的讲解下 SORT
的逻辑,其中也会引用部分网络资源,具体步骤如下:
1. 物体状态描述
SORT
接收的输入数据是图片/视频中的物体状态,而这些状态可以由深度学习网络模型进行检测与识别,再把识别结果作为跟踪算法的输入,在这里,官网提供的物体的姿态描述为:
\alpha = [u, v, s, r, u^, v^, s^]
其中,u,v
表示中心点, s,r
表示面积和长宽比, u^ , v^, s^
表示下一帧的坐标;
2. 卡尔曼预测
根据 detection
的结果,卡尔曼滤波算法会对这一块进行预测,预判下一帧可能会到达的问题;
3. 匈牙利分配
根据当前帧 detection
的检测结果和卡尔曼滤波的预测测姿态计算 IOU
( IOU
的计算也有不同的定义喲),然后可以构成二维矩阵,将 IOU
的值作为权重参数,然后根据综合权重最小的原则实现检测框与预测框的分配功能。
经过分配后,整体数据就分为3类:
a. 满足设置的阈值条件,可实现认为正确的配对—成功追踪
b. 检测中有,但预测中没有—新增
c. 预测中有,检测中没有—出了视野
这里面有个bug
:其一为对于同一个物体,若中间丢帧,可能会在跟踪计算时出错;其二位若一个物体重复在视野中出现,也会出现多技术的问题
4. 卡尔曼更新
步骤3完成了检测物的分类,根据匹配结果,更新对应索引物体的姿态。
三. 代码解读
核心算法如下:
class Sort(object):
def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):
"""
Sets key parameters for SORT
"""
self.max_age = max_age
self.min_hits = min_hits
self.iou_threshold = iou_threshold
self.trackers = []
self.frame_count = 0
def update(self, dets=np.empty((0, 5))):
"""
Params:
dets - a numpy array of detections in the format [[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
Requires: this method must be called once for each frame even with empty detections (use np.empty((0, 5)) for frames without detections).
Returns the a similar array, where the last column is the object ID.
NOTE: The number of objects returned may differ from the number of detections provided.
"""
logging.info(dets)
self.frame_count += 1
# get predicted locations from existing trackers.
trks = np.zeros((len(self.trackers), 5))
to_del = []
ret = []
for t, trk in enumerate(trks):
# logging.info(t)
# pos the last detection [x1, y1, x2, y2]
pos = self.trackers[t].predict()[0]
trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
if np.any(np.isnan(pos)):
to_del.append(t)
# logging.info(len(to_del))
logging.info(to_del)
# logging.info(trks)
# the same is predeal trks generaly
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
# logging.info(trks)
for t in reversed(to_del):
self.trackers.pop(t)
logging.info(len(self.trackers))
# assign detections&&trackers, get un/matched relationship
matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,trks, self.iou_threshold)
# update matched trackers with assigned detections
# update real state
for m in matched:
self.trackers[m[1]].update(dets[m[0], :])
# create and initialise new trackers for unmatched detections
for i in unmatched_dets:
trk = KalmanBoxTracker(dets[i,:])
self.trackers.append(trk)
i = len(self.trackers)
logging.info("the number of trackers : %d", i)
for trk in reversed(self.trackers):
d = trk.get_state()[0]
if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1)) # +1 as MOT benchmark requires positive
i -= 1
# remove dead tracklet
if(trk.time_since_update > self.max_age):
self.trackers.pop(i)
if(len(ret)>0):
return np.concatenate(ret)
return np.empty((0,5))
四. 小结
SORT
的基本原理是匈牙利算法和卡尔曼滤波算法,所以,他也又卡尔曼滤波算法本身的一些缺陷,比如物体运动位移小时,误差就会很大,以及出现某一帧漏检时,会出现跟踪错误的情况,但是,这个方法不矢为一个很好的模板,在其基础上进行各种优化,更好的适配应用场景。