第三章:目标跟踪算法原理——状态估计、数据关联、跟踪门、航迹管理
好,咱们进入正题。目标跟踪算法,说白了就是回答三个问题:目标在哪?哪个测量值属于它?它的轨迹怎么连起来? 这三个问题分别对应状态估计、数据关联和航迹管理。我在嵌入式平台上移植过好几套跟踪算法,踩过的坑不少,今天把核心原理掰开揉碎讲给你听。
3.1 状态估计:猜目标下一步在哪
状态估计是跟踪的“大脑”。它根据上一帧的目标位置,预测当前帧的位置,再用当前帧的测量值去修正这个预测。最经典的就是卡尔曼滤波(KF),以及它的非线性变种EKF和UKF。
我个人习惯把卡尔曼滤波拆成两步:预测和更新。
- 预测步:用运动模型(比如匀速、匀加速)算目标大概在哪。公式很简单:
x_k = F * x_{k-1} + B * u_k。F是状态转移矩阵,说白了就是“上一帧的位置+速度,这一帧会变成什么样”。 - 更新步:用实际测量值z_k去修正预测值。核心是卡尔曼增益K,它决定了“我更相信预测还是更相信测量”。
核心公式(嵌入式实现最常用的简化版):
// 预测
x_pred = F * x_prev;
P_pred = F * P_prev * F' + Q;
// 更新
K = P_pred * H' * inv(H * P_pred * H' + R);
x_est = x_pred + K * (z - H * x_pred);
P_est = (I - K * H) * P_pred;
注意:Q是过程噪声协方差,R是测量噪声协方差。这两个参数调不好,滤波器直接发散。我在项目中遇到过,Q设太小,滤波器反应迟钝;R设太小,又会被噪声带偏。
为什么嵌入式上常用卡尔曼滤波?因为它计算量小、内存占用低。矩阵维度通常只有4x4(位置+速度)或6x6(位置+速度+加速度),在Cortex-M4上跑一次更新也就几十微秒。
避坑指南: 我曾经在移植EKF时,忽略了雅可比矩阵的数值稳定性。结果在目标做急转弯时,滤波器直接崩了。后来改用UKF(无迹卡尔曼滤波),虽然计算量大了30%,但鲁棒性好了很多。如果你的目标运动非线性强(比如导弹机动),建议直接上UKF。
3.2 数据关联:哪个测量值属于我的目标?
状态估计算出了预测位置,但传感器会返回一堆测量值(比如雷达点云)。数据关联要解决的就是:这么多点,哪个是我要跟踪的目标?
最常用的方法是最近邻(NN)和联合概率数据关联(JPDA)。
- 最近邻:选距离预测位置最近的测量值。简单粗暴,但多目标场景容易跟丢。
- JPDA:计算每个测量值属于每个目标的概率,然后加权平均。精度高,但计算量爆炸,嵌入式上很少用全JPDA。
我个人在嵌入式上常用的是全局最近邻(GNN),配合匈牙利算法做分配。你想想看,雷达一帧可能返回几十个点,目标只有两三个,用匈牙利算法做最优匹配,计算量可控,效果也不错。
数据关联的嵌入式实现要点:
- 距离度量用马氏距离,而不是欧氏距离。因为马氏距离考虑了测量噪声的协方差,更准确。
- 阈值设置:超过一定距离的测量值直接丢弃,减少计算量。这个阈值就是后面要讲的“跟踪门”。
- 我习惯用固定大小的关联矩阵(比如10x10),避免动态内存分配。嵌入式上最怕malloc。
注意: 数据关联是跟踪算法中最容易出bug的地方。我曾经遇到一个场景:两个目标交叉飞行,最近邻算法直接把两个目标的测量值交换了,导致航迹交叉。后来加了速度一致性检查才解决——如果关联后的目标速度突变超过阈值,就拒绝这次关联。
3.3 跟踪门:给目标画个“活动范围”
跟踪门,说白了就是给每个目标画一个椭圆或矩形区域,只有落在这个区域内的测量值才参与数据关联。这样做有两个好处:
- 减少计算量:不用遍历所有测量值,只检查门内的点。
- 抑制杂波:远离预测位置的噪声点直接被过滤掉。
跟踪门的形状通常由新息协方差矩阵S决定。S = H * P_pred * H' + R,它反映了预测和测量的不确定性。门的边界一般取卡方分布的阈值,比如95%置信度对应门限值约为9.21(2自由度)。
嵌入式实现中的跟踪门优化:
// 判断测量值z是否在跟踪门内
// 新息向量:v = z - H * x_pred
// 马氏距离平方:d = v' * inv(S) * v
// 如果 d < GATE_THRESHOLD,则接受
// 实际代码中,为了避免求逆,可以预计算S的Cholesky分解
// 或者直接用S的逆矩阵(如果维度小)
float d = v[0]*v[0]*invS[0][0] + 2*v[0]*v[1]*invS[0][1] + v[1]*v[1]*invS[1][1];
if (d < 9.21f) {
// 接受该测量值
}
注意:门限值不能设太大,否则杂波全进来了;也不能设太小,否则目标稍微机动就丢了。我一般根据目标运动特性动态调整:目标机动性强时,门放大一些;平稳跟踪时,门缩小。
避坑指南: 我曾经在移植时,把跟踪门设成了固定大小的矩形。结果目标做高速转弯时,预测位置和实际位置偏差很大,测量值直接落在门外,导致跟踪丢失。后来改成自适应椭圆门,根据新息协方差实时调整形状和大小,问题就解决了。
3.4 航迹管理:从生到死的完整生命周期
航迹管理负责跟踪目标的整个生命周期:从首次检测到目标(航迹起始),到持续跟踪(航迹维持),再到目标消失(航迹终止)。
嵌入式上常用的航迹管理状态机如下:
| 状态 | 描述 | 触发条件 |
|---|---|---|
| 临时航迹 | 刚检测到目标,还不确定是不是真目标 | 连续N帧(通常3帧)都有测量值关联上 |
| 确认航迹 | 目标稳定跟踪中 | 临时航迹持续M帧(通常5帧) |
| 预测航迹 | 目标暂时丢失,但还在预测 | 连续K帧没有测量值关联上 |
| 终止航迹 | 目标彻底丢失 | 预测航迹持续L帧(通常3-5帧)仍无关联 |
我个人习惯在嵌入式上实现一个固定大小的航迹池,比如最多同时跟踪10个目标。每个航迹用一个结构体表示,包含状态、卡尔曼滤波器参数、未命中计数器等。
航迹管理的嵌入式实现要点:
- 航迹起始:用“m/n逻辑”——连续n帧中,至少有m帧检测到目标,才确认航迹。我常用3/5逻辑。
- 航迹终止:连续丢失n帧后,直接删除航迹。注意释放内存(如果是动态分配的话)。
- 航迹合并:两个航迹距离太近时,可能是同一个目标被重复跟踪。我一般用最近邻合并,保留置信度高的那个。
注意: 航迹管理中最容易出问题的是航迹分裂。我曾经遇到一个场景:目标被遮挡后重新出现,算法把它当成新目标,导致同一个目标有两条航迹。后来加了航迹关联检查——新航迹起始前,先检查是否与现有航迹的预测位置接近,如果是,则合并到现有航迹中。
3.5 嵌入式移植的实战建议
好了,原理讲完了。最后给你几个我在嵌入式上移植跟踪算法的实战建议:
- 定点化计算:卡尔曼滤波中的矩阵运算,尽量用定点数代替浮点数。我习惯用Q15或Q31格式,精度够用,速度翻倍。
- 避免动态内存:所有数组、矩阵都静态分配。航迹池、关联矩阵、测量值缓存,都在初始化时分配好。
- 中断保护:跟踪算法通常在定时器中断或DMA中断中调用。注意临界区保护,防止数据被破坏。
- 性能调优:用循环展开和SIMD指令加速矩阵乘法。我在Cortex-M7上做过测试,4x4矩阵乘法用SIMD可以快3倍。
最后一句: 目标跟踪算法移植,说白了就是在有限的计算资源下,做最靠谱的估计。别追求理论上的最优,嵌入式上稳定、实时、可预测才是王道。我见过太多人把PC上的算法直接搬过来,结果跑不起来。记住:先跑通,再优化,最后才谈精度。