卡尔曼滤波+匈牙利算法:多目标跟踪工程实践全解析
2026/9/15 12:02:58 网站建设 项目流程

多目标跟踪里,卡尔曼滤波加匈牙利算法这套组合,几乎就是 MOT 领域的“入门必修课”。我做跟踪也踩了不少坑,回头看这两个算法被很多人讲得玄乎,其实拆开看就是一套“先猜后配”的思路:卡尔曼负责猜目标下一帧在哪,匈牙利负责把猜测和检测对上号。这篇文章我就用工程落地的方式,把这两块掰开揉碎讲清楚,顺带把 SORT 那套流程完整过一遍,保证你看完能自己写个能跑的 MOT demo。

这篇内容适合这几类人看:刚接触多目标跟踪、打算复现 SORT/DeepSORT 的算法工程师;用 YOLO 等检测器做项目但检测框老是抖、ID 老切换的开发者;还有准备面试背八股但想真搞懂底层原理的学生。我会重点讲状态向量怎么设计、噪声协方差怎么调、代价矩阵怎么构建、阈值怎么设置,以及为什么你调出来的跟踪器 ID Switch 特别多。

1. 多目标跟踪的两个核心问题:预测与匹配

1.1 为什么检测器跑得好好的,还要做跟踪

很多人一开始不理解,YOLO 不是已经把目标框出来了吗,为什么还要多此一举做跟踪?这里有个本质区别:检测是“单帧独立”的,每张图从头算一遍;但跟踪要求的是“跨帧关联”。你得知道同一个行人,在第 1 帧是框 A,第 2 帧是框 C,中间隔着框 B(另一辆车),你不能把 ID 搞混。

做检测的都知道,再好的检测器也会有漏检、误检和抖动。漏检那一帧,没有跟踪器的话目标就断了;检测框抖动的话,你算出来的速度、轨迹都是毛刺。跟踪器干的事,就是用一个运动模型把目标的状态“顺”下去,再用一个匹配策略把新检测框“挂”到已有轨迹上。这里面最经典、最省算力的实现,就是 SORT 用的那套:卡尔曼滤波预测位置,匈牙利算法做数据关联。

1.2 数据关联问题:跟踪的核心本质

所谓数据关联,说白了就是给每个检测框分配一个轨迹 ID。假设当前有 M 条轨迹、N 个检测框,你就得算一个 M×N 的代价矩阵,然后找出一个“总代价最小”的方案。这问题在数学上叫线性指派问题,匈牙利算法是解决它的经典方法。

但有个前提你必须明白:匈牙利算的是“全局最优”的匹配,不是每个目标单独找最近的检测框。单独找最近的这种贪心策略,很容易出现两个轨迹抢同一个检测框的情况,或者一个轨迹被硬生生拽到另一个目标身上的情况。全局匹配避免了这个矛盾,但代价是需要一个可靠的代价矩阵。MOT 里最常用的代价是 IoU(交并比),因为它不关心框的绝对大小,只关心重叠程度,天然适合做同一目标的相似度度量。

2. 卡尔曼滤波的工程视角:状态设计才是重中之重

2.1 MOT 里的状态向量怎么设计

卡尔曼滤波的形式大家都熟:预测、更新两个公式来回迭代。但落到 MOT 里,第一个实际问题就是状态向量选什么。SORT 和 DeepSORT 的做法是直接用检测框的参数建状态,不搞图像坐标系的复杂建模。

我自己最喜欢用的状态是 7 维或 8 维:如果是 2D 检测框,用中心坐标 (u, v)、宽高比 γ、高度 h,再加上各自的速度,构成一个 8 维向量。宽高比通常认为不变,所以速度设成 0,高度速度单独算。这样设计的好处是,你不需要手动区分目标的运动类型是行人还是车辆,统统建模成“匀速运动 + 轻微噪声”,模型足够简单,算得快,而且在短时间间隔内效果不差。

