跟踪算法sort
1.引言
在实现对画面当中的目标进行检测后,紧接着就会考虑到场景当中的这几个问题,我检测的目标会往哪个方向走,我检测的目标出现的时间和消失的时间,这就会引入另一个算法:跟踪算法,目前主流的几个跟踪算法方向如下:使用传统跟踪算法(SORT,DeepSORT),传统深度学习的跟踪算法(LSTM),大模型框架下的跟踪算法(MOTR,MOTR2)等。
算法逻辑
SORT的核心主要是有两个:使用卡尔曼滤波用于目标运动的预测和更新,使用匈牙利算法处理数据匹配的问题。
我们使用YOLO可以拿到比较稳定的输出目标框
输入的格式:
BBOX = [x, y, w, h, score, class]
输出格式:携带着稳定ID的跟踪结果
T_BBOX = [track_id, x, y, w, h, score, class]
算法的核心思想:
运动先验:每条轨迹都有KF,先预测当前帧的位置与尺度(得到预测框)。
空间一致:用预测框 vs 检测框的IOU构造代价矩阵。
全局分配:使用匈牙利算法在所有轨迹与检测框做全局最优匹配。
生存管理:新建/确认/保活/删除,抑制噪声与短时漏检。

3.轨迹状态与卡尔曼滤波
3.1状态与量测
状态向量
$$x=[C_x,C_y,s,r,C_x',C_y',s']$$
Cx, Cy:框中心;s:面积;r:宽高比w/h
点表示速度项(常速度模型)
量测向量
$$z=[C_x,C_y,s,r]^T$$
3.2 预测与更新
Predict:用状态转移矩阵 F 将 𝑥𝑡−1→𝑥𝑡∣𝑡−1,并得到预测协方差 𝑃𝑡∣𝑡−1
Update:若和某检测匹配,用量测 𝑧𝑡 做卡尔曼更新,得到 𝑥𝑡∣𝑡、𝑃𝑡∣𝑡;未匹配则仅保留预测(不确定性增加)
4.数据关联
4.1代价矩阵(Cost)
对每个“预测轨迹框” 𝑇𝑖pred与“当前检测框” 𝐷𝑗
$$C = 1-IOU$$
- 若 IoU𝑖𝑗<iou_threshold(如 0.3),将该对视为不可匹配(代价置大数或直接屏蔽)。
4.2 匈牙利匹配(线性分配)
在代价矩阵 C 上做全局最小化,得到三类集合:
matches:配对成功的(轨迹 i ↔ 检测 j)
unmatched_tracks:本帧未匹配到检测的轨迹
unmatched_dets:本帧未被任何轨迹消费的检测
5.生命周期管理:新建-确认-保活-删除
- 新建(spawn):对每个未匹配检测,初始化一条新轨迹(KF 状态由检测框转换)。
- 确认(confirm):为避免噪声检测,轨迹需连续命中 ≥ min_hits(如 3)才标记为 confirmed 并对外输出。出。
- 保活(ageing):未匹配轨迹仅预测,time_since_update += 1;在 ≤ max_age(如 30 帧)期间允许“隐身”,便于遮挡后“接回”。
- 删除(prune):time_since_update > max_age 的轨迹被移除。
6.完整工作流程
检测:从检测器取本帧框,按 类别/ROI 过滤。
预测:对所有存活轨迹执行 KF Predict,得到预测框。
造价:计算 预测框 vs 检测框 的 IoU,构造代价矩阵;IoU 低于阈值的对剔除。
匹配:匈牙利算法求全局最优分配,得到匹配/未匹配集合。
更新:匹配上的轨迹执行 KF Update;未匹配轨迹仅预测并记一次未命中。
新建:对未匹配检测生成新轨迹。
清理:删除超龄轨迹;达到 min_hits 的轨迹由 tentative→confirmed
输出:仅输出 confirmed 轨迹作为跟踪结果(用于计数/统计/上游预测)。
7.参考实现
tracks = [] # 轨迹对象:KF、id、age、hits、time_since_update、status 等
next_id = 1
iou_thr = 0.3
min_hits = 3
max_age = 30
for frame in video_stream:
dets = detector(frame) # [[x1,y1,x2,y2,score,cls], ...]
dets = filter_by_roi_and_class(dets)
# 1) KF 预测
for t in tracks:
t.kf.predict()
t.pred_box = state_to_box(t.kf.x)
# 2) IoU 代价矩阵(IoU < iou_thr 的对置为大数或剔除)
C = build_cost_matrix(tracks, dets, iou_thr=iou_thr)
# 3) 匈牙利匹配
matches, u_trks, u_dets = hungarian_assign(C)
# 4) 更新匹配到的轨迹
for i, j in matches:
z = box_to_meas(dets[j]) # z = [cx,cy,s,r]
tracks[i].kf.update(z)
tracks[i].time_since_update = 0
tracks[i].hits += 1
tracks[i].age += 1
tracks[i].status = 'confirmed' if tracks[i].hits >= min_hits else 'tentative'
# 5) 未匹配轨迹仅预测(已在 step 1 完成)
for i in u_trks:
tracks[i].time_since_update += 1
tracks[i].age += 1
# 6) 为未匹配检测新建轨迹
for j in u_dets:
t = new_track_from_det(dets[j], next_id) # 初始化 KF、id、计数器等
tracks.append(t)
next_id += 1
# 7) 删除超龄
tracks = [t for t in tracks if t.time_since_update <= max_age]
# 8) 输出仅 confirmed 的轨迹
outputs = [(t.id, state_to_box(t.kf.x)) for t in tracks if t.status == 'confirmed']
yield outputs
8.参数选择与工程实践
iou_threshold(默认 ~0.3):低了易误配,高了易漏配;小目标/抖动大可稍降(0.2–0.3)。
min_hits(2–4):越大越能抑制假框,但轨迹“生效”更慢。
max_age(与 FPS 相关):30 FPS 场景可先取 30(≈1s 隐身);低 FPS/遮挡重可取 45–60。
按类分轨:car/bus/truck/person 分别跑一套 SORT,避免跨类错配。
计数/统计:仅对 confirmed 轨迹计数;跨线后对 同 track_id 做短 TTL 去重,防止抖动重复计数。
低 FPS/快速运动:适当调大 KF 的 过程噪声 Q 或 max_age,容忍更大的位移与预测误差。
- 可视化与日志:保存每帧的匹配关系、未匹配集合、IoU 分布,利于调参与复现。
9.常见问题和增强路线
近距离交汇/遮挡 → ID 互换
- 原因:SORT 只用 IoU,不看外观。
解决(从轻到重):
运动门控(马氏距离 χ²):将运动不合理的候选对直接剔除。
DeepSORT:加入 ReID 外观特征 与 级联匹配(强烈改善 ID 稳定)
OC-SORT:用光流/几何一致性改进时间关联(无外观也稳)。
BoT-SORT / StrongSORT:更强的外观建模与相机补偿。
ByteTrack:高/低置信两阶段关联,拥挤场景召回更高。
移动相机(行车记录仪):考虑 相机运动补偿(CMC) 或选 OC-SORT/BoT-SORT。
多传感器融合:有毫米波/激光雷达时,把 距离/速度 加入门控或代价项,串号率会显著下降。