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) |
2.1.3 检测输出与跟踪的接口
检测器输出什么?说白了就是一堆框:[x1, y1, x2, y2, confidence, class_id]。这些框会喂给跟踪器。但这里有个坑——检测器的帧率必须稳定。如果检测器突然掉帧,跟踪器就会“断片”。
我曾经在一个项目中,检测器在GPU上跑30fps,但CPU负载一高就掉到15fps。结果跟踪器频繁丢失目标。后来我加了帧率监控和缓冲机制,才算稳住。
2.2 卡尔曼滤波入门
卡尔曼滤波,听起来很高大上,其实没那么玄乎。它就是一个“预测+修正”的循环。你想想看,我们跟踪目标时,目标的位置是不断变化的。卡尔曼滤波能帮我们估计出目标最可能的位置。
2.2.1 核心思想
卡尔曼滤波假设系统是线性的,噪声是高斯分布。它有两个主要步骤:
- 预测(Predict):根据上一帧的状态,预测当前帧的状态
- 更新(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是宽高比。
2.2.2 在跟踪中的应用
在SORT中,卡尔曼滤波负责预测每个目标在下一帧的位置。然后我们用匈牙利算法,把预测框和检测框做匹配。
我记得第一次调卡尔曼滤波的参数时,Q和R怎么设都不对。Q是过程噪声协方差,R是观测噪声协方差。Q设大了,预测会抖动;R设大了,更新会滞后。后来我总结了一个经验:Q先设小一点,R根据检测器的精度来调。
2.3 匈牙利算法匹配
匈牙利算法,说白了就是解决“谁跟谁配对”的问题。在跟踪里,我们要把上一帧的轨迹和当前帧的检测框一一匹配起来。
2.3.1 算法原理
匈牙利算法解决的是指派问题。给定一个代价矩阵,找到总代价最小的匹配方案。代价矩阵里,行是轨迹,列是检测框,每个元素是它们之间的“距离”。
算法步骤:
- 每行减去最小值
- 每列减去最小值
- 用最少的线覆盖所有零
- 如果线数等于矩阵维度,找到最优解;否则调整矩阵,重复
嗯,听起来有点绕。但在代码里,我们直接用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,然后根据跟踪效果微调。如果目标频繁遮挡,我会加大外观特征的权重。
2.3.3 匹配的阈值
匹配不是无条件的。我们通常会设一个阈值,只有代价小于阈值的匹配才被接受。在SORT里,IoU阈值一般设为0.3。小于0.3的,就算匹配上了也不认。
为什么?因为如果两个目标离得太远,强行匹配只会让跟踪器“漂移”。我曾经试过把阈值设到0.1,结果跟踪器疯狂跳变,ID切换率飙升。
2.4 三者如何协同工作
好了,三个基础都讲完了。它们是怎么配合的?我画个流程给你看:
- 检测器(YOLO):输出当前帧的所有检测框
- 卡尔曼滤波:预测上一帧轨迹在当前帧的位置
- 匈牙利算法:计算预测框和检测框的代价矩阵,做最优匹配
- 更新:匹配上的轨迹用检测框更新卡尔曼滤波;没匹配上的检测框新建轨迹;没匹配上的轨迹标记为丢失
这个循环每帧执行一次。你想想看,是不是很简单?
下一章,我们会正式进入SORT算法的实现。到时候你会看到,这三个基础组件是如何被组装成一个完整的跟踪系统的。嗯,做好准备,代码量会有点大。