2、基础知识回顾:目标检测基础(YOLO系列)、卡尔曼滤波入门、匈牙利算法匹配

各位同学,欢迎来到第二章。在正式进入多目标跟踪之前,咱们得先把地基打牢。你想想看,跟踪的前提是什么?是检测。没有检测,你连“目标”在哪儿都不知道,还谈什么跟踪?

所以这一章,我会带大家快速过一遍三个核心基础:YOLO系列目标检测卡尔曼滤波、以及匈牙利算法匹配。这三个东西,说白了就是多目标跟踪的“三驾马车”。

2.1 目标检测基础:YOLO系列

目标检测这块,我个人最常用的就是YOLO。为什么?因为它快,而且准。我在做实时跟踪项目时,YOLO几乎是首选。

2.1.1 从R-CNN到YOLO

早期的检测方法,比如R-CNN,是“先提候选框,再分类”。这流程太慢了。YOLO的思路完全不同——它把检测当成一个回归问题,一次搞定。

YOLO的核心思想很简单:

  • 把图片分成S×S的网格
  • 每个网格负责预测B个边界框
  • 每个框输出:x, y, w, h, confidence, 以及类别概率

嗯,这里要注意:YOLO的损失函数是直接优化检测精度的。它把定位误差、置信度误差、分类误差放在一起算。我刚开始用YOLOv3时,总觉得这个损失函数设计得有点“糙”,但后来发现,正是这种“糙”让它跑得飞快。

2.1.2 YOLOv3与YOLOv5的差异

YOLOv3用了Darknet-53作为骨干网络,还引入了多尺度预测。YOLOv5呢?它其实不是官方版本,但社区用得最多。我个人习惯用YOLOv5,因为它的工程化做得特别好。

特性 YOLOv3 YOLOv5
骨干网络 Darknet-53 CSPDarknet
数据增强 基础 Mosaic, MixUp
训练速度 较慢 快(自动锚框)
部署难度 中等 简单(ONNX/TensorRT)
我的建议:如果你刚入门,直接上YOLOv5。它的文档全,社区活跃,踩坑成本低。我曾经在YOLOv3上折腾了三天才跑通一个自定义数据集,换成YOLOv5后,半天搞定。

2.1.3 检测输出与跟踪的接口

检测器输出什么?说白了就是一堆框:[x1, y1, x2, y2, confidence, class_id]。这些框会喂给跟踪器。但这里有个坑——检测器的帧率必须稳定。如果检测器突然掉帧,跟踪器就会“断片”。

我曾经在一个项目中,检测器在GPU上跑30fps,但CPU负载一高就掉到15fps。结果跟踪器频繁丢失目标。后来我加了帧率监控和缓冲机制,才算稳住。

2.2 卡尔曼滤波入门

卡尔曼滤波,听起来很高大上,其实没那么玄乎。它就是一个“预测+修正”的循环。你想想看,我们跟踪目标时,目标的位置是不断变化的。卡尔曼滤波能帮我们估计出目标最可能的位置。

2.2.1 核心思想

卡尔曼滤波假设系统是线性的,噪声是高斯分布。它有两个主要步骤:

  1. 预测(Predict):根据上一帧的状态,预测当前帧的状态
  2. 更新(Update):用当前帧的观测值,修正预测值

公式其实就五个:

# 预测步骤
x_pred = F * x_prev + B * u
P_pred = F * P_prev * F^T + Q

# 更新步骤
K = P_pred * H^T * (H * P_pred * H^T + R)^(-1)
x_updated = x_pred + K * (z - H * x_pred)
P_updated = (I - K * H) * P_pred

别被公式吓到。在SORT和DeepSORT里,我们只用到了位置和速度。状态向量一般是 [x, y, s, r, vx, vy, vs],其中s是面积,r是宽高比。

关键点:卡尔曼滤波的“记忆”能力来自协方差矩阵P。P越大,说明我们越不确定,更新时就越依赖观测值。P越小,说明我们越相信预测。

2.2.2 在跟踪中的应用

在SORT中,卡尔曼滤波负责预测每个目标在下一帧的位置。然后我们用匈牙利算法,把预测框和检测框做匹配。

我记得第一次调卡尔曼滤波的参数时,Q和R怎么设都不对。Q是过程噪声协方差,R是观测噪声协方差。Q设大了,预测会抖动;R设大了,更新会滞后。后来我总结了一个经验:Q先设小一点,R根据检测器的精度来调

避坑指南:我曾经在低帧率场景下(比如10fps),卡尔曼滤波的预测误差特别大。因为帧间隔太长,线性运动假设不成立了。这时候要么提高帧率,要么改用更复杂的运动模型。

2.3 匈牙利算法匹配

匈牙利算法,说白了就是解决“谁跟谁配对”的问题。在跟踪里,我们要把上一帧的轨迹和当前帧的检测框一一匹配起来。

2.3.1 算法原理

匈牙利算法解决的是指派问题。给定一个代价矩阵,找到总代价最小的匹配方案。代价矩阵里,行是轨迹,列是检测框,每个元素是它们之间的“距离”。

算法步骤:

  1. 每行减去最小值
  2. 每列减去最小值
  3. 用最少的线覆盖所有零
  4. 如果线数等于矩阵维度,找到最优解;否则调整矩阵,重复

嗯,听起来有点绕。但在代码里,我们直接用scipy的linear_sum_assignment函数就行。一行代码搞定。

from scipy.optimize import linear_sum_assignment

# cost_matrix: 轨迹数 × 检测数
row_ind, col_ind = linear_sum_assignment(cost_matrix)

2.3.2 代价矩阵的设计

代价矩阵怎么算?在SORT里,用的是IoU距离。IoU越大,代价越小。在DeepSORT里,还加入了外观特征距离(余弦距离)。

我个人习惯把两种距离加权融合:

cost = lambda_iou * iou_distance + lambda_app * appearance_distance

权重怎么调?我一般先设lambda_iou=0.5, lambda_app=0.5,然后根据跟踪效果微调。如果目标频繁遮挡,我会加大外观特征的权重。

小技巧:匈牙利算法要求代价矩阵是方阵。如果轨迹数和检测数不一样,可以用一个很大的值(比如1e9)填充。这样算法就不会把不存在的匹配算进去。

2.3.3 匹配的阈值

匹配不是无条件的。我们通常会设一个阈值,只有代价小于阈值的匹配才被接受。在SORT里,IoU阈值一般设为0.3。小于0.3的,就算匹配上了也不认。

为什么?因为如果两个目标离得太远,强行匹配只会让跟踪器“漂移”。我曾经试过把阈值设到0.1,结果跟踪器疯狂跳变,ID切换率飙升。

2.4 三者如何协同工作

好了,三个基础都讲完了。它们是怎么配合的?我画个流程给你看:

  1. 检测器(YOLO):输出当前帧的所有检测框
  2. 卡尔曼滤波:预测上一帧轨迹在当前帧的位置
  3. 匈牙利算法:计算预测框和检测框的代价矩阵,做最优匹配
  4. 更新:匹配上的轨迹用检测框更新卡尔曼滤波;没匹配上的检测框新建轨迹;没匹配上的轨迹标记为丢失

这个循环每帧执行一次。你想想看,是不是很简单?

总结一下:YOLO负责“看”,卡尔曼滤波负责“猜”,匈牙利算法负责“配对”。三者缺一不可。

下一章,我们会正式进入SORT算法的实现。到时候你会看到,这三个基础组件是如何被组装成一个完整的跟踪系统的。嗯,做好准备,代码量会有点大。