有人会问,为什么不直接建模 x, y, w, h 四个参数的导数?也可以,但宽和高变化在像素域不是线性的,特别是目标转向或相机抖动时,直接对 w、h 做匀速假设容易发散。用“中心坐标 + 宽高比 + 高度”这套组合,是实作里更稳的选择。

2.2 预测与更新方程落地:矩阵怎么设

卡尔曼滤波最劝退新人的就是那一堆矩阵。我们不用背全部推导,但 F、H、Q、R 这四个矩阵的含义必须吃透。

F 是状态转移矩阵,体现“匀速运动”这个假设。Δt 在逐帧处理中可以视为 1,所以 F 就是一个分块矩阵:左上角单位阵,右上角单位阵乘 Δt,左下角全 0,右下角单位阵。

H 是观测矩阵,因为我们的观测值就是检测框 bbox,而状态向量里也有 bbox 参数,所以 H 就是一个选择矩阵,把状态向量中的位置部分挑出来。

Q 是过程噪声协方差,表示你有多相信“匀速运动”这个模型。设得太小,模型太死板,目标一加速就跟丢;设得太大,滤波结果就跟检测框一样抖,丧失平滑意义。

R 是观测噪声协方差,表示你对检测框本身的信任程度。检测器越稳,R 可以设得越小。

实操中,Q 和 R 的比例关系,比它们的绝对值重要得多。我一般先把检测框自身的偏差按像素估个大概(比如中心点 5 像素、宽高 10 像素),作为 R 的初值;Q 再按 R 的几十倍去调,先让跟踪不丢,再慢慢收紧。这个调参过程没有标准答案,只能靠 MOTA 和 ID Switch 两个指标反复评估。

2.3 卡尔曼在 MOT 里的几个典型误用

第一个误用是把它当成单纯平滑器。有些朋友把卡尔曼滤波的输出直接当成最终检测、拿去画框,这是浪费了它的预测能力。MOT 里卡尔曼更大的价值是输出“预测框”,给匈牙利算法做匹配用,同时补上漏检帧的位置。

第二个误用是没考虑目标机动性。卡尔曼的“匀速”假设在自行车转弯、车辆刹车时很容易失效。解决办法不是什么花哨的模型,而是把 Q 调大一点,让滤波器更“信检测”,再配合门控阈值控制匹配范围。

第三个误用是不知道什么时候重置状态。一旦目标发生了遮挡后重新出现,或者 ID 已经切换了,旧的状态应该果断清掉,不要硬保留。我会在后面的实操部分讲 max_age 的设置,很多 ID 切换问题都是因为保留了过多的“僵尸轨迹”。

3. 匈牙利算法的工程实现:不只是调库

3.1 从代价矩阵到指派问题

匈牙利算法的输入是一个代价矩阵,输出是让总代价最小的行到列的分配。MOT 里这个代价矩阵的构建方式,直接决定了跟踪效果,比算法本身的优化还重要。

最常见的做法是用 IoU 距离。设轨迹的预测框为 A,检测框为 B,IoU = (A∩B) / (A∪B)。IoU 越大代表越可能是同一个目标,所以代价可以简单定义为1 - IoU。当两个框完全重叠时代价为 0,完全不重叠时代价为 1。也可以用-IoU作为代价,但用 1 减更容易理解,也方便与其它代价(比如外观距离)加权融合。

需要提醒的是,如果在多类别场景下做目标跟踪,比如同时跟踪行人和车辆,通常会在匹配前先按类别分组,只在同类别的轨迹和检测之间算代价矩阵。否则一个行人轨迹匹配到一辆车的检测框,代价再小也是错的。

3.2 一个最小实现:看懂匈牙利算法内部在干什么

很多工程同学直接用 scipy.optimize.linear_sum_assignment,几行代码就把匈牙利解决了,这是好事,但至少要知道它内部做的是行归约、列归约、找零元素覆盖的最小直线数这三板斧。

