畅联云平台丨05-ByteTrack多目标跟踪

ByteTrack 多目标跟踪:高低分两阶段关联与隔帧匹配
本文讲透一件事:每帧彼此独立的人体(或框)检测,如何被赋予跨帧稳定的 track_id。本项目用 ByteTrack(第三方 ByteTrack 库)封装,并按 YOLO 分数与检测步长,用「按 YOLO 分数与步长生成跟踪配置」自适应放宽匹配。空检测帧只做 Kalman 预测。本文不讨论「谁是谁」(识别),只讨论「前后是不是同一个未知者」。
一、 跟踪问题形式化
每一帧有:
- 检测集 D:框 + 分数;
- 轨迹集 T:历史框、速度、状态、id。
任务:把 D 分给 T,或开新轨迹,或宣布丢失。代价常用 IoU(或再加马氏距离),再用匈牙利算法 做全局匹配。
难点不在「人少且清晰」,而在:
- 互相遮挡;
- 检测分数忽高忽低;
- 短时出画再入画;
- 两人交叉(ID switch);
- 隔帧检测 使相邻观测间距变大。
跟踪器吃的是检测的脏时间序列,输出相对干净的身份时间轴。
二、默认配置:四个关键数

注意: 匹配阈值 在头文件默认 0.55,但稠密检测时工厂函数会升到 0.70——见后文。
头文件注释写明:由调用方按 YOLO 阈值 / 检测步长等传入,勿在封装内写死。
三、 传统痛点:扔掉低分框
许多跟踪器只拿高分检测关联。遮挡时真目标分数往往先掉进低分区再消失。若低分直接丢:
- 轨迹立刻匹配失败;
- 很快被判丢失/删除;
- 人再出现时开新 ID —— 同一人换号。
ByteTrack 的关键洞察:低分框里仍可能藏着被遮挡的真目标,应留给「还没匹配上的轨迹」做第二次机会。高分是「很有把握的点名」;低分是「声音发虚但仍可能是本人」。

图:检测按 high_thresh 分成高/低分集;第一阶段高分匹配全部活跃轨迹;第二阶段用低分给未匹配轨迹续命;未匹配高分才新建轨迹。
四、两阶段关联流程
准备:按分数划高分集 / 低分集(边界即 高分阈值;低于 跟踪分数下界 的通常不进跟踪)。
检测框
├─ 高分 (≥ high_thresh) ──┐
└─ 低分 [track_thresh, high) │
▼
第一阶段:高分 ↔ 全部活跃轨迹
│
┌─────────────┼─────────────┐
▼ ▼ ▼
已匹配 未匹配轨迹 未匹配高分
│ │ │
│ ▼ │
│ 第二阶段:低分 ↔ 未匹配轨迹
│ │ │
▼ ▼ ▼
更新 仍未匹配 新建轨迹
→ 丢失逻辑 (通常只用高分)
要点:
- 低分通常不新建轨迹,以免噪声刷屏 ID;
- 低分的职责是 续命,不是 开户。
玩具数字例子
轨迹 T1、T2;检测 D_h_a(0.8)、D_h_b(0.7)、D_l_c(0.27);high=0.30,match=0.55。
- 第一阶段:D_h_a 与 T1 的 IoU=0.72 → 配给 T1;D_h_b 与 T2 的 IoU=0.20 → 未配;
- 第二阶段:D_l_c 与未匹配 T2 的 IoU=0.58 → T2 用低分续命;
- 未匹配高分 D_h_b → 新开 T3;
- 若还有轨迹整帧未配 → 丢失计数 +1。
把这张桌子走两遍,比只看论文伪代码更容易记住「低分为什么不能新建」。
五、轨迹生命周期与 track_buffer

图:新生确认 → 跟踪中 → 丢失(Kalman 预测撑着)→ 超过 track_buffer 删除;buffer 太大易串号,太小易遮挡换号。
- 新生:连续确认若干帧才稳定(防闪烁噪声);
- 跟踪中:正常匹配更新;
- 丢失:暂时匹配不上,保留 ID,用预测框撑着;
- 删除:丢失超过 轨迹缓冲 帧,回收。
本项目默认 buffer=30。按秒理解:

buffer 太大: ID 占坑久,后来的新人可能接上旧号。
buffer 太小: 遮挡一下就换号。
商场跟拍通常愿意略大,优先减少主角换号。
六、按 YOLO 与 stride 生成配置
track / high 对齐检测阈
若 YOLO score_thresh=0.25:
track_thresh = 0.25
high_thresh = max(0.30, 0.30) = 0.30
若 YOLO 提到 0.40:
track_thresh = 0.40
high_thresh = max(0.45, 0.30) = 0.45
高低分池整体上移,避免「检测已滤掉的分数带」在跟踪里空转。
match 随 stride 放宽

