很多人解说DeepSORT时都是按照论文的思路说,陷入了细节,还贴上公式,让初次接触的人看完还是感觉很懵,我力求说得简单易懂点。

      DeepSORT相对于SORT增加了个抽取特征数据的深度学习模型,这个模型可以是目标检测的卷积提取特征部分,也可以reid模型,早期的python版提供了两种简单实现,C++版使用的yolov3,后来有人拿去改成其他比较新的模型,例如yolov5,反正不论使用什么模型,本质都是为了提取跟踪目标的特征数据。

      SORT和DeepSORT的核心思路可以简单地说是把跟踪目标在以往帧里被检测到的检测历史数据存入tracks列表,第一次加入tracks列表时分配一个track id,当前帧里检测出来的目标检测数据拿去和tracks列表里的历史数据做匹配,匹配上了的就返回tracks列表里的对应匹配的track,没匹配成功的就认为是第一次被检测到,按第一次加入tracks列表处理。

     这里的匹配就是力求把当前帧里检测出的每个目标(object),在tracks列表里找到这个目标在以往帧中被检测到时的track,怎么找就是核心技术所在,当前帧检测到的目标怎么和tracks列表里的历史帧里检测到的目标进行匹配呢?首先,最容易想到的肯定是将两者的bbox做iou计算啊,这种匹配也是计算最快的,可是呢,实际应用场景中会有漏检、和遮挡情况发生,导致某些目标有时没被检测出来,等再次被检测出来时,目标的位置已经变化比较大,另外,如果目标运动较快,帧和帧之间目标所在位置也可能变化较大,所以光凭直接iou匹配显然是不够的,对于连续两帧之间的位置可能变化,引入卡尔曼滤波来根据tracks列表中每个track中记录的上次位置做运动预测,预测出这些track假如在当前帧出现的话预计所在位置(其实就是bbox坐标数据),然后拿当前帧中检测出来的目标和这些更新了位置的track的位置数据做iou计算并匹配,这样直观感觉应该比直接拿没有做位置预测更新的track来和当前帧中检测出来的目标做iou计算并匹配更准一点。但是这样做还只适用于理想的情况,也就是目标在连续的两帧里都被检测到了,或者目标短时间内没被检测到(例如间隔一帧被检测到),对于连续没被检测到的次数稍多一点,卡尔曼滤波预测也不起作用了(因为看看卡尔曼滤波的历史不用仔细看它的那些计算公式就可以直观感觉到,卡尔曼滤波跟踪导弹时是依赖于雷达的连续测量获取实际测量到的数据来校正预测数据的,如果导弹中间一段时间被雷达跟丢了,那神仙都不知道导弹到哪里了,除非飞行轨迹是接近有规律的匀速直线运动或者自由落体运动 ^_^,那还怎么预测呢),也就是目标一段时间内跟丢了,目标跟丢了再次出现怎么判断它是谁呢?也就是当前帧内检测出来的物理目标和tracks列表中用来记录它最后一次被检测出来的位置的对应的track的只根据位置计算iou来匹配是根本就匹配不上了,这就需要引入其他方法来解决这个问题,DeepSORT相对于SORT的改进引入抽取特征的模型来抽取目标的特征数据,在目标加入到tracks列表里时同时保存到track里,这样当将当前帧里检测出的目标的特征数据和tracks列表里的track做iou匹比失效时,计算当前帧检测出来的目标的特征数据向量逐一和tracks列表里的track里的特征数据向量的consine值得出相似度,大于相似度阈值的就可认为是匹配的,于是使用当前帧检测出来的目标的数据来更新匹配成功的track的数据,并返回这个track (本质是使用track id)。实际匹比中并不是非要等到iou匹比失败了才使用特征数据相似度匹比,而是两者融合的:先对每个track使用卡尔曼滤波预测在当前帧中的位置,计算各个track的特征数据和当前帧里检测出来的各个目标的特征数据的相似度获得cosine代价矩阵,计算和当前帧中检测出来的目标的位置的马氏距离,当马氏距离大于指定的阈值的(认为位置上相差太远),就把代价矩阵里对应位置的相似度值设置为一个很大的值,也就是使用马氏距离约束cosine相似度,这时即使真正计算出来的1-cosine值很小,也把这个值修改为一个很大的值,就是强制不认为两者不是同一个目标的不同轨迹。

     另外, 由于论文中为了防止误识别,设了个确认参数(代码实现中为n_init=3),也就是一个目标要连续三次被检测出来才被确认是有效的目标 ,如果前没达到三次连续被检测出来就漏检或者遮挡或者目标真的运动出画面了,那么其对应的track就被从tracks列表中删了。

    当前很多模型算法在理论指标上表现不错(所谓的达到了SOTA),实际应用到项目中时可却表现不咋的, DeepSORT当然也是这样,DeepSORT这样的跟踪算法首先只是在视频中帧是连续时表现还可以,如果视频播放过程中因为实际需要而需要间歇性丢帧,那么DeepSORT的iou匹配就基本不可靠了,用马氏距离约束相似度代价矩阵这时也就不合理了,还有上面的需要连续三次被检测到才被确认纳入跟踪,这个些参数设置都不大合理了,还有光凭特征相似度对比很多时候也是不可靠的,这个原因弄过reid项目的应该知道,光照、角度、遮挡截断、尺寸和比例变化等等因素都会导致同一个物体的两个特征的相似度达不到阈值而被认为是新的人,所以有人加入了光流数据或深度图像数据来辅助弥补,在上面的iou和特征相似度匹配失败后使用光流或者深度数据(可能也加iou约束)再次来做匹比以增加匹比成功的可能性,当然这些方法也有在自身的问题,首先也不能做到非常可靠,其次光流和深度数据这些数据计算量大,在边缘设备上性能会比较差,另外,深度数据的获取还依赖于特定设备。

     还有一点就是代码实现上的问题,deepsort的python版和C++版实现代码里一般都加了个buget机制,这个我觉得不是很合理,它无非提升了一点点性能,但是它约束了特征的存储量,当一个目标频繁在不同角度、距离或光照或遮挡条件下出现时,因为buget限制而被重写丢失,特征数据得不到充分保存加大了匹比失败,如果改为时间约束,然后再设置一个总的特征数据容量上限这样应该比较合理点。另外上篇文章中提到那个C++版的DeepSORT对buget缓冲区的循环写入存在会导致系统崩溃的bug,需要修改。

    最后,从实际工程落地的角度上来说,DeepSORT的现有实现代码(无论是python还是C++版)没有考虑记录Detection Objects和Tracked Objected之间的对应关系,还有置信度和类别都没有记录,这点在实际项目中可能不能满足要求,例如Deepstream tracker plugin就要求我们在实现tracker library里返回tracker objects时要指定和原来传入的detection objects之间的关联关系,不提供的话也可以跑,但是所有的tracked objects的得分都变成了-0.1,因为没有关联就认为这些objects全是由tracker这边补充提供的,而不是检测出来的,这种设计不大合理,本来应该是既然以tracked objects为准往下游提供数据,那么tracked objects就应该忠实记录当时对应的detection objects的相关数据,置信度什么的都应该以detection objects的为准,只有确实是由tracker预测提供的而不是检测出来的object的置信度才可以设置成特殊值-0.1,但是由于这个plugin的源码是没有开放的,所以我们做不了什么。