我建议自己动手写一个最小实现,不需要优化到工程水准,但能帮你彻底明白“为什么这个算法能取到全局最优,而不是局部最优”。核心思想就是:代价矩阵每行减去行最小值、每列减去列最小值,不改变最优匹配的位置;然后通过找增广路的方式,不断调整匹配,直到所有行都被分配。

伪代码逻辑不复杂:先对每一行找最小代价,行内做减法;再对每一列找最小代价,列内做减法;然后逐行寻找可行的零元素并标记匹配;如果某行没匹配上,就通过交替路径扩增匹配数,相当于重新洗牌之前的匹配,腾出位置。这个“洗牌”过程就是匈牙利算法的精髓,也是贪心匹配永远做不到的。

3.3 为什么用 IoU 而不是欧氏距离

纯坐标上的欧氏距离有个问题:大框和小框的像素尺度差异太大了。一个 200×300 的行人框和一个 20×30 的远处行人框,中心偏移 10 个像素,对前者来说只是轻微抖动,对后者来说可能已经跑出框了。IoU 是归一化的,天然消除了这个尺度差异。

代价算法本身也有几个实际执行细节值得注意。第一,门控阈值要先过滤掉不可能匹配的对,比如1 - IoU > 0.6的直接不要,缩小矩阵规模。第二,不要拿完整的 M×N 矩阵去算,M 和 N 一旦到几百,匈牙利算法复杂度 O(n³) 就上来了,先把明显离谱的候选对裁掉,速度能快好几倍。第三,匈牙利算法返回的是一组配对列表,不是每个检测框对应的 ID 索引,取索引的时候要细心,我见过不少人在这里把行列搞反了。

4. 完整实操过程:跑通一个 SORT 风格的 MOT

4.1 跟踪流程的主循环

抛开花里胡哨的进阶版,一个最小可用的 MOT 系统只需要五步:检测、预测、匹配、更新、轨迹管理。我按这个顺序给你捋一遍。

第一步,对当前帧跑检测器,拿到 N 个检测框。第二步,对每个已有的轨迹,用卡尔曼滤波的预测步骤,推算出它在当前帧的预测框。第三步,计算 IoU 代价矩阵,结合门控阈值,用匈牙利算法做匹配。第四步,匹配上的轨迹用检测框做卡尔曼更新,修正状态。第五步,没有匹配上的检测框初始化新轨迹,持续多帧都没有匹配的轨迹则删除。

这里有一个很多人忽略的细节:匹配顺序。不是所有轨迹都一起匹配。SORT 里一般先做一次全部匹配,而 DeepSORT 引入了“级联匹配”,优先匹配最近更新过的轨迹,避免较老的轨迹抢占新目标的机会。如果你发现 ID 切换频繁,可以优先试这个改动,而不用立刻上外观特征。

4.2 卡尔曼预测和更新的代码骨架

用 filterpy 库写卡尔曼滤波非常省事,但我不建议完全黑盒调库,至少要能说出每个参数的维度。

状态向量是 8 维(中心坐标 u、v,宽高比 γ = w/h,高度 h,以及各自速度)。观测向量是 4 维(u, v, γ, h)。

预测步骤就两行:x_pred = F @ x,P_pred = F @ P @ F.T + Q。更新步骤按标准卡尔曼公式走,关键是用检测框 z 来算残差 y = z - H @ x_pred,再算卡尔曼增益 K,最后更新状态和协方差。注意 H 是 4×8 的矩阵,只挑出位置部分,速度部分是观测不到但可以通过滤波推出来的。

4.3 轨迹的出生与死亡:min_hits 和 max_age 怎么调

轨迹管理是 MOT 里最“工程”的部分,也是最影响指标的地方。

新检测框不会立刻立为正式轨迹,而是要连续命中几帧才会转正,这个参数叫 min_hits。设小了,误检会变成一条假轨迹,导致 FP 涨;设大了,跟踪响应慢,前面几帧 ID 不连续。SORT 里一般设 3 左右。

