news 2026/10/14 1:04:10

多目标跟踪算法Deep SORT全拆解:卡尔曼滤波、级联匹配与ReID实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多目标跟踪算法Deep SORT全拆解:卡尔曼滤波、级联匹配与ReID实战

做过多目标跟踪项目的人,一定绕不开Deep SORT这个名字。直到现在,很多落地的视觉感知系统里,它仍然是数据关联环节的默认候选方案之一;很多初学者第一次接触“跟踪”这个概念,也是从读Deep SORT的代码和论文开始的。这篇我打算把Deep SORT的完整流程从头到尾拆一遍,包括卡尔曼滤波的状态预测、轨迹生命周期管理、级联匹配和匈牙利匹配的细节、ReID特征怎么训练怎么用,以及一堆我在实际项目里踩过的坑和调参经验。不管你是第一次接触多目标跟踪,还是已经在用别的跟踪器但想回头把Deep SORT吃透,这篇应该都能给你一些参考。

1. 从SORT到Deep SORT:为什么跟踪算法需要“深度学习”加持

1.1 SORT的痛点:快是快,但太“健忘”

在Deep SORT出现之前,SORT(Simple Online and Realtime Tracking)已经是一个被广泛使用的基础跟踪器。SORT的思路非常直接:检测器输出目标框,卡尔曼滤波预测下一帧位置,然后用匈牙利算法在预测框和检测框之间做IoU匹配。整条链路里没有任何外观信息,全靠位置和形状的几何关系硬扛。

这个方案的最大优势是快。纯CPU上跑几百路视频都没有压力,实时性在当时是碾压级的。但代价也相当明显:一旦目标被遮挡、密集交错、或者短暂离开画面再回来,SORT基本就断了链子。原因是IoU这个度量太脆弱了,目标稍微被挡一下,检测框和预测框的IoU可能直接掉到匹配阈值以下,跟踪ID马上切换。在拥挤的商场、地铁站这种人头攒动的场景里,ID Switch的次数能高到让人怀疑人生。

我印象很深的一次测试是在某高校的实验室里,用SORT去跟踪一段教学楼门口的监控视频。课间学生涌出教学楼的时候,ID几乎是一帧一变,整个轨迹乱成一团。当时负责这个项目的同学直接说这玩意“记不住人”,只能记住“框”。这个评价其实很准确——SORT没有记忆,它只有几何,没有身份。

1.2 Deep SORT的核心改进:给跟踪器装上“人脸记忆”

Deep SORT的全称是Deep Simple Online and Realtime Tracking,从名字就能看出来,它是在SORT的基础上做深度学习扩展。核心改动只有一个:在数据关联阶段,除了运动信息,还引入了一条外观特征分支。检测框里的目标会被一个预训练好的ReID(行人重识别)网络提取成一个特征向量,这个向量相当于目标的“身份证”;跟踪轨迹也会保存历史特征库。匹配的时候,检测框和轨迹之间的相似度不再是只靠IoU,而是把运动距离和外观距离加权组合起来一起算。

这个改动的意义非常大。即使两个目标靠得极近、运动轨迹高度相似,只要外观特征不同,匹配算法也能把两者区分开。反过来,如果一个目标被遮挡了很久,位置预测已经漂到不知道哪里去了,只要外观特征还在,它重新出现时仍然有机会被认出来,继承原来的ID。

简单类比一下:SORT是只靠“距离感”认人的脸盲症,Deep SORT则是既看位置又看长相的正常人。前者在面对拥挤、遮挡、折返这些真实场景时很容易懵,后者就稳健得多。

1.3 Deep SORT的整体流程图

Deep SORT的主循环可以用下面这段伪代码概括,这是我在理解整个算法时自己梳理过的版本:

输入:视频帧序列 每帧处理: 1. 目标检测:从当前帧中获得检测框集合 D 2. 特征提取:用ReID网络为每个检测框提取外观特征 r 3. 状态预测:用卡尔曼滤波预测每条已存在轨迹在当前帧的位置 4. 数据关联: a. 对确认态轨迹和检测框计算融合代价(运动马氏距离 + 外观余弦距离) b. 按轨迹年龄从小到大分级,用匈牙利算法逐级匹配 5. 状态更新: - 匹配上的轨迹:用检测框更新卡尔曼状态,更新特征库 - 未匹配的检测框:初始化新轨迹 - 未匹配的轨迹:按max_age判断是否删除 6. 输出:当前帧所有确认态轨迹的跟踪框和ID

这个流程看起来简单,但每一个环节都有很多值得深挖的细节,下面逐个拆开来讲。

