Deep-OC-SORT = OC-SORT 的"以观测为中心"骨架 + 五个叠加改进。本文逐项对照两版源码讲清每个改进改了什么、为什么、在代码哪里、怎么开关。骨架三机制:ORU——漏检时冻结滤波参数,找回后在最后两个真实观测间线性插值出虚拟轨迹逐帧重放,把滤波参数恢复到与真实轨迹一致;OCR——找回轮用最近真实观测框而非漂移的预测框做二次关联;VDC——关联代价中加入速度方向一致性项,同向加分、反向减分。本文只讲五个叠加改进,骨架机制在改进中的参与随文说明。
总开关关系:五个改进默认全开;全关(--emb_off --cmc_off --aw_off --new_kf_off --grid_off)即 OC-SORT 基线。
改进一:外观特征(Embedding)—— 关--emb_off
解决什么
OC-SORT 是纯运动跟踪器:关联只看 IoU 和方向。两个后果:
- 遮挡后认不回:目标被挡几帧再出现,位置可能还在预测框附近(OCR 能救),但如果旁边正好有别的目标挤过来,纯 IoU 分不出"这框是谁";
- 长时间离开视野必换 ID:目标出画再入画,运动连续性彻底断裂,纯运动方案无解。
外观通道补上"长得像不像"这重证据:每条轨迹维护一个外观特征向量(ReID 网络提取、L2 归一化),关联时与检测的特征算相似度,直接进总代价。
代码对照
| 环节 | OC-SORT | Deep-OC-SORT |
|---|---|---|
| 特征提取 | 无 | EmbeddingComputer.compute_embedding()(deep_ocsort/embedding.py),每帧对全部过关检测框批量推理 |
| 轨迹特征 | 无 | KalmanBoxTracker.emb,建档时初始化为该检测特征,之后每帧配对时 EMA 更新(update_emb) |
| 关联代价 | -(iou + angle_diff_cost) | -(iou + angle_diff_cost + emb_cost)(association.pyassociate()) |
| 找回轮 | OCR 纯 IoU | 同样纯 IoU(源码注释 TODO maybe use embeddings here——官方也暂未在找回轮用外观) |
特征怎么算
- 检测侧:过关检测框裁剪 → BGR 转 RGB → resize 64×128 → ImageNet 归一化 → ckpt.t7(resnet18 风格,512 维)→ L2 归一化;
- 轨迹侧:
update_emb(emb, alpha):trk_emb = alpha * 旧 + (1-alpha) * 新再归一化。alpha 不是常数——dets_alpha = af + (1-af)*(1-trust),其中trust = (conf - det_thresh) / (1 - det_thresh):检测分越低 alpha 越大(越信历史特征),高分检测才让新特征大比例进来。遮挡中的检测(往往低分)不会污染轨迹特征; - 相似度:整框模式
emb_cost = det_embs @ trk_embs.T(两 都是归一化的,点积即余弦);网格模式走split_cosine_dist(见改进五)。
本工程的适配
源码用 FastReID SBS-S50 权重(需从 Google Drive 下载、硬编码 GPU+half)。本工程 embedding.py 重写:换 ckpt.t7(512 维 resnet18 风格权重,已内置在deep_ocsort/deep/checkpoint/,无需额外下载)、CPU/GPU 自适应、删 MOT 数据集 pickle 缓存。对外接口与源码基本一致(少 tag 参数与缓存落盘接口),算法行为等价(骨干不同、特征空间不同,但"512 维归一化外观向量 + 余弦比对"的机制一致)。
改进二:相机运动补偿(CMC)—— 关--cmc_off
解决什么
OC-SORT 的运动模型默认相机不动。相机一动(手持、车载、云台),画面里所有目标"看起来"整体位移——这个位移和目标自身运动混在一起,卡尔曼速度估计、VDC 方向、OCR 找回锚点全体失准。典型症状:相机一抖,满屏 ID 切换。
怎么做
每帧估计相机自身的运动(2×3 仿射矩阵),在关联之前把全体轨迹的参考坐标"搬"到新相机坐标系下:
estimateAffinePartial2D(上一帧角点, 本帧角点, RANSAC) ↓ 仿射矩阵 [m | t] KalmanBoxTracker.apply_affine_correction(affine) ├── last_observation(OCR 找回锚点)搬移 ├── observations 里 Δt 范围内的观测(VDC 参考点)搬移 └── KF 状态搬移(kalmanfilter.py apply_affine_correction, 8 维用 kron(I,m) 变换全部状态含速度;冻结快照同步搬)关键细节
- 取点避开目标:
compute_affine把本帧检测框区域掩掉——目标是"画面里动的东西",只用背景估计相机运动; - 固定相机无害:固定机位下估计出的仿射接近单位矩阵,轨迹坐标几乎不动,开销恒定(一次角点检测 + 一次 LK 光流),可以无脑默认开;
- 估计方法:默认 sparse(goodFeaturesToTrack 最多 3000 角点 + LK 金字塔光流 + RANSAC 刚体估计),可选 sift(SIFT 描述子 + 暴力匹配 + Lowe 比值筛选)。
--cmc_method切换。
与 8 维 KF 的配合
仿射对 8 维状态的变换(np.kron(np.eye(4), m))把 [x,y,w,h,vx,vy,vw,vh] 八个分量里的每个 (值,速度) 对按 2×2 线性部分变换,平移只加在中心上——速度向量跟着旋转缩放,宽高不接收平移。7 维状态的 s(面积)、r(宽高比)不是线性量,只能跳过(代码里注释掉了缩放尝试),这正是新 KF 与 CMC 更搭的原因之一。
改进三:自适应加权(AW)—— 关--aw_off
解决什么
外观权重w_assoc_emb是个全局常数(0.75)。两难:
- 调大:外观主导——ReID 特征不可靠时(同类车长得都差不多、光照突变)疯狂配错;
- 调小:运动主导——外观信息基本浪费。
AW 的思路:让每个检测/轨迹自己的"区分度"决定外观话语权。一个检测如果与轨迹 A 特别像、与轨迹 B 完全不像(top1 与 top2 差距大),说明外观在这里区分度很高,该信;如果跟谁都差不多像(差距小),说明外观分不出谁是谁,少信。
代码(association.pycompute_aw_new_metric)
对外观相似度矩阵 emb_cost: 每行(检测视角):row_bonus = min(top1 - top2, max_diff) / 2 每列(轨迹视角):col_bonus = min(top1 - top2, max_diff) / 2 权重矩阵 = 全阵填基准 w_assoc_emb + 行列奖金 最后 emb_cost *= 权重矩阵- 行列各发一半奖金:检测侧和轨迹侧的区分度都认可才重罚;
min(…, aw_param=0.5)封顶:区分度再高,外观权重最多涨 0.5;- 关闭时(aw_off)退回
emb_cost *= w_assoc_emb固定权重。
直觉例子
三个检测 D1/D2/D3,三条轨迹 T1/T2/T3,外观相似度:
T1 T2 T3 D1 0.95 0.20 0.18 ← top1-top2=0.75(封顶0.5):D1 明显像 T1,外观权重+0.25 D2 0.60 0.55 0.58 ← top1-top2=0.05:D2 跟谁都差不多,权重≈基准D1 的外观证据被加权放大(该信时就信),D2 的外观证据被稀释(分不出来就靠 IoU 和方向)。
改进四:新 8 维卡尔曼滤波(New KF)—— 关--new_kf_off
状态向量对照
| OC-SORT 7 维 | Deep-OC-SORT 8 维 | |
|---|---|---|
| 位置 | x, y(中心) | x, y(中心) |
| 尺寸 | s = 面积, r = 宽高比 | w, h 直接宽高 |
| 速度 | vx, vy, vs(r 无速度) | vx, vy, vw, vh 四个都有 |
7 维表示的两个问题:
- s、r 是非线性量:面积变化 = 宽高变化的乘积,检测框宽高噪声到 s/r 上被非线性放大/稀释;宽高比 r 连速度建模都没有(车辆侧转时 r 突变,KF 只能硬拉 s);
- 噪声是全局常数:
R[2:,2:] *= 10这种一刀切,远处小目标(几个像素)和近处大目标(几百像素)用同一套绝对噪声。
8 维改法:
- 尺寸直接建模:w、h 各自线性、各自有速度,侧转、变尺寸场景预测平滑;
- 噪声随尺寸自适应(ocsort.py
new_kf_process_noise/new_kf_measurement_noise):Q = diag((p·w)², (p·h)², (p·w)², (p·h)², (v·w)², (v·h)², (v·w)², (v·h)²),p=1/20、v=1/160——每帧 predict 按当前宽高重算,大目标噪声绝对值大、相对比例一致;R = diag((m·w)², (m·h)², (m·w)², (m·h)²),m=1/20——每次 update 重算,小目标不会被大目标的观测噪声尺度淹没;
- 负宽高保护:predict 前
w+vw ≤ 0则强制vw = 0,杜绝外推出负尺寸; - 初始化:
P = Q 尺度,位置块 ×4、速度块 ×100(初速度不可信),与 7 维版思想一致但直接从自适应 Q 派生。
其余机制(ORU 冻结/回滚、观测登记与状态维护)不变——unfreeze里按 KF 类型取 [x,y,w,h] 或 [x,y,s,r] 分别处理,虚拟轨迹重放对两版都成立。
详细推导
预测方程、噪声设计理由、ORU 重放过程见 卡尔曼滤波预测过程.md。
改进五:网格特征(Grid)—— 关--grid_off
解决什么
整框一个特征的问题:部分遮挡时特征被污染。车下半截被前车挡住,整框特征里混进大量前车像素——特征既不像自己也不像前车,比对失真。
怎么做
每个框横切上中下三等分,各出一支 512 维特征(_get_horizontal_split_patches):
检测框特征: (3, 512) 轨迹特征: (3, 512) ↓ split_cosine_dist 比对: 检测的第 i 条 vs 轨迹的第 1/2/3 条 → 3×3 余弦阵 检测每条取三列最大值 → patch_affinity (3,) 硬阈值: 三条全部 > 0.55 才算有效相似(取 max),否则整对判 0- 错位比对:检测的上条可以跟轨迹的中条比——目标在框内的构图偏移(检测框抖动导致条带错位)不至于误杀;
- 硬阈值一票否决:任何一条完全不像(< 0.55),整对相似度归零——部分遮挡时,露出那几条足够判断像不像,被挡的条带本来就不该像。
与其他改进的关系
- 网格开着时,
emb_off关掉才有意义(外观整体关闭则网格无从谈起); - AW 对网格相似度矩阵同样生效(区分度逻辑与特征维度无关);
- EMA 轨迹特征在网格模式是 (3,512) 逐元素滑动平均,语义不变。
开关速查
| 开关(默认全开) | 关闭后退化为 | 依赖关系 |
|---|---|---|
--emb_off | 无外观的 OC-SORT 关联 | 关掉后 grid/aw 随之失效(emb_cost=None 走 0) |
--cmc_off | 不补偿相机运动 | 独立 |
--aw_off | 外观权重固定 0.75 | 依赖 emb 开 |
--new_kf_off | 7 维 KF | 独立(CMC 对 7 维的搬移少搬 s/r) |
--grid_off | 整框单特征 | 依赖 emb 开 |
| 描述 | 链接 |
|---|---|
| 项目 | 基于 Ultralytics +Deep-OC-SORT 多目标跟踪与改进 |
| 往期回顾 | 链接 |
| 数据集 | 数据集汇总 |
| 软件开发 | 深度学习软件开发汇总 |