轨迹丢失后,并不会马上删除,而是进入“待定”状态,等待重新出现,这个等待帧数叫 max_age。设太大,遮挡超过几秒后目标重现,系统会认为还是原来的人,但中间可能已经 ID 错了;设太小,短暂遮挡就断轨迹。这个参数没有标准,按你的场景来。我建议行人密集场景设 5~10 帧,车辆稀疏场景可以适当放宽到 20 帧左右,核心是不要让它跟“重新初始化轨迹”的能力打架。

4.4 MOT 指标怎么算:脱离指标调参就是盲调

调参之前,一定要先搞懂指标。MOT 领域的四个核心指标:MOTA、IDF1、MT/ML、ID Switch。这个也是很多人搜“yolo 多目标跟踪的指标怎么得到”时最困惑的地方。

MOTA 的公式是 1 - (FN + FP + IDSW) / GT。它综合了漏检、误检和 ID 切换,但注意它的上限不是 100% 的准确率,你把所有目标都漏掉,MOTA 也可以小于零。FP 和 FN 好理解,唯一要记牢的是:IDSW 计数发生在“一个跟踪轨迹的 ID 突然变成另一个已有的跟踪 ID”的时候,或者在标注里目标真实 ID 对应的轨迹被打断后,再被接上时算一次。

IDF1 则是衡量“ID 保持”的指标,它计算的是匹配上的 GT 和轨迹的 F1 分数。和 MOTA 相比,IDF1 更关注跟踪的一致性。这两个指标经常打架:MOTA 高不代表跟踪不切 ID,因为 MOTA 里 IDSW 只罚一次,后面只要重检测对了就行;IDF1 则惩罚整个跟踪片段的断裂。所以调参时两个都要看,不能只看一个。

MT(Mostly Tracked)和 ML(Mostly Lost)是按“轨迹被覆盖的帧数占比”来统计的,分别表示目标至少 80% 的帧数被跟踪、最多 20% 的帧数被跟踪。这对评估模型的“长时间跟踪能力”很有用。

5. 常见问题与排查技巧实录

5.1 为什么我的跟踪器 ID 不停切换

ID 切换多,八成原因不在匈牙利算法,而在你的检测质量。检测框抖动太厉害,卡尔曼滤波器预测的框来回跳,匹配代价变高,然后就匹配错了人。

排查思路我按优先级排:先看检测器单帧效果,尤其遮挡和重叠场景;再看 max_age 和 min_hits 设得是否合理;再调卡尔曼的 Q、R 比例;最后才考虑换更复杂的关联代价。很多人一上来就加外观特征(DeepSORT),但你得明白:加了外观特征只是兜底,检测不稳,一切白搭。

有一种典型的 ID Switch 发生在两个目标交叉瞬间。匈牙利算法做的是全局最优,交叉瞬间它可能为了“整体代价最小”直接交换了两个 ID。这种情况没什么灵丹妙药,只能靠更强的外观信息或者运动模型来缓解,或者干脆接受少量 IDSW,优先保证不丢目标。

5.2 卡尔曼滤波在遮挡时的表现:预测漂移怎么处理

目标被完全遮挡时,卡尔曼滤波没有观察值可以更新,只能靠运动模型一直往前“猜”。匀速假设下,这个预测框会沿着旧速度方向匀速飞出去,速度越快,漂移越远。等目标重新出现时,预测框可能已经远离检测框,匹配失败,轨迹只能重建,ID 就换了。

解决思路有几个:一是降低 max_age,遮挡时间长就放弃旧轨迹;二是给长期未更新的轨迹加大过程噪声 Q,让滤波器变得更不确定、预测框范围更大;三是加门控时对未更新帧数多的轨迹放宽阈值,给重新匹配留点余地。最粗暴也最有效的方法就是:遮挡太久了,干脆清掉,让目标重新初始化一个新轨迹,很多场景下指标反而更好。

5.3 目标多了性能下降:匈牙利不是瓶颈,代价计算才是

匈牙利算法本身 O(n³),n=100 时还是有点压力的,百万次操作,单帧毫秒级,一般不是性能瓶颈。真正的瓶颈是每次匹配前都要计算 M×N 个 IoU,矩阵一大就卡。