2. 卡尔曼滤波与轨迹状态管理

2.1 状态空间设计与运动模型

Deep SORT中的卡尔曼滤波跟踪的是bounding box的状态。原论文用的是8维状态向量:

x = [u, v, s, r, u_dot, v_dot, s_dot]

其中u、v是目标框中心点的横纵坐标,s是目标框的面积(宽乘高),r是宽高比,带点号的变量是对应的时间导数,也就是速度项。这里有个细节很多人第一次看会懵:为什么不直接跟踪四个角点或者宽高?原因是,r(宽高比)在很多场景下变化非常缓慢,尤其是行人目标,姿态变化虽然会改变身高和宽度,但宽高比相对稳定。所以把r当作一个几乎恒定的量来处理,能减少状态空间的维度,提升滤波稳定性。

运动模型用的是匀速模型(Constant Velocity),状态转移矩阵就是标准匀速模型的矩阵形式。这个假设在短时间间隔内是比较合理的,毕竟两帧之间的时间间隔通常只有几十毫秒,目标在这段时间内近似匀速运动不会引入太大误差。但如果摄像头本身在运动,比如装在无人机或者巡检机器人上,匀速假设就不成立了。这种情况我后面会专门讲。

2.2 观测模型与协方差更新

检测器给出的观测值是4维向量:

z = [u, v, s, r]

观测矩阵H负责把8维状态投影到4维观测空间。在标准的卡尔曼滤波流程里,每一帧要经历两个阶段:

  • 预测阶段:利用运动方程和上一帧的后验状态,推算出当前帧的先验状态和先验协方差。
  • 更新阶段:利用当前帧的检测框作为观测值,结合先验状态计算卡尔曼增益,得到后验状态和后验协方差。

协方差矩阵在整个过程中代表对状态估计的不确定度。匹配完成后,卡尔曼增益会自动决定“更相信预测”还是“更相信观测”。当检测框质量很高的时候,观测噪声小,后验状态基本跟着检测框走;当检测框抖动剧烈时,观测噪声大,系统更倾向于平滑预测。

我在实际工程里发现一个现象:如果检测器偶尔会输出一些定位不准的框,卡尔曼滤波的状态更新会在短时间内出现明显的“拉扯感”。解决办法倒不是去改卡尔曼参数,而是先在检测端加置信度过滤,把不确定的检测框提前滤掉。

2.3 轨迹生命周期:tentative、confirmed与deleted

Deep SORT把轨迹状态分成三档,这一点对理解匹配逻辑至关重要:

状态进入条件匹配规则离开条件
未确认(Tentative)检测框连续n_init帧未匹配到任何轨迹时新建只与检测框做IoU匹配(不参与级联匹配)超过n_init帧仍未确认,则删除
确认(Confirmed)连续n_init帧成功匹配检测框参与完整级联匹配连续max_age帧未匹配到检测框,则删除
删除(Deleted)达到删除条件不参与匹配从轨迹列表中移除

这里有一个很容易被忽略的关键点:新轨迹并不是马上进入级联匹配池的,而是先经过一个“观察期”。只有连续若干帧都能匹配上检测框,才确认这是一条稳定轨迹,之后才具备参与级联匹配的资格。这个设计是为了防止把单帧的误检当成真实目标来跟踪。

反过来,已确认的轨迹如果连续很多帧没有匹配到任何检测框,会被标记为删除。这个“很多帧”就是max_age参数,代码里常设为30帧。我之前在项目里试过把它调成60帧,结果发现目标长时间消失后重新出现,虽然还能靠外观特征找回ID,但内存里的失效轨迹数量会明显增长,每帧的匹配计算量也随之上升。所以这个参数不是越大越好,要在“找回能力”和“计算开销”之间做平衡。

3. 数据关联:级联匹配与匈牙利算法的配合

3.1 两种距离度量:马氏距离与余弦距离

Deep SORT的运动匹配度量是马氏距离(Mahalanobis Distance),它不是简单的欧式距离,而是考虑了状态估计的不确定性:

d1(i, j) = (d_j - y_i)^T * S_i^(-1) * (d_j - y_i)

这里的y_i是第i条轨迹预测的观测位置,d_j是第j个检测框的位置,S_i是轨迹的协方差矩阵。马氏距离最大的好处是,如果一个目标位置预测得非常确定(协方差小),那么稍微偏离一点就会产生很大的代价;如果预测很不确定(协方差大),则允许更大的偏移。这比直接算像素距离要科学得多,因为很多检测框的定位误差本身就不是均匀分布的。