图:detect_stride 增大时,相邻观测间人移动更远,IoU 自然变小;match_thresh 从 0.70 降到 0.62/0.55,避免一开隔帧 ID 全碎。
按 YOLO 分数与步长生成跟踪配置时,按 stride 设 match:
if (detect_stride <= 1) cfg.match_thresh = 0.70f;
else if (detect_stride == 2) cfg.match_thresh = 0.62f;
else cfg.match_thresh = 0.55f;
cfg.high_thresh = std::max(score + 0.05f, 0.30f);
失败模式: 开了 stride=3 仍用 0.70 → 快速走动全面断轨;stride=1 却用 0.55 → 交叉更易 ID switch。
七、update:空检测 = 只预测
当 detections 为空(隔帧的「非检测帧」):
- objects 为空;
- 底层仍调用 PersonFocusByteTracker::update:Kalman 预测 + 轨迹维护,不新建、不匹配新观测;
- 输出框是预测位置,可供跟拍/UI,但业务应知「本帧无测量」。
这是隔帧策略能工作的关键:检测贵、预测便宜;中间帧靠运动模型把轨迹 推到当前时刻,下一检测帧 IoU 才可能过阈。
匀速假设的边界
短时近似匀速。剧烈变向、急停、被撞开时预测偏;此时:
- 略大的 轨迹缓冲;
- 略松的二次低分匹配;
- 略低的 匹配阈值(已由 stride 调节);
构成安全网。预测不是为了「画得好看」,是为了时间间隙上仍有重叠可配。
八、输出状态字段
- track_id:身份主键;识别相似度应聚合在 id 上,而不是裸框;
- activated:是否已确认激活(新生未确认可能为 false,视底层实现);
- 丢失态仍可能输出预测框——上层要区分测量/预测,避免误以为检测成功。
九、三个旋钮与检测阈的分工

调参应看:ID switch 次数、碎片轨迹数、主角连续存活长度——不是单帧检测 mAP。
十、 ID switch 典型场景
- 两人交叉:仅靠 IoU 易绑错;可加外观 ReID,或短时看运动方向一致性。
- 队列贴靠:框长期高 IoU,需更强运动模型或地面位置。
- 检测抖动:框左右跳;可轻度平滑观测再关联,但避免双重滞后。
- 遮挡超过 buffer:出遮挡必然新 ID——设计取舍。
- 镜面反射:检出「第二个我」,养出平行假轨。
ByteTrack 原版偏运动与 IoU,极简且强;店内跟拍先把高低分与 buffer、stride-match 调顺,再考虑 ReID。
十一、与隔帧检测的协作表

ByteTrack 能消化一定低分与短暂缺失,但不是无限。N 越大越依赖运动模型,也越要接受 ID 稳定性下降。
调用方典型写法:检测帧把 YOLO 框填进 update;非检测帧传 空 vector 做预测。
十二、fps 与 buffer 的秒意义
底层 源帧率 非法时钳到 25。同一 track_buffer=30:
- 25 fps ≈ 1.2 s 容错;
- 若误把 60 fps 素材标成 25,buffer 的「秒」被拉长,串号风险升。
按秒思考比按帧更不容易误判产品体验。
十、 调试故事三则
故事 A:一开隔帧 ID 全碎
原因:match 仍按「相邻帧人几乎不动」。
处理:空帧用空检测列表调用 update 做预测;按 YOLO 分数与步长生成跟踪配置时自动降 match。
故事 B:遮挡后主角换号
原因:分数掉进低分区被丢,或 buffer 太短。
处理:保证 low 分进入第二阶段;加大 buffer;检查 跟踪分数下界 是否高于实际遮挡分数。
故事 C:广告牌也有 track_id
原因:噪声高分立刻建轨。
处理:提高新生确认;检测侧 NMS/形态过滤先干净;勿让低分建轨。
十四、ByteTrack 不负责什么
- 不负责认出「这是张三」(识别);
- 不负责把切块重复框合并干净(应在进入跟踪前 NMS);
- 不保证出画再入画一定同号(buffer 外新开);
- 不自动修复系统性偏移的检测框(预处理错了,跟踪只能稳稳地错)。
边界划清,才不会把识别问题当成跟踪问题拧。
十五、框平滑与跟踪的先后
- 关联用较新的观测(或轻预测);
- 对外显示/跟拍用轻度平滑;
- 平滑很重时,交叉瞬间框会黏,IoU 更易绑错。
不要把平滑当成提高检测分数的手段。
十六、 算力:代价矩阵规模
关联复杂度约「轨迹数 × 检测数」。高峰两者都大时,匈牙利本身也可观。缓解:
- 空间门控,只对邻近候选算 IoU;
- 限制最大轨迹数,回收长期丢失;
- 检测侧先 NMS,避免重复框抬高矩阵。
算法思想轻,输入框质量与数量直接变成运行时成本。
十七、丢失态预测框怎么用
丢失时仍可输出预测框,但标记丢失,避免业务当检测成功。预测框继续参与下一帧匹配;一旦匹配成功,用真实框纠正并清零丢失计数。人突然折返时短时 IoU 可能对不上——略大 buffer 与略松二次匹配就是容错带。
十八、ID 回收策略

跟拍与事后复盘更适合永不复用。选定后不要中途改变。
十九、参数起步与本项目契约对照

准备 5~10 段含交叉、遮挡、进出画的短视频,每次改参人工数主角换号次数。
二十、 一帧关联的 IoU 门槛直觉
match_thresh=0.70 要求两框大部分重叠才配——适合每帧检测。
0.55 允许更多位移:若人每 stride 周期水平移 40 px、框宽 80,仅平移造成的 IoU 可能掉到 0.5~0.6 量级;再叠加尺度变化,0.70 会系统性失败,0.55 才接得住。
粗算:两同尺寸框水平偏移 δ、宽 W:
IoU ≈ (W-δ)/W × H/H 的简化(高度对齐时)= 1 - δ/W
δ/W=0.3 → IoU≈0.7;δ/W=0.45 → IoU≈0.55。stride 变大 → δ 变大 → 必须降阈或先预测再比。