优化思路:先用坐标门控粗筛一遍,比如中心距离超过最大速度×帧间隔的候选对直接丢掉;再按类别分组匹配;最后对 IoU 矩阵用向量化计算,别在 Python 里一层层循环。如果目标真的上百个,可以把匈牙利换成贪心匹配先跑一版,实测在很多场景下指标损失不大,速度能快一个数量级。

5.4 常见问题速查表

现象优先排查项参考调整方向
ID 切换频繁检测框稳定性、代价矩阵阈值提高检测置信度阈值、调小门控 IoU 阈值
轨迹断断续续max_age 过小、R 设得过大增大 max_age、降低观测噪声
目标跟丢后漂移Q 过小、运动模型太自信增大 Q、限制最大速度
快速目标跟不上检测帧率低、目标机动大提高检测器速度、增大 Q
多个目标频繁互换 ID遮挡交叉、纯 IoU 代价不够加外观特征或运动方向约束
假轨迹很多检测误检高、min_hits 太小提高检测阈值、增大 min_hits
MOTA 为负FN 和 FP 太高,说明跟踪基本废了先单独看检测指标,修好检测再说

6. 更进一步:从 SORT 到 DeepSORT 的演进思路

6.1 加了外观特征,解决了什么问题

SORT 最大的弱点是纯靠 IoU 做匹配,一旦目标遮挡、检测失败、或者两个目标互相靠近,ID 极容易互换。DeepSORT 的改进思路很直接:除了运动信息之外,再给每个轨迹和检测框都提取一个外观特征向量(通常是 ReID 模型输出),用余弦距离计算外观相似度,和 IoU 代价加权融合得到最终的代价矩阵。

这么做的代价是额外跑一个 ReID 网络,推理开销明显上升,而且 ReID 特征的好坏直接决定上限。在行人跟踪里,DeepSORT 的效果提升通常很明显;但车辆跟踪里,因为同款车太多,外观特征区分度低,反而容易带来新的误匹配。所以加不加外观特征,要按场景来。

6.2 运动模型的替代方案:从匀速到恒转率

卡尔曼滤波里的“匀速模型”确实太简单。目标转弯时,预测框会偏离很多。有人用恒转率模型,状态向量增加角速度;也有人干脆不做运动模型,直接靠高帧率检测和 IoU 匹配。这个选择本质上是在“预测准确性”和“计算复杂度”之间做权衡。

我个人做工程任务的经验是:室内行人和常规车辆场景,匀速模型足够了。真正要换模型的场景,一般同时伴随着相机运动(车载、无人机视角),这时更该做的不是调卡尔曼参数,而是先用图像配准或 EGO 运动补偿把帧间背景对齐。

6.3 端到端方案与启发式方案的取舍

现在很多论文已经不需要卡尔曼和匈牙利这套流程了。Transformer 类的端到端跟踪模型,比如 MOTR、TrackFormer,直接输出跟踪轨迹,不需要显式的关联步骤;ByteTrack 则证明了在高阈值检测下用简单关联也能逼近很好的效果;OC-SORT 在低帧率、强遮挡场景里改进了噪声补偿和观测中心一致性。这些方法各有强势,但卡尔曼+匈牙利这套组合依然是领域基石,也是理解所有后续方法的“接口”。把这两个算法吃透了,再看任何 MOT 论文,你都能快速定位它在改哪个环节。

最后再说一点实操感受。我见过很多同学跑 SORT 代码,改了几个阈值好像效果变好了,但并不知道为什么变好;后来又改了另一个参数,效果又回去了。做 MOT 调试,必须养成记录指标的习惯,每次改动只动一个变量,对比 MOTA、IDF1、IDSW 三个指标的变化,而不是肉眼看视频觉得“好像挺准”。这条经验我踩过不少次,也希望你能少走这个弯路。先跑通最小闭环,再谈改进模型和算法,这条路比一上来就背论文里的公式要快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询