外观匹配度量是余弦距离。ReID网络把每个检测框压缩成一个128维(或其他维度)的特征向量,轨迹端保存该目标最近若干帧的特征集合,匹配时计算检测特征与集合中每个特征之间的余弦相似度,再取最小距离:

d2(i, j) = min{ 1 - r_j^T * r_k } (k遍历轨迹i的特征库)

3.2 融合策略与门控机制

最终的匹配代价是两者的加权和:

c(i, j) = lambda * d1(i, j) + (1 - lambda) * d2(i, j)

lambda在代码默认实现里通常设为0。很多人会问:lambda设成0岂不是完全不用运动信息了吗?实际上不是。Deep SORT里还有一个“门控”环节,先用马氏距离做一次过滤,只有满足马氏距离阈值的“候选对”才会进入后续计算;真正的排序和选择靠的是外观距离。马氏距离在这里更像一票否决的资格筛选,而不是贡献打分的维度。

门控条件有两个:

  • 马氏距离小于卡方分布的阈值(根据状态维度和置信度查表获得)
  • 余弦距离小于设定的最大距离阈值(ReID特征的质量上限)

两个条件同时满足,才认为检测框和轨迹存在关联候选关系。这个设计避免了外观特征全盘主导导致的目标错配——如果两个目标长得像但位置差很远,即使余弦距离很小,也不会被错误匹配。

3.3 为什么必须是“级联”匹配

这是Deep SORT流程中最精髓的地方,也最容易被初学者忽略。

简单的匈牙利匹配会把所有确认态轨迹和所有检测框一次性丢进代价矩阵里求全局最优解。但Deep SORT不是这么做的,它先把所有确认态轨迹按照“最后一次匹配成功的帧到现在”的帧数(age)从小到大分成多个层级。age=0代表上一帧刚刚匹配过,age=1表示已经断了一帧,以此类推。

然后,按age从小到大的顺序,逐层执行匈牙利匹配,匹配完的检测框要从检测框集合里移除,下一层只能匹配剩下的检测框。

为什么非要这样逐级来?因为匈牙利算法做全局最优时,代价是“统一”的。但一条轨迹如果很长时间没被匹配到,它的位置预测早就不可靠了,马氏距离会变得很大;一条刚刚更新过的轨迹,预测位置可信度高,两者的代价尺度不一样。如果直接把所有轨迹放进同一个代价矩阵,那些长期失配的轨迹会因为“反正已经烂了”而抢到本该属于近期轨迹的匹配机会。这会导致刚被遮挡了几帧的目标立刻失去ID,而原本已经丢失很久的目标反而被优先匹配。

一句话总结:级联匹配的本质是给不同的轨迹赋予不同的匹配优先级,刚更新过的轨迹优先匹配,失配越久的优先级越低。这是对“公平匹配”的反动,但非常有效。实测下来,级联匹配比单轮全局匹配能显著降低ID Switch,尤其是目标频繁进出画面的时候。

这一段的实操心得:如果某个场景里目标很少位移、遮挡也不严重,你可以把max_age调小,级联层数自然减少,速度还能快一点。如果场景拥挤、遮挡频繁,不要动级联逻辑,优先检查ReID特征质量。

4. 外观特征:ReID网络的训练与使用

4.1 特征网络结构与输出维度

ReID网络的任务是给同一个目标在不同帧、不同角度下的图像输出高度相近的特征向量。Deep SORT原论文里对网络结构没有做强制约束,只给出了一个参考实现。主流做法是使用残差网络(比如ResNet的变体)作为骨干网络,把最后的全连接分类层去掉,换成输出128维特征的嵌入层。

特征维度是一个值得斟酌的选择。维度太低,区分度不够,容易把不同的人判成同一个人;维度太高,距离计算的方差会变大,且存储和计算成本上升。我做过的对比实验显示,在中等规模的行人数据集上,128维到256维之间的效果差距不大,真正影响匹配效果的是特征本身的判别力,而不是维度数。

训练的时候,数据组织用的是“同一个ID的多张图”构成正样本对,不同ID的图构成负样本对。常用的损失函数包括三元组损失(Triplet Loss)和分类损失(ID Loss)的组合。三元组损失的核心思想是:把同一ID的特征向量的距离拉近,不同ID的距离推开,并设置一个margin来控制推开的程度。

4.2 特征库的实时更新策略

光有特征网络还不够,Deep SORT在每个确认态轨迹上保存的不是一个特征向量,而是一组最近匹配到的特征向量集合(原实现默认保存最近100帧的特征)。做匹配时,检测特征要和这组特征逐一算余弦距离,然后取最小值。

