引言
视频比图像多了一个维度:时间。这让视频理解同时面对两类问题——「每一帧里有什么」与「跨帧它是谁、在做什么」。前者是检测,后者是跟踪与动作识别。工业上最典型的需求是客流统计、行为分析、交通流量与视频监控,它们的共同点是要在时间轴上维持稳定的身份。
多目标跟踪(Multi-Object Tracking,MOT)的难点不在单帧检测,而在数据关联:把这一帧的检测框与上一帧的轨迹正确配对。遮挡、目标交叉、相似外观、相机运动都会让关联出错,表现为 ID Switch(同一目标被赋予不同 ID)或轨迹碎片化。而动作识别是另一条线:把一段时空体映射成一个类别,难点在时序建模与采样策略。
本文按「范式 → 关联算法 → 代码 → 遮挡处理 → 动作识别 → 指标 → 性能优化」的顺序讲,重点把卡尔曼滤波、匈牙利匹配、ByteTrack 的低分框策略讲透。检测基础见 目标检测与 YOLO 系列工程落地 ,外观特征与 特征提取与图像匹配 中的描述子思路相通。
目录
- 视频理解的两条主线
- 检测跟踪范式与联合检测
- SORT 与卡尔曼滤波
- DeepSORT 与外观特征
- ByteTrack 与低分框利用
- 数据关联与匈牙利算法
- 代码:卡尔曼预测与匈牙利匹配
- 遮挡、ID Switch 与轨迹管理
- 动作识别:3D CNN 与时序 Transformer
- 采样策略与视频输入
- 跟踪评估指标
- 视频推理的算力与延迟优化
1. 视频理解的两条主线
| 主线 | 输入 | 输出 | 代表任务 |
|---|---|---|---|
| 逐帧感知 | 单帧或短片段 | 框、掩码、关键点 | 检测、跟踪、姿态 |
| 时序理解 | 一段视频 | 类别、描述、时间定位 | 动作识别、时序动作定位 |
两条主线的工程约束不同:逐帧感知追求帧率与延迟,通常每帧独立处理再加关联;时序理解追求片段级准确率,可以离线批处理但输入尺寸大。
一个常见的架构是「跟踪提供身份,时序模型提供行为」:先用检测跟踪得到每个 ID 的轨迹,再把轨迹裁剪成片段送动作识别,输出「3 号目标在打架」。这种解耦让两个模块可以独立迭代。
2. 检测跟踪范式与联合检测
MOT 有两代范式:
- TBD(Tracking-by-Detection):先逐帧检测,再做跨帧关联。模块化、易替换、工程成熟,代表是 SORT、DeepSORT、ByteTrack。绝大多数落地系统属于这一类。
- JDE(Joint Detection and Embedding):一个网络同时输出检测框与外观嵌入,省掉独立的外观模型。代表有 FairMOT、JDE、CenterTrack。速度快、端到端,但训练更复杂、对检测器改动大。
TBD 的优势在于可解释与可调试:关联失败时可以单独看是检测漏了还是匹配错了。JDE 的优势在于速度与部署简单。中小规模场景优先 TBD,超大并发或端侧优先 JDE。
3. SORT 与卡尔曼滤波
SORT(Simple Online and Realtime Tracking)的核心只有两件事:卡尔曼滤波预测 + 匈牙利算法匹配。
卡尔曼滤波用状态向量描述每个目标的运动。常用状态是 [cx, cy, s, r, vx, vy, vs]:中心坐标、面积、宽高比,以及它们的变化率。预测步骤用匀速模型外推,更新步骤用当前帧的检测框修正。它假设运动平滑,对匀速或缓变目标很准,对急转弯会滞后。
匹配的代价矩阵用预测框与检测框的 IoU,然后跑匈牙利算法求最小代价的分配。IoU 低于阈值的配对视为无效,未匹配的检测新建轨迹,未匹配的轨迹保留若干帧等待重新出现。
SORT 在 MOT17 上能跑到很高的 MOTA,但因为没有外观特征,遮挡后重新出现时容易换 ID。
4. DeepSORT 与外观特征
DeepSORT 在 SORT 上加了外观嵌入:用一个 ReID 网络(如 OSNet、ResNet 变体)为每个检测框提取 128 维或 512 维特征,用余弦距离构建第二重代价。
最终代价是运动代价与外观代价的加权和:
cost = λ * IoU_cost + (1 - λ) * cosine_cost
匹配时用级联匹配(matching cascade):优先匹配最近几帧出现过的轨迹,把「消失很久的轨迹」放到最后,降低错误重认的概率。外观特征用最近若干次匹配的加权平均维护,而不是只存最后一帧。
代价是每个检测框都要跑一次 ReID 网络,延迟与显存显著上升。工程上常把 ReID 换成轻量模型,或只在匹配歧义时才启用外观分支。
5. ByteTrack 与低分框利用
ByteTrack 的关键洞察是:低置信度检测框不该被直接丢弃。被遮挡的目标常常只有 0.1 到 0.3 的置信度,但它们的位置信息仍然有用。
ByteTrack 的做法是两阶段关联:
- 用高分框(如 score > 0.5)与所有轨迹做第一次匹配,关联方式同 SORT(IoU 加匈牙利)。
- 对剩余未匹配的轨迹,用低分框(0.1 到 0.5)再做一次匹配,只用 IoU 代价,不引入新轨迹。
这样既保住了被遮挡目标的轨迹,又避免了低分框制造大量假轨迹。ByteTrack 在 MOT17 上约 80 MOTA、77 IDF1,且不需要外观模型,速度极快,是目前工程首选。
def bytetrack_associate(tracks, dets_high, dets_low, iou_thr=0.3):
# 第一阶段:高分框匹配
matches, unmatched_tracks = hungarian_match(tracks, dets_high, iou_thr)
# 第二阶段:用低分框抢救剩余轨迹(只更新位置,不新建轨迹)
rescues, still_unmatched = hungarian_match(
[tracks[i] for i in unmatched_tracks], dets_low, iou_thr)
return matches, rescues, still_unmatched
6. 数据关联与匈牙利算法
关联问题的数学形式是「二分图最小代价匹配」:左边是轨迹,右边是检测,边权是代价,要求每个点最多连一条边且总代价最小。
- 匈牙利算法(Kuhn-Munkres):精确解,复杂度 O(n³)。轨迹与检测数量在几十到几百时完全够用。
- 贪心匹配:按代价排序逐个配对,O(n log n),快但非最优,密集场景容易次优。
- 代价设计:IoU 距离
1 - IoU、中心点距离、外观余弦距离,或三者加权。
工程上要注意几点:代价矩阵要做门限裁剪(超过阈值置为无穷),否则会强行配对明显不相关的框;匹配失败的目标要区分「轨迹未匹配」(可能是遮挡)与「检测未匹配」(可能是新目标),处理方式完全不同。
7. 代码:卡尔曼预测与匈牙利匹配
下面手写一个最小可用的跟踪核心,便于理解框架内部行为。
import numpy as np
from scipy.optimize import linear_sum_assignment
class KalmanBox:
def __init__(self, box):
# 状态 [cx, cy, s, r, vx, vy, vs],观测 [cx, cy, s, r]
cx, cy = (box[0] + box[2]) / 2, (box[1] + box[3]) / 2
s = (box[2] - box[0]) * (box[3] - box[1])
r = (box[2] - box[0]) / max(box[3] - box[1], 1e-6)
self.x = np.array([cx, cy, s, r, 0, 0, 0], dtype=float)
self.F = np.eye(7)
for i in range(3):
self.F[i, i + 4] = 1.0 # 匀速模型
self.P = np.eye(7) * 10.0
def predict(self):
self.x = self.F @ self.x
self.P = self.F @ self.P @ self.F.T + np.eye(7) * 0.1
return self.x[:4]
def iou_matrix(tracks, dets):
M = np.zeros((len(tracks), len(dets)))
for i, t in enumerate(tracks):
for j, d in enumerate(dets):
xx1, yy1 = max(t[0], d[0]), max(t[1], d[1])
xx2, yy2 = min(t[2], d[2]), min(t[3], d[3])
inter = max(0, xx2 - xx1) * max(0, yy2 - yy1)
area_t = (t[2] - t[0]) * (t[3] - t[1])
area_d = (d[2] - d[0]) * (d[3] - d[1])
M[i, j] = inter / (area_t + area_d - inter + 1e-6)
return M
def hungarian_match(tracks, dets, iou_thr=0.3):
if len(tracks) == 0 or len(dets) == 0:
return [], list(range(len(tracks)))
cost = 1.0 - iou_matrix(tracks, dets)
cost[cost > 1 - iou_thr] = 1e6 # 门限裁剪,禁止强行配对
rows, cols = linear_sum_assignment(cost)
matches = [(r, c) for r, c in zip(rows, cols) if cost[r, c] < 1e6]
matched_rows = {r for r, _ in matches}
return matches, [i for i in range(len(tracks)) if i not in matched_rows]
注意 linear_sum_assignment 求的是最小代价,所以要用 1 - IoU 而不是 IoU 本身。门限裁剪是关键:没有它,匈牙利算法会把明显不相关的轨迹与检测强行配对。
8. 遮挡、ID Switch 与轨迹管理
遮挡是 ID Switch 的主要来源。工程手段有三类:
- 轨迹保留:轨迹未匹配时不立即删除,保留
max_age帧(常用 30 帧)等待重现,超时才回收。 - 状态机:新轨迹先标记为「暂定」,连续命中若干帧才转「确认」,避免检测噪声制造假轨迹。
- 运动预测补偿:遮挡期间用卡尔曼外推位置,重现后靠预测位置与检测框匹配。
- 外观重识别:重现时用 ReID 特征确认身份,代价是额外算力。
- 相机运动补偿:相机本身在动时,先用光流或特征点估计全局变换,再在补偿后的坐标系做关联。
评估上,ID Switch 数量是最直观的指标。若 ID Switch 集中在某些片段,通常是密集交叉或长时间遮挡,需要针对性地调 max_age 与匹配门限。
9. 动作识别:3D CNN 与时序 Transformer
动作识别把一段时空体映射成类别,主流三条路线:
| 方法 | 代表 | 特点 |
|---|---|---|
| 双流网络 | Two-Stream、TSN | RGB 帧 + 光流,光流算得慢但有效 |
| 3D 卷积 | C3D、I3D、SlowFast | 直接建模时空,效果好,算力大 |
| 时序 Transformer | TimeSformer、VideoMAE、ViViT | 注意力建模长程依赖,需大数据预训练 |
SlowFast 用两条通路:慢通路低帧率高分辨率抓外观,快通路高帧率低分辨率抓运动,两者融合。VideoMAE 把 MAE 的掩码重建搬到视频,自监督预训练后微调效果很好。
工程上的现实选择是:算力有限用 2D CNN 加时序聚合(TSN 思路),算力充足用 SlowFast 或 VideoMAE。Kinetics-400 上 SlowFast R50 约 77% Top-1,VideoMAE ViT-B 可达 81% 以上。
10. 采样策略与视频输入
视频输入的采样直接决定模型看到什么。
- 均匀采样:从整段均匀取 T 帧(常用 8、16、32),覆盖全片段,适合长动作。
- 密集采样:连续取帧,保留细节运动,适合短促动作。
- 多片段采样:把视频切成若干段,每段取一帧,兼顾覆盖与效率,TSN 的经典做法。
- 帧率与时长权衡:30fps 取 16 帧只覆盖 0.5 秒,长动作需要降采样率或增大 T。
import numpy as np
def uniform_sample(num_frames, T=16, seg=2):
# 多片段均匀采样:切成 seg 段,每段均匀取 T//seg 帧
per = T // seg
idx = []
seg_len = num_frames / seg
for s in range(seg):
start = s * seg_len
idx += [int(start + (i + 0.5) * seg_len / per) for i in range(per)]
return [min(i, num_frames - 1) for i in idx]
print(uniform_sample(300, T=16, seg=2))
采样之外,视频解码本身常是瓶颈:H.264 解码在 CPU 上串行,大规模处理时要考虑硬件解码或预抽帧。
11. 跟踪评估指标
| 指标 | 含义 | 关注点 |
|---|---|---|
| MOTA | 1 − (FN + FP + IDSW) / GT | 综合,但 FP/FN 权重高于 IDSW |
| IDF1 | ID 维度的 F1,衡量 ID 保持能力 | 身份一致性 |
| HOTA | 检测与关联的几何平均 | 更平衡的现代指标 |
| ID Switch | 身份切换次数 | 越低越好 |
| MT / ML | 大部分跟踪 / 大部分丢失的轨迹占比 | 轨迹完整性 |
| FPS | 处理帧率 | 实时性 |
MOTA 的缺陷是它对 ID Switch 的惩罚很轻:ID 换了但框仍覆盖目标时 MOTA 几乎不受影响。因此评估身份保持能力应看 IDF1 与 HOTA。现代跟踪论文普遍同时报 MOTA、IDF1、HOTA 三个数。
import numpy as np
def idf1(gt_ids, pred_ids):
# 简化版:按 ID 对统计 TP/FP/FN,再算 ID 级 F1
gt_ids, pred_ids = np.array(gt_ids), np.array(pred_ids)
tp = int(((gt_ids > 0) & (pred_ids > 0) & (gt_ids == pred_ids)).sum())
fp = int(((pred_ids > 0) & (gt_ids != pred_ids)).sum())
fn = int(((gt_ids > 0) & (gt_ids != pred_ids)).sum())
return 2 * tp / max(2 * tp + fp + fn, 1)
print("IDF1:", round(idf1([1, 1, 2, 2], [1, 1, 3, 2]), 3))
12. 视频推理的算力与延迟优化
视频推理的算力需求是单帧的 N 倍,优化空间很大。
- 跳帧检测:检测每 2 到 3 帧跑一次,中间帧用跟踪外推,帧率提升明显、精度损失可控。
- 批处理:把多帧或多路视频拼成一个批送 GPU,吞吐显著提升,方法见 视觉服务化与推理加速 。
- 硬件解码:用 NVDEC 或 VAAPI 做硬件解码,把 CPU 从解码中解放。
- 模型选择:检测用 YOLO-n 系列,跟踪用 ByteTrack,ReID 只在必要时启用。
- TensorRT:导出引擎后 FP16 通常有 1.5 到 2 倍加速,多路并发收益更大。
- 区域裁剪:只在感兴趣区域(ROI)做高分辨率推理,其余区域降采样。
- 多路复用:一路 GPU 同时处理多路视频流,提高利用率。
实时场景要区分「端到端延迟」与「帧率」:跳帧检测能提升帧率,但会让检测结果滞后若干帧,交互类应用要谨慎。
权衡取舍
| 维度 | 倾向 | 代价 |
|---|---|---|
| TBD 与 JDE | 中小规模用 TBD,端侧用 JDE | JDE 训练复杂、难调试 |
| 是否用外观特征 | 遮挡严重时启用 ReID | 延迟与显存上升 |
| 跳帧与逐帧 | 实时优先跳帧 | 检测滞后,快速运动漏检 |
| 采样帧数与算力 | 帧数多精度高 | 算力与显存线性上升 |
| MOTA 与 IDF1 | 身份重要时看 IDF1 | MOTA 掩盖 ID Switch |
| 轨迹保留时长 | 长保留抗遮挡 | 误重认风险上升 |
几条实用原则:
- 先确定业务最在意的是「不漏检」还是「ID 不乱」,这决定调参方向。
- 遮挡密集场景优先上外观特征,稀疏场景 ByteTrack 足够。
- 跳帧是性价比最高的提速手段,但要评估对 ID 稳定性的影响。
- 评估必须同时报 MOTA 与 IDF1,只看一个会误导。
常见坑清单
- 全类别一起做关联:不同类别目标互相抢匹配,需按类别分别跟踪。
- 轨迹立即删除:未匹配就删,遮挡后重现必然换 ID,应设
max_age。 - 代价矩阵不做门限:匈牙利算法强行配对无关目标,产生大量错误关联。
- 检测阈值过高:低置信度的遮挡目标被过滤,轨迹断裂。
- 忽略相机运动:相机平移时所有 IoU 都低,关联全崩,需做运动补偿。
- 用 MOTA 汇报身份能力:ID Switch 被掩盖,业务验收不通过。
- 视频抽帧与训练采样不一致:训练均匀采样,线上连续取帧,分布漂移。
- 光流预处理成瓶颈:双流网络的光流在 CPU 上算得极慢,需 GPU 光流。
- 跳帧后忘记时间对齐:跟踪结果时间戳与视频不同步,事件定位偏移。
- 评估集与线上帧率不同:训练 30fps 测试 10fps,指标不可迁移。
- 忽略轨迹初始化确认:噪声检测直接建轨迹,假目标泛滥。
- 批处理维度弄错:视频批的维度顺序与图像不同,静默算出错误结果。
小结
视频理解工程的主线是:逐帧检测 → 卡尔曼预测 → IoU 与外观代价 → 匈牙利匹配 → 轨迹生命周期管理,动作识别则走「采样 → 时空网络 → 片段聚合」这条线。记住三个判断点:关联的质量决定 ID 稳定性、低分框不该直接丢、评估要看 IDF1 而非只看 MOTA。
选型上,中小规模用 ByteTrack 加轻量 ReID,端侧或超高并发考虑 JDE 方案。性能优化优先跳帧与批处理,其次才是换更小的模型。当业务从「谁在哪」升级到「谁在做什么」时,把跟踪结果作为时序模型的输入,是最省工程的组合方式。
延伸阅读
- 目标检测与 YOLO 系列工程落地 — 跟踪的检测前端
- 特征提取与图像匹配 — 外观特征的经典做法
- 视觉服务化与推理加速 — 多路视频的批处理与吞吐优化
- 多模态视频分析 — 用大模型做视频级理解
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。