TBD系列第三篇来了。前面两篇我们把MOT(多目标跟踪)的基本概念、评测指标和检测器选择都过了一遍,也把数据关联里最基础的匈牙利匹配讲清楚了。这篇文章我们进入真正决定跟踪效果的核心部分:运动预测和匹配策略。换句话说,检测器只负责告诉你在哪,但目标下一帧可能去哪、轨迹之间怎么连,全看这一层的设计。如果你已经能跑通一个Track-by-Detection流程,却发现ID Switch频繁、轨迹断裂、边缘场景跟着跟着就丢了,那这篇大概率能帮你找到症结所在。
这一篇的重点不是罗列算法公式,而是讲清楚卡尔曼滤波在MOT里到底怎么落地,级联匹配为什么比单纯的全局匹配更抗遮挡,以及那些常规文档里不会写的参数调优经验。我尽量按自己实际调试的顺序来讲,先聊设计取舍,再给可复现的配置,最后附上排错经验,适合看完前两篇、准备自己动手调一套TBD系统的开发者参考。
1. 为什么第三篇才碰运动预测——TBD链路的关键次序
1.1 TBD流程中的三个关键环节
Track-by-Detection从流程上看就三个环节:检测、运动预测、数据关联。检测有多重要我们第一篇就聊过,检测器直接决定召回率和误检率,是整个系统的上限;数据关联决定怎么把检测框拼成轨迹,是系统能否稳定的骨架。很多入门教程把运动预测当成一个并不起眼的辅助模块,觉得无非是拿卡尔曼滤波做几步外推。但实际调过之后你会发现,运动预测才是连接检测质量和关联质量的桥梁。
检测器输出的是每一帧的检测框,如果直接把检测框按IoU去匹配,帧率稍微不稳定就会出问题:目标被遮挡几帧后位置可能发生不小偏移,相邻帧检测结果对应的置信度也起伏不定。这时候如果没有运动预测做“合理的预期”,数据关联就只能靠检测框当前帧的几何相似度去猜,但现实场景里目标在遮挡期间的位移、目标自身形变都会让几何相似度迅速失效。所以运动预测解决的核心问题是提供“目标的假设位置”,让关联算法在匹配时有一个可靠的先验。
我在实际调试中发现,很多新手一上来就调关联阈值,改了又改效果都不明显,最后回头一查发现卡尔曼滤波的参数完全是默认值,预测框要么跟着检测框剧烈抖动,要么在目标短暂丢失后直接“飞”到错误位置。先搞懂运动预测的工作方式,再回去调匹配,逻辑就顺很多,这也是我把这篇放在第三篇的原因。
1.2 检测质量对跟踪效果的“放大器效应”
关于TBD,我们还可以从错误传递的角度看:检测器输出的漏检和误检本身不可完全避免,但跟踪系统对检测质量的依赖方式,并不是线性的。同一个检测器在不同参数配置下,ID Switch数量可能差出好几倍,这中间的差距往往就是运动预测和关联策略是否合理导致的。
举个例子,某段监控视频里,目标短暂走到柱子后面,检测器连续几帧都没有输出。如果卡尔曼滤波的速度外推太激进,预测框就会顺着之前的运动方向越走越远,等目标从柱子后面出来时,预测框已经在几个像素之外了,于是数据关联把同一个目标错误地匹配给了别的轨迹,或者干脆开辟了一条新轨迹。如果外推太保守,预测框基本停在原地,目标出遮挡时又需要好几帧来“重新捕获”,轨迹同样容易断。检测质量的“锅”,最后会放大成跟踪质量的“锅”。
所以在TBD里,检测、预测、关联这三者的调试顺序特别重要:先确定检测器在目标场景下的漏检率和误检率,再去匹配策略里选参数。检测器都明显丢目标的情况下,运动预测再怎么调也救不回ID Switch的问题。反过来,检测器足够好了,运动预测和关联策略就是决定系统上限的关键。
2. 卡尔曼滤波调参:从状态向量到协方差矩阵
2.1 状态向量的定义其实是个取舍问题
多目标跟踪里用的卡尔曼滤波,大多数实现都采用“匀速模型”,状态向量一般设计成8维或4维。8维版本是:中心点坐标x、y,宽高比ratio,高度h,以及对应的速度vx、vy、vratio、vh。观测值则取当前检测框的x、y、ratio、h。这样的好处是宽高比和高度分开建模,能应对目标尺寸变化,又不会让模型过于复杂。
但用8维还是4维,是个取舍问题。8维模型在目标运动比较平稳时预测更准,跟踪质量更高;但它的参数更多,调起来更麻烦,对初始协方差和过程噪声也更敏感。4维模型只保留x、y、w、h四个量,不显式建模速度,等效于假设每帧之间的速度变化很小,实现简单、鲁棒性也不错。很多快速原型和资源受限的边缘设备上都倾向4维版本。
我的习惯是:如果目标运动比较平滑,比如车辆、行人直线或缓弯,用8维,预测准确度带来的收益很明显。如果场景里目标运动轨迹杂乱、频繁加减速,比如球类运动、人群聚集,8维模型反而容易因为速度估计不准而放大误差,这时候4维加一个较小的过程噪声可能更稳。实践中可以两条路都跑一下小规模实验,用IDF1和MOTA对比做决定,别凭感觉定。
2.2 协方差矩阵初始化:滤波器对检测的“信任程度”
协方差矩阵P是滤波器对当前状态估计不确定性的度量。初始化的时候P不能太小,如果太小,相当于告诉滤波器“我对初始位置特别信任”,结果前几帧里滤波器更相信自己的预测而不是检测结果,跟踪容易滞后。P初始值一般取单位矩阵乘一个系数,比如P = 10 * I,让滤波器在刚开始阶段快速接纳检测框的新信息。
过程噪声矩阵Q对应运动模型本身的不确定性。Q设得太大,相当于对匀速模型非常不信任,滤波器几乎只用检测结果,预测框会跟着检测框的抖动而抖动;Q设得太小,滤波器就非常相信“目标继续保持上一帧的运动状态”,遇到短暂遮挡或检测偏移时预测框会沿着旧方向跑偏。Q通常可以设为一个对角线小型矩阵,比如0.01到0.1之间的数值乘单位阵。
我在调试时有个容易忽略的细节:测量噪声R也要同时考虑。很多教程默认把R设成1,但检测框位置本身的噪声和图像分辨率、检测器性能都有关系。高分辨率监控画面里,几像素的检测框偏移可能不算什么;同样几像素的偏移,放在低分辨率小目标上就是很大的相对误差。如果目标尺寸普遍偏小,R可以调低一点,让滤波器更愿意相信检测,否则预测框会“跟不上”目标。这个“信任程度”的取舍,翻译成生活经验就是:一个可靠的朋友给的建议你多听,一个三心二意的人给的建议你就要多打折扣,卡尔曼滤波做的事情本质上也是这个。
2.3 坐标体系转换:为什么先归一化再做滤波
卡尔曼滤波在MOT里最容易踩的隐形坑是坐标体系。有的实现直接在原始像素坐标上做滤波,但同一段视频里,目标尺寸可能随时变化,离镜头近的框大、速度看上去快,离镜头远的框小、速度看上去慢。传统监控场景下,同样一个行人以相同地面速度走路,在画面近端和远端对应的像素位移完全不同。如果不做归一化,状态向量里的速度分量会随着目标尺寸和距离变化而不稳定,卡尔曼滤波的速度估计也就没有意义。
比较好的做法是把检测框坐标归一化到图像尺寸,或者至少统一到相对框尺寸的空间里。归一化之后,目标速度的含义变成“每帧移动了自身高度的多少比例”,更加接近真实三维空间中的运动,这样卡尔曼滤波的输出会更平滑,数据关联阶段计算马氏距离也更稳定。
有人会问,直接用像素坐标也能跑啊,为什么非要归一化?在单一固定场景、目标尺度变化不大的情况下,直接用像素坐标确实能跑,而且实现更简单。但一旦要换场景、换相机视角或者做多路视频处理,就会明显感觉到预测框的抖动和漂移。我建议从一开始就把归一化做进去,省得后期排查问题时到处找“为什么目标一变大轨迹就飘”。
3. 匹配策略选择:级联、贪心与两阶段匹配
3.1 运动匹配与外观匹配的互补关系
数据关联阶段的匹配距离一般由两部分组成:运动距离和外观距离。运动距离常用马氏距离,它把卡尔曼滤波预测的均值、协方差和当前检测框的位置结合起来,不是单纯算欧氏距离,而是会考虑预测的不确定性。外观距离则利用检测框区域的视觉特征,比如ReID特征,计算余弦相似度或距离。
为什么要同时用两种距离?因为它们各有所长。运动距离对短时预测很有效,帧间隔小、目标移动少时稳定可靠;但目标一旦被遮挡几帧,预测误差会加大,这时马氏距离可能就不够准确了。外观距离则对屏幕内的形变和短时遮挡更鲁棒,目标身体角度变一点、走路姿势变一点,特征依然有很大概率接近;但目标颜色、装束接近时,外观特征区分度不足,容易让不同目标互相串扰。
两个距离融合时通常做加权相加,权重怎么设没有万能答案,要看你场景中目标的区分特征够不够强。曾经我调一个人群密集场景,目标外观特征区分度很差,强行提高外观权重反而导致轨迹频繁在相邻目标间横跳;后来把运动距离权重调高,把外观只作为低置信度检测时的辅助条件,ID Switch立刻降了下来。所以调试建议是:先用运动距离为主跑通流程,确认瓶颈确实是外观区分度不足导致串扰后,再逐步增加外观比重。
3.2 级联匹配与贪心匹配的实际使用场景
级联匹配是当前主流TBD方案里的标配做法。它的基本逻辑是按轨迹的“年龄”分组:那些连续多帧都被稳定匹配的老轨迹优先匹配,最近才中断过的轨迹次之,刚出现的新轨迹最后匹配。这么做是因为老轨迹经过多帧确认,置信度更高,值得优先保护;而新轨迹帧数太少,可靠性低,优先级自然要靠后。
第一次实践级联匹配时,你会直观感受到它对目标被短暂遮挡时的友好程度:某一个被跟踪了20帧的行人突然被遮挡一瞬,级联匹配会努力把他和当前检测框先接上,不被后出现的新检测框“抢走轨迹”。这种设计逻辑也好理解:已经陪着系统跑了一路的老伙计,比刚冒头的新面孔更值得信任。
但级联匹配也解决不了所有问题。如果场景中要实时处理的目标数量特别多,全局的匈牙利匹配计算量比较大,有些工程直接退化成贪心匹配——每步只让优先级的轨迹先去抢最近的检测框,抢完就走。贪心匹配速度极快,却存在一个缺陷:它只保证局部最优,密集场景里容易把检测框分配给并不够匹配的轨迹,导致后续轨迹交叉错乱。我的经验是:目标密度低、对帧率要求高时用贪心匹配;目标密度中等以上或场景关系复杂时,老老实实做匈牙利匹配,通常也就几毫秒到几十毫秒的开销,值得。
3.3 置信度阈值与动态调整
匹配阶段的阈值设置直接影响轨迹的启停行为。如果阈值特别严格,检测框必须跟预测框很接近才算是匹配,短管漏检和轻微偏移会直接切断轨迹;如果阈值特别宽松,误检或相近位置的假检测框会被纳入轨迹,污染轨迹特征,后续更容易发生ID Switch。
比较流行的做法是分阶段处理检测置信度。检测器会输出置信度分数,高置信度的检测框直接进入匹配流程,保证可靠目标优先被关联;低置信度的检测框通常代表可能有重影、遮挡或者误检,这些可以作为“候选”,先做匹配补充或多帧确认后再决定要不要创建新轨迹。我在实际工程里经常用双阶段设计:高置信度的检测框走级联匈牙利匹配,低置信度的检测框在剩余未匹配集合里再做一轮基于IoU的贪心补充匹配。这样既保证了可靠性,又不会错过遮挡边缘的目标。
这个双阈值思路有个额外的好处:高低置信区间的划分可以动态调整。比如这段视频目标很多,检测器平均置信度偏低,把高置信阈值设得太高会导致很多真实目标进不了匹配,适当下调能提高召回;目标稀疏场景里,把阈值设高一点可以减少误检轨迹的产生。动态调整依赖你对检测器的了解,需要在实际场景里采样分析,不能一直沿用默认数字。
4. 一个完整调参案例:从轨迹频繁切断到平稳跟踪
4.1 可复现的最小实验配置
为了让你对前面讲的内容有更直观的概念,我搭一个小实验来说明调参过程。假设场景是某园区摄像头拍的行人行走视频,目标画面跨度中等,行人有往同一方向走的,也有偶尔停留交谈的。检测器选一个开源的通用目标检测模型,不做场景微调,直接使用预训练权重。跟踪框架采用最常见的TBD结构:卡尔曼滤波预测 + 级联匹配 + ReID外观特征。
实验配置大概这样:
- 状态向量:8维(x, y, ratio, h, vx, vy, vratio, vh)
- 过程噪声Q:初始取对角线元素0.05
- 测量噪声R:初始取单位阵1.0
- 级联匹配过程分为三级:刚丢失0帧、丢失1到3帧、丢失4到10帧
- 运动门控阈值:马氏距离的卡方分位数取13.28,对应自由度4约99%置信区间
- 外观融合:余弦距离阈值0.4,权重0.3
- 轨迹删除条件:连续30帧未匹配上任何检测框则删除轨迹
这套配置不是最优,只是提供基准线。接下来所有改动都基于这个基线,方便对照效果。
4.2 实测中最容易翻车的三个点
第一次跑这套配置,我遇到的情况非常典型:行人正常行走时轨迹没问题,但两个人擦肩而过时,ID Switch概率骤然上升;行人A从摄像头画面中消失,过了一段时间重新出现,系统并不认为他是之前那一个人,而是又创建了一条新轨迹。我做了三个关键调整,花了大概两三个小时,最终把ID Switch数量降了一半以上。
第一个问题是马氏距离门控太严。两个人的预测框和检测框本来就挨得近,加上行人运动有自然摆动的随机性,马氏距离经常超过13.28的阈值,导致匹配被拒绝。我把门控阈值放宽到17,相当于给了目标稍微大一点的匹配容忍空间,两次擦肩场景的ID Switch立刻变少。但阈值也不能再继续放宽,太宽松会把不同目标也匹配到同一条轨迹上,这个度靠可视化逐帧检查来确定。
第二个问题是过程噪声Q值过低。行人短时停在原地聊天时,卡尔曼滤波的速度状态还保持之前的外推方向,预测框不断漂移,导致轨迹越来越偏离检测框,最终被判定丢失。我把Q从0.05调到0.1,让运动模型不那么自信,滤波器能更快接受最新检测位置信息,停车、折返、起步这些动态场景明显好转。代价是正常行走时预测框抖动变大了一点,但总体上优点大于缺点。
第三个问题集中在外观特征匹配权重。行人目标的视线方向、服装姿势变化很快,ReID特征在不同帧之间的余弦距离本身就波动。权重大了会让两个外观相似的目标抢来抢去,权重小了又起不到抗遮挡作用。后来我采取了一个折中方式:外观距离只作为“候选近邻”过滤条件,不作为硬性否决条件;只有当前IoU匹配明显不一致时才使用外观距离决定。这一改,系统的整体鲁棒性提升了不少。
4.3 小数据调参与迭代节奏
有经验的人会在调参时严格控制变量,每次只改一个参数,并且在固定的短视频片段上做前后对比。我自己习惯的做法是:准备一段包含遮挡、交叉、离开视野又返回、短暂停滞等典型情况的2分钟切片,先把这段视频的检测结果缓存下来,后面调参过程中反复回放同一段,比较轨迹质量和ID Switch数量。
第一次调参时容易陷入的误区是“全参数一起调”。这样即使效果变好了,你也不知道是哪一步产生的作用;效果变坏了,更不知道如何回退。一次只改一个,每次只跑同一个测试切片,把结果记录下来,这看起来慢,实际上是最快的方法。
另一方面,参数之间不是完全独立的。比如调大Q让目标重新定位更快,却可能让预测框输出不稳定,从而让IoU匹配变差。所以你在调第二个参数前,要先把上一个参数的效果“消化”完,确认你是在新的最佳基线上继续改动,而不是在原来的基线上做加法。
5. 常见问题速查:轨迹断裂、ID Switch与僵尸轨迹
5.1 轨迹频繁断裂的排查入手点
如果你遇到“目标明明还在画面里,但轨迹ID却新开了一条”的情况,优先检查三处。第一:卡尔曼滤波的过程噪声Q是不是设置得太小,导致预测跟不上检测的新位置;第二:马氏距离门控是不是太严,尤其是目标贴近其他物体或角度突然变化时;第三:检测器本身在目标快速移动或转身时容易漏检,这决定了你漏接的帧数到底有多长。
轨迹断裂并不一定完全是跟踪模块的问题。检测框位置抖动性较大时,卡尔曼滤波输出也不稳。碰到这种情况,我会先缓存检测框序列,统计一下目标每帧检测框中心点到真实中心的偏差,由此判断到底是检测不稳定还是跟踪不稳定,避免在错误的方向上反复折腾。
5.2 ID Switch高发场景的处理经验
ID Switch最常出现在两个时刻:两个目标相遇又分离、目标被完全遮挡后重新出现。这两个场景本质上有区别:相遇分离时,外观特征在接近瞬间容易被另一目标的特征短暂抢占,导致轨迹归属发生变化;被遮挡后重新出现时,则是因为轨迹预测位置已偏离实际位置,匹配算法没把这份检测框和责任轨迹接上。
处理相遇分离,可以加入外观特征更新机制,每成功匹配一次,就按一定比例更新该轨迹的特征参考向量。这样做的好处是目标特征能适应当前帧的状态变化,不会一直停留在“旧”照片状态。处理遮挡重出现,则要合理设置轨迹存活时间。存活时间太短,目标出遮挡后连不上旧轨迹;存活时间太长,场景里会积累大量僵尸轨迹,为后续匹配制造噪声。
5.3 僵尸轨迹与虚假轨迹的抑制
所谓僵尸轨迹,就是目标已经离开视野,但系统里还保留着它的“空壳”轨迹。如果这个空壳轨迹在后续帧里恰好预测位置碰上了另一个目标的检测框,就可能发生灾难性的错误匹配,把新人接在旧身份上。抑制僵尸轨迹的方法是按帧数上限强制删除未被匹配的轨迹,同时要注意区分“短时遮挡”和“真正离开”的时间尺度。
另外还有一类虚假轨迹,通常由低置信度的误检框连续拼接而成。抑制它们需要在创建新轨迹前设置“确认期”:候选轨迹只有连续若干帧都匹配到检测框才转为正式轨迹。这个确认期太短会放过误检,太长则会让真实目标的新轨迹迟迟建立不起来。具体帧数取决于你场景里检测器的误检率,一般取3到6帧比较合理。
5.4 快速问题定位参考表
| 现象 | 可能原因 | 优先排查项 | 常见修正方向 |
|---|---|---|---|
| 轨迹频繁断裂 | Q过小或门控过严 | 目标移动速度、马氏距离分布 | 调大Q,放宽马氏距离阈值 |
| ID Switch严重 | 外观权重失衡或遮挡恢复失败 | 相遇分离段、遮挡段视频 | 调整特征权重,设置轨迹存活期 |
| 轨迹抖动剧烈 | Q偏大或R偏大 | 目标匀速段预测框稳定性 | 适当降低Q,检查R的标定 |
| 僵尸轨迹过多 | 轨迹存活时间过长 | 离开视野目标数量 | 缩短未匹配删除帧数 |
| 新目标迟迟不入轨 | 确认期设置过长或置信度阈过高 | 新目标首次出现帧 | 缩短确认期,调节置信度阈值 |
| 小目标跟丢 | 坐标尺度未归一化 | 低分辨率目标偏移量 | 归一化坐标,针对小目标调R |
最后分享一个我自己的调试习惯:调参前先把检测结果缓存下来,这样每次调跟踪参数只需要重放缓存检测帧,不用反复跑检测器,一天能节省大量时间;而且缓存之后能非常直观地对照检测框变化,判断哪些问题其实出在检测阶段。另外,任何参数改动都要在固定测试片段上验证,完成后顺手记下改动前后对比,哪怕只是记录几个数字,时间久了作用都很大。这套方法不一定适合每一个场景,但思路是通用的,希望对正在搞TBD系统的你有参考价值。