这个“取最小值”的设计很有意思。我们最初的做法是取平均值,后来发现如果目标在行走过程中发生了明显的视角变化或局部遮挡,平均特征会把“早期正常状态”和“后期遮挡状态”混合成一个四不像的特征,导致谁都匹配不上。取最小值则是只要那个目标和历史上任何一个时刻像,就认为有望匹配,对那些“一半时间正常、一半时间异常”的目标特别友好。

特征库的更新需要注意一个隐藏问题:如果一次错误匹配把别人的检测框并入了当前轨迹,那个错误检测的特征也会被存入特征库。随着错误特征占比变高,轨迹的“身份证”会被逐渐污染,最后甚至会出现轨迹特征越来越像别人、越来越不像自己的情况。很多项目里的ID漂移问题,追溯到最后都发现是特征库污染。解决办法是在更新特征库时设置一个余弦相似度的“准入阈值”,太像或太不像的特征都先不收,或者对特征库做一个滑动窗口的加权,而不是简单全量覆盖。

4.3 训练数据与场景迁移

ReID模型对训练数据的依赖非常大。在很多实际场景里,我们遇到的“目标”不是什么行人,而是鱼、车辆、动物甚至工业零件。直接用别人训练好的行人ReID模型来做鱼群的跟踪,效果可想而知。

我做过一个水下生物的跟踪项目,最初直接用开源的通用ReID模型,结果特征区分度极差,两条不同品种的鱼经常被匹配成同一轨迹。后来我们在自己的标注数据上重新微调了ReID模型,只是把骨干网络冻结、换了最后的分类头,效果就提升了一大截。这也是Deep SORT相对其他跟踪器一个比较大的工程化优势:ReID模块和跟踪主流程是解耦的,你完全可以单独换一个更适合自己场景的特征模型,跟踪器本身不用动。

5. 工程落地:参数配置、性能调优与常见坑

5.1 关键参数与初始化参考

Deep SORT的实现里,有这么几个核心参数,我结合自己的工程经验整理了一份参考表:

参数常见默认值作用调参建议
max_dist0.2余弦距离门控阈值特征质量好时放宽到0.25~0.3,质量差时收紧到0.15
min_confidence0.3检测置信度下限视检测器质量可调到0.5
n_init3新轨迹确认所需的连续匹配帧数高帧率视频可适当调大
max_age30轨迹失配保留的最大帧数场景遮挡严重时调到60~100
lambda0运动与外观代价的加权系数相机静止时保持0,相机运动时考虑调高
feature_budget100每条轨迹保存的最大特征数拥挤场景建议加大到150~200

这些参数之间是有联动关系的。比如你把max_age调大了,却没有同时把feature_budget调大,目标重新出现后的外观匹配可能会因为历史特征被提前挤出特征库而失败。再比如你把min_confidence调高了,检测框数量变少,n_init如果还保持3,新轨迹就很容易因为缺帧而迟迟无法确认。

5.2 检测器与跟踪器的配合

Deep SORT对输入的检测框质量极其敏感。检测器的漏检会导致轨迹失配并进入老化;检测器的误检会导致伪轨迹产生,消耗大量匹配资源。两个方向都会显著拉低跟踪精度。

在我做过的部署项目里,有一个比较通用的做法:把检测器的输出先过一遍NMS和后处理逻辑,把低于阈值的框剔除,再把剩下的框交给Deep SORT。这听起来像废话,但很多人会在pipeline中间省略这一步。另外,如果使用的是视频流,建议对检测结果做一下时间域的平滑,比如相邻帧的检测框中心点位移如果突变超过固定阈值,就认为可能是误检。

一个容易被忽略的协作点:检测器的类别信息。Deep SORT本身不关心检测目标的类别,但实际场景中往往有多类目标同时出现。如果直接把行人检测框和车辆检测框混在一起送进跟踪器,外观特征之间的区分度会变得很混乱。我建议按类别分流,每一类单独维护一套跟踪器,而不是一个Deep SORT实例跟踪所有类别。

5.3 性能优化:从CPU到边缘设备

Deep SORT的性能瓶颈通常在两个地方:ReID特征提取和匈牙利匹配。匹配部分在目标数量小于几百个时,耗时基本可以忽略;真正吃算力的是每帧把所有检测框送入ReID网络。

边缘设备上我常用的优化手段有两个。第一,降低特征提取频率,不需要每一帧都对检测框提取特征,比如每两帧提取一次,中间帧只靠卡尔曼预测和IoU匹配过渡,代价是ID稳定性略微下降;第二,把ReID网络量化到INT8,对于128维特征这种下游任务,量化造成的精度损失通常可以接受。

我实测过一个项目:同样的Deep SORT流程,在Jetson级别的边缘设备上,FP32的ReID网络每帧处理50个检测框需要约25毫秒,INT8量化后降到8毫秒,而跟踪精度只下降了约1个百分点。这在很多实时场景里是值得的。

5.4 常见问题排查速查表

现象可能原因排查方向
ID频繁切换ReID特征区分度差检查特征提取模型是否与场景匹配,尝试更换或微调ReID模型
目标遮挡后丢IDmax_age过小调大max_age和feature_budget
跟踪框抖动剧烈检测框本身抖动调高min_confidence,或在检测端做平滑
新轨迹迟迟不确认n_init过大调小n_init,或检查检测器漏检率
两个相邻目标互相串扰匹配代价值区分度不足尝试调高lambda,增加运动信息的权重
运行速度慢ReID模型太重降低特征提取频率、量化模型、用轻量级骨干网络

6. 边界与改造方向:Deep SORT不是万能的

6.1 场景失效与假设缺陷

Deep SORT的很多假设在现实场景里会失效。最典型的是相机运动。卡尔曼滤波假设目标在帧间是匀速运动,但在相机运动的情况下,画面中目标的像素位移并不是目标自身的运动,而是由相机运动主导的。此时基于匀速模型的预测会大幅度漂移,马氏距离门控会把大量正确匹配过滤掉。

我做过一个无人机俯视视角的目标跟踪项目,相机在飞行过程中姿态一直在变,Deep SORT默认配置的表现非常差,ID Switch率高得离谱。后来给检测框的位置加了一个基于云台姿态的全局偏移补偿,每帧先把检测框从图像坐标变换到相对地面的局部坐标,再做跟踪,效果才恢复了正常。

另一个薄弱点是拥挤场景。目标之间大量重叠时,ReID特征提取的是遮挡部分的图像内容,特征本身就不可靠;加上检测框之间的IoU很高,运动信息几乎无法区分。这种场景下,Deep SORT的表现会明显下降。业界常用ByteTrack这类基于流量分配的算法来做高密度场景,效果更优,但Deep SORT在中等密度、目标清晰度尚可的场景里仍然足够用。

6.2 工程改造方向

如果你的项目确定要长期使用Deep SORT,有几个改造方向是可以考虑的:

  • 把ReID网络从行人专用模型换成场景自训练的通用特征模型。
  • 在特征库更新上做门控,防止错误匹配污染轨迹特征。
  • 在级联匹配前加一个类别一致性过滤,避免不同类目标被关联在一起。
  • 对卡尔曼滤波的观测噪声参数做自适应,检测置信度低时自动增大观测噪声权重。
  • 如果相机运动,把全局运动补偿(如光流或特征点匹配估算的仿射变换)前置到检测框坐标上。

这些改造本身都不复杂,但每一条都能让Deep SORT在实际工程里稳定不少。

我这里花了比较大的篇幅把Deep SORT的流程从原理到工程一层层剥开,其实最终想强调的是:跟踪算法的核心不在某个单独的模块多厉害,而在于各个模块之间的配合方式。级联匹配、门控机制、轨迹生命周期管理,这些设计单独看都很朴素,组合在一起才构成了一个在真实复杂场景中能稳定跑的框架。我自己在实际项目里感受最深的一点是,不要一上来就想着魔改算法,先把默认流程吃透,再根据自己场景的特点在参数和模块边界上做小步调优,往往能收获比大改算法结构更明显的效果提升。

最后分享一个小技巧:无论你用的是哪个开源实现,拿到代码后请先给每个模块写一段日志,把每一帧的匹配数量、未匹配检测框数量、轨迹状态变化都打出来。调试跟踪算法时,日志比可视化界面更能帮你定位问题。经过了几个项目的折腾之后,你会发现大多数跟踪问题的根源都不在算法本身,而在你输入给它的数据质量。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 1:03:52

尖叫声音样本数据集与声音事件检测:监控报警的工程实践指南

简介:EVA尖叫人声样本数据集来自NTHU音乐信息检索课程项目,面向音频分类研究者和机器学习初学者,可用于填补尖叫声音样本稀缺的研究空白。数据集从16首歌曲的人声轨道中切分出565个尖叫片段,并附有完整的时间标记、annotations.cs…

作者头像 李华
网站建设 2026/10/14 1:03:47

AES-128 CMAC完整实现:从原理到C#代码实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:03:07

Modbus TCP工业通讯程序实战:从连通到产线级稳定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:02:51

Java数组内存机制全解析:从对象头到性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:02:50

Matlab与V-REP激光雷达2D动态建图仿真链路搭建指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华