视频理解这几年在很多方向里都成了“卡脖子”问题:单看一帧图像已经能做人、物、场景识别,但要回答“这个人在做什么”“事件是怎么演变的”“下一帧会发生什么”,就必须回到视频本身。而视频的本质不是图片的堆叠,是目标在不断运动、姿态在连续变化、动作在时间维度上起伏。这时候,视频理解就不再只是分类网络的问题,而是由多个“后置模块”共同完成的系统工程。
本文要聊的内容,正是视频理解流水线里最容易被科研新手忽略、却又决定系统上限的三个模块:多目标跟踪(MOT)、2D人体姿态估计、SlowFast时序建模。如果你是刚开始接触计算机视觉的研究生,或者想从图像任务往视频任务迁移,这篇文章会给你一张相对完整的“地图”。
1. 为什么视频理解需要“组合式流水线”
1.1 单帧模型的局限:看到了,但不一定看得懂
先从一个常见的实验现象说起。假设你用 ImageNet 预训练的分类网络去处理一段“两个人擦肩而过,其中一个抬手打招呼”的视频,模型很可能输出“person”“hand”这种空间语义,却没有办法告诉你“谁在打招呼”“动作持续了多久”“另一个人的反应是否构成交互”。
原因在于,单个帧级模型看到的是静态构图,而视频信息至少包含三个层次:
- 空间层:画面里有哪些目标、目标之间是否存在遮挡;
- 时间层:目标在连续帧中的位移轨迹、姿态变化、动作快慢;
- 语义层:目标交互是否构成某个行为或事件。
如果把单帧模型直接放到视频上推理,最直接的问题是缺少“帧与帧之间的身份一致性”。模型能检测出人,却不知道前一帧里的“人A”是不是当前帧里的“人A”。没有这种一致性,后续所有行为统计、轨迹分析、动作计数都没有依据。
1.2 后置模块在整个流程中的位置
在一个标准的视频理解系统中,通常会先做目标检测或行人检测,然后进入多目标跟踪模块得到“谁在哪、持续在哪”,接着用单人姿态估计来提取骨骼关键点,最后把一段时间内的轨迹或骨架序列交给时序建模模块(比如 SlowFast)判断动作类别。
这里有一个很形象的比喻:检测器像是眼睛,负责看见场景;跟踪器像是记忆,负责把同一目标跨帧绑定;姿态估计像是骨骼感知,负责让模型理解人的身体结构;SlowFast 这类时序模型则像大脑,负责整合前面积累的证据,判断“到底发生了什么”。
所以“后置”并不代表模块技术含量低,反而是决定理解质量的承重墙,这也是为什么很多新手训练的动作分类模型明明 loss 很低,一放到真实视频上就表现很差:因为前面的目标跟踪和后置的时序对齐没有做好。
2. 核心概念拆解:三个模块各解决什么问题
2.1 多目标跟踪 MOT:让模型拥有“跨帧记忆”
多目标跟踪,英文 Multi-Object Tracking,缩写 MOT。它的输入是视频帧(通常还会配合检测器输入),输出每帧中每个目标的边界框和全局一致的身份编号 ID。
跟踪的本质,是解决数据关联问题。想象你面前有一群人在移动,你的眼睛需要判断:第 1 帧里左侧穿红衣的人,在第 5 帧移动到了右侧,这个“红衣人”在数学上是一串框,但它们共享同一个 ID。多目标跟踪要做的事情,就是在检测结果之间建立正确的对应关系,同时尽量保证不漏检、不误匹配。
早期流程以 SORT 为代表,做法很简单:检测器给出位置,卡尔曼滤波预测下一帧位置,再用匈牙利算法做 IoU 匹配。后续 DeepSORT 加入了 Re-ID 外观特征,让目标在遮挡后还能被认回来。近几年 ByteTrack 的思路更有意思:把检测器输出的低分框也利用起来做二次匹配,显著减少了目标暂时遮挡导致的漏跟踪。
从工程应用看,多目标跟踪本身不只是视觉任务,也是下游动作识别、人群计数、自动驾驶轨迹预测的根基。
2.2 2D人体姿态估计:把视觉转换为可计算的“骨架”
2D人体姿态估计的目标是定位人体关键点,比如肩膀、手肘、手腕、膝盖、脚踝等,在图像坐标系中的 x、y 坐标。它的输出是带语义的关键点序列,相当于把视频中的“像素人”抽象成“骨架人”。
主流方法可以分成两类:
- Top-down:先用检测器把每个人裁出来,再对单人姿态做估计。优点是精度高,但速度会随人数增加而下降。
- Bottom-up:直接在整图上找到所有关键点,再分组到对应的人身上。优点是在多人场景下速度快,但分组过程容易出错。
在早期的很多动作识别论文中,大家习惯把姿态估计结果直接作为模型的输入,用骨架序列做分类。这种做法有一个好处:模型不再被背景、光照、衣服颜色干扰,视频理解的注意力可以完全放在人体运动本身。不过,一旦检测或者跟踪出错,姿态质量也会连带下降,这也是“后置模块相互耦合”的体现。
2.3 SlowFast与时序建模:像人眼一样“既看清又看动”
SlowFast 是 Meta AI(原 Facebook AI Research)在 2019 年提出的视频动作识别框架(原论文《SlowFast Networks for Video Recognition》)。它有一个很独特的生物启发:人的视觉系统里,有一部分神经元对空间细节敏感,处理得比较慢;另一部分对变化敏感,处理得更快。
SlowFast 模型因此设计成双路径网络:
- Slow 路径:使用的帧率低(比如每 alpha 帧取一帧),但通道数较多,负责提取稳定的空间语义信息;
- Fast 路径:使用的帧率是 Slow 路径的 alpha 倍,但通道数较少,负责捕捉运动变化;
- 两条路径之间通过横向连接(lateral connections)融合信息,最后统一分类。
这里的 alpha 是一个关键的超参数,通常取 4 或 8。也就是说,如果 Slow 路径每秒处理 4 帧,Fast 路径就处理 16 帧或 32 帧。Fast 路径虽然单个帧分辨率低,但它“看得勤”,所以可以把目标移动、挥手速度、动作节奏这类动态信息捕获下来。
从时序建模的角度看,SlowFast 并非唯一方案。著名的时序建模方式还包括:
- 3D 卷积,比如 C3D、I3D,用三维卷积核同时编码空间和时间;
- 视频 Transformer,比如 TimeSformer、VideoSwinTransformer,用 attention 机制建模长距离时空关系;
- 双流网络,比如把 RGB 帧和光流帧分别送入两个网络再融合。
SlowFast 的优势在于,它不需要像光流那样做复杂的预计算,而是用“不同帧率+不同通道宽度”的廉价方式拆分了快慢信息,训练和部署都比较方便,再加上在 Kinetics 数据集上的强表现,成了入门视频理解时非常适合研究的模型之一。
3. 技术结合点:跟踪、姿态与时序建模如何协作
3.1 从“检测人”到“识别人的行为”
单独做目标跟踪,输出只是“灰色框+ID”。单独做姿态估计,输出是“关键点+骨架”。单独做 SlowFast,往往直接送分类器,但它缺少对具体目标的跟踪对齐能力。
三者的协作关系可以这样想象:
- 第一层:使用目标检测器找出每一帧中所有人。
- 第二层:多目标跟踪对检测框分配 ID,生成连续轨迹,并输出某一目标过去 N 帧的裁剪序列。
- 第三层:2D 姿态估计在裁剪序列上提取每帧的关键点,得到骨架轨迹。
- 第四层:把裁剪序列或骨架序列送入 SlowFast,判断具体动作类别。
这样一来,模型不再是对整段视频做“全局平均”,而是关注到某个具体人的一段时间变化。这样也能减少背景和他人动作对行为识别的干扰。
3.2 多目标跟踪在 SlowFast 中扮演的角色
SlowFast 的输入通常是连续的视频片段(clip),但如果把整段视频都送进去,模型很难区分场景里多个行为者。多目标跟踪在中间起到了“时空定位器”的作用,它把轨迹片段裁剪给 SlowFast,让分类单元聚焦到一个目标的时间包络上。这个“跟踪 + 识别”的级联结构,在真实监控视频、体育视频中非常常见。
3.3 姿态轨迹与时序建模的互相补充
2D姿态估计提供的是人体拓扑信息。它的好处是可以在遮挡较多时用骨骼结构推测动作;缺点是它太依赖前端跟踪质量。如果跟踪里出现 ID Switch(目标ID交换),姿态轨迹就会混入另一个人的关键点,后续时序模型很容易误判。因此很多研究开始把跟踪置信度、姿态热图置信度融合进时序模型,让模型学会“质疑”输入质量。这也是后续做科研时可以考虑的切入点。
4. 一起动手:搭建一个简单的视频理解 Demo
4.1 环境准备与技术选型
这部分不给出固定版本号,因为相关库迭代较快,不同电脑、不同 CUDA 环境会有差异。建议参考下面这个基线:
- 操作系统:Ubuntu 20.04/22.04,或 Windows 10/11 + WSL2
- Python:3.8 或 3.9/3.10,优先使用 conda 环境
- 深度学习框架:PyTorch 2.x/1.x,按显卡驱动匹配 CUDA 版本
- 检测模块:建议使用 Ultralytics YOLOv8 或 YOLOv5,它们对环境友好,代码可读性高,也适合新手做实验
- 跟踪模块:可选择 ByteTrack,数据关联逻辑清晰,代码不复杂
- 姿态估计模块:建议使用 MMPose,或在简单 demo 里先使用 YOLO 姿态模型
- 时序动作识别模块:可以使用 MMAction2 中提供的 SlowFast 实现,也可以自己写一个双路径结构
实验项目建议先按下面的结构组织:
video-understanding-demo/ ├── checkpoints/ # 保存检测器、跟踪器与姿态模型权重 ├── configs/ # 实验配置 ├── data/ # 视频数据与标注 ├── outputs/ # 输出结果与可视化 ├── models/ │ ├── detector.py │ ├── tracker.py │ └── pose_estimator.py ├── pipelines/ │ └── action_recognition_pipeline.py └── demo.py4.2 第 1 步:用公开检测模型完成帧级检测
先做一个最简单的小闭环:读视频、对采样帧运行检测模型、打印检测框。这一步是为了建立“原来模型看到的是这类输出”的直觉。
# models/detector.py from ultralytics import YOLO class FrameDetector: """ 帧级目标检测器,负责从视频帧中获取人体边界框。 注意:检测分数阈值要根据场景调优,阈值太高会漏检, 太低会引入大量噪声,影响后续跟踪。 """ def __init__(self, weights_path: str, conf_threshold: float = 0.4): self.model = YOLO(weights_path) self.conf_threshold = conf_threshold def detect_person(self, frame_bgr): results = self.model(frame_bgr, classes=[0]) boxes = [] for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() conf = float(box.conf[0]) if conf >= self.conf_threshold: boxes.append({ "bbox": [int(x1), int(y1), int(x2), int(y2)], "score": conf, }) return boxes在实际使用前,需要先安装 ultralytics 并下载对应模型权重,比如 YOLOv8n 或 YOLOv8s,也可以在命令行中验证安装是否成功:
conda create -n video_demo python=3.10 -y conda activate video_demo pip install ultralytics torch torchvision opencv-python yolo predict source="data/test_video.mp4" model="yolov8n.pt"这里你可以看到“先运行起来一个最小的检测流程”,再进入后置模块开发,而不是一上来就堆代码。
4.3 第 2 步:实现一个多目标跟踪器的最小原型
下面我们用一个简化的 ByteTrack 核心思想,来演示跟踪过程。此代码重点是帮助你理解核心逻辑,实际生产时建议直接阅读并引入 ByteTrack 官方代码。
核心思想:
- 每个跟踪目标被建模为一个轨迹,包含 bbox 和对应的 ID;
- 每一帧先做检测;
- 把当前帧检测框与上一帧跟踪框进行 IoU 匹配;
- 匹配上的目标更新位置,未匹配的检测框诞生新 ID,连续多帧未匹配的轨迹则删除;
- 如果检测分数低,可能需要做二次匹配,从而挽救部分被遮挡目标。
下面的代码只做了第一步的简单逻辑,适合理解主流程:
import numpy as np def compute_iou(box_a, box_b): """ 计算两个边界框的交并比。 box_a 和 box_b 格式为 [x1, y1, x2, y2] """ x1 = max(box_a[0], box_b[0]) y1 = max(box_a[1], box_b[1]) x2 = min(box_a[2], box_b[2]) y2 = min(box_a[3], box_b[3]) inter_area = max(0, x2 - x1) * max(0, y2 - y1) area_a = (box_a[2] - box_a[0]) * (box_a[3] - box_a[1]) area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1]) union_area = area_a + area_b - inter_area return inter_area / union_area if union_area > 0 else 0 class SimpleTrack: def __init__(self, track_id, bbox): self.track_id = track_id self.bbox = bbox self.miss_frames = 0 def update(self, bbox): self.bbox = bbox self.miss_frames = 0 class SimpleTracker: """ 一个仅使用 IoU 匹配的最小多目标跟踪器。 没有卡尔曼滤波,也没有 Re-ID 外观特征,仅适合理解跟踪的过程。 """ def __init__(self, iou_threshold=0.3, max_miss_frames=10): self.tracks = [] self.next_id = 1 self.iou_threshold = iou_threshold self.max_miss_frames = max_miss_frames def update(self, det_boxes): updated_tracks = [] remaining_detections = list(det_boxes) for track in self.tracks: best_idx = -1 best_iou = self.iou_threshold for i, det in enumerate(remaining_detections): iou = compute_iou(track.bbox, det) if iou > best_iou: best_iou = iou best_idx = i if best_idx >= 0: track.update(remaining_detections.pop(best_idx)) else: track.miss_frames += 1 if track.miss_frames <= self.max_miss_frames: updated_tracks.append(track) # 仍未匹配的检测框创建新轨迹 for det in remaining_detections: updated_tracks.append(SimpleTrack(self.next_id, det)) self.next_id += 1 self.tracks = updated_tracks return [ {"id": t.track_id, "bbox": t.bbox, "miss_frames": t.miss_frames} for t in self.tracks ]把上面的检测和跟踪拼接起来,可以逐个读取视频帧并输出带 ID 的轨迹,这就是一个最小可用的多目标跟踪 Demo。后续你可以把它替换成 ByteTrack 官方实现,或引入外观重识别特征来减少 ID Switch。
4.4 第 3 步:2D人体姿态估计的接入方式
在工程里,接入姿态估计最省事的方案是直接使用支持姿态输出的 YOLO 系列模型。不过为了理解原理,这里给出一个“Top-down 姿态估计”的接入思路:
class PoseEstimator: """ 2D人体姿态估计器。 使用前需要将关键点检测模型放到 checkpoints 目录下。 本类代码为接口示意,不同推理框架下 API 会有所不同。 """ def __init__(self, weights_path=None): # 实际接入时可加载 MMPose 或相应 ONNX 模型 self.model = None # 这里只展示流程占位 self.input_size = (192, 256) def estimate_single_person(self, person_crop): """ person_crop: 根据检测框裁剪出的人体图像 返回关键点(x, y, score),数量取决于数据集定义。 COCO 数据集通常为 17 个关键点。 """ # 1. resize 到模型输入尺寸 # 2. 归一化并增加 batch 维度 # 3. 前向推理得到热图 # 4. 对热图取 argmax 或将热图解码成坐标 # 5. 把关键点坐标映射回原图像尺寸 if self.model is not None: keypoints = self.model(person_crop) return keypoints return [] def run_pipeline(self, frame_bgr, person_boxes): pose_results = [] for box in person_boxes: x1, y1, x2, y2 = box["bbox"] crop = frame_bgr[y1:y2, x1:x2] skeleton = self.estimate_single_person(crop) pose_results.append({"bbox": box, "keypoints": skeleton}) return pose_results需要提醒的是,单人姿态估计的输入分辨率会影响关键点精度,尤其对手腕、脚踝这些小目标,直接裁剪并 resize 到 192×256 时很容易丢失尺度信息。在正式实验中,通常需要维护一个“短边对齐 + 中心裁剪”的预处理流程。
4.5 第 4 步:SlowFast 时序建模的基本使用方式
使用开源动作识别库(如 MMAction2)时,SlowFast 的接入通常会经历:数据集注册、配置文件改写、训练/测试。这里不打算贴一份很长且容易过时的配置,而是展示视频片段如何组织成 SlowFast 需要的“双路径输入”。
import random def build_slowfast_clip(frames, alpha=4, slow_num_frames=8): """ frames: 按时间顺序排列的视频帧列表 alpha: Slow 与 Fast 路径的帧率比 slow_num_frames: Slow 路径使用的总帧数 这个函数演示 SlowFast 输入的一个核心想法: 从同一段视频中,按不同采样密度生成两条帧序列。 Slow 路径:均匀抽取 slow_num_frames 帧 Fast 路径:从 Slow 路径的相邻帧之间再插入 alpha-1 帧 """ total_frames = len(frames) slow_indices = [] for i in range(slow_num_frames): frame_idx = int(i * (total_frames / slow_num_frames)) slow_indices.append(min(frame_idx, total_frames - 1)) fast_indices = set() for s_idx in slow_indices: for offset in range(alpha): target = s_idx + offset if 0 <= target < total_frames: fast_indices.add(target) fast_indices = sorted(fast_indices) slow_clip = [frames[i] for i in slow_indices] fast_clip = [frames[i] for i in fast_indices] return slow_clip, fast_clip # 使用示例:加载 32 帧视频,alpha=4 时, # Slow 路径取 8 帧,Fast 路径可能接近 32 帧 # slow_clip, fast_clip = build_slowfast_clip(frames, alpha=4, slow_num_frames=8)真正送入 SlowFast 时,Slow 路径走的主干网络通道数较大,Fast 路径的通道数较窄,这种做法可以在不算太高的计算成本下,有效提升时序动作识别的准确率。
5. 把后置模块串成完整动作识别流水线
为了让理解更直观,我们可以在一个伪流水线类里面把上述模块组合起来。这个类不会直接可运行,但能检验你是否理解模块间的数据流转。
class ActionRecognitionPipeline: """ 视频理解完整流水线: 检测 -> 跟踪 -> 姿态估计 -> 时序建模(SlowFast) """ def __init__(self, detector, tracker, pose_estimator, action_model): self.detector = detector self.tracker = tracker self.pose_estimator = pose_estimator self.action_model = action_model # 存储每个轨迹 ID 对应的历史帧片段,长度由时序窗口决定 self.track_history = {} def process_frame(self, frame_bgr, frame_index): # 1. 检测人体 det_boxes = self.detector.detect_person(frame_bgr) # 2. 跟踪,得到每个检测框的目标 ID track_results = self.tracker.update([b["bbox"] for b in det_boxes]) # 3. 对每个有效轨迹裁剪、估计姿态 for track in track_results: track_id = track["id"] bbox = track["bbox"] # 姿态估计(可选:也可直接用 RGB 裁剪片段送 SlowFast) # keypoints = self.pose_estimator.run_pipeline(frame_bgr, [{"bbox": bbox}]) # 保存历史片段,用于后续时序建模 x1, y1, x2, y2 = bbox crop = frame_bgr[y1:y2, x1:x2] if track_id not in self.track_history: self.track_history[track_id] = [] self.track_history[track_id].append({ "frame_index": frame_index, "crop": crop, "bbox": bbox }) # 4. 当历史帧数达到窗口要求,执行动作识别 if len(self.track_history[track_id]) >= 16: action_label = self.action_model.predict(self.track_history[track_id]) # 输出当前目标的动作类别 print(f"Frame {frame_index}, Track ID {track_id}: {action_label}") # 实际工程中会做滑窗/重叠采样,而不是清空 self.track_history[track_id].pop(0) return track_results这套流水线也是很多小型视频理解项目的雏形。它处理的不是“整段视频是什么动作”,而是“某个人在某个时间段是什么动作”,因此更贴近真实场景。
6. 常见问题与排查思路
对于刚接触这三个模块的新手,下面这些问题是出现频率比较高的:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 跟踪 ID 频繁切换 | 检测框抖动、IoU 阈值不合适、没有使用外观特征 | 调低 IoU 阈值,或引入卡尔曼滤波平滑;必要时使用 Re-ID 特征做二次匹配 |
| 漏检导致跟踪中断 | 检测器置信度阈值过高;目标被遮挡 | 适当降低置信度阈值,结合 ByteTrack 二次匹配策略,保留低分框 |
| 姿态估计关键点跳跃明显 | 检测框不稳定、裁剪区域过小、模型输入分辨率低 | 对检测框做时序平滑,提高姿态模型输入分辨率,或增加关键点时间滤波 |
| 姿态估计多人时顺序混乱 | 没有把姿态结果与跟踪 ID 正确对齐 | 在跟踪结果上裁剪,而不是独立运行姿态检测;使用 bbox IoU 或中心点匹配关键点组 |
| SlowFast 训练 loss 下降但测试差 | 时序窗口不一致、数据增强不足、样本类别不平衡 | 固定时序长度,统一训练测试采样方式,关注类别分布 |
| 视频推理很慢 | 检测器+跟踪+姿态+时序串行执行 | 减少输入帧分辨率、模型轻量化、使用 TensorRT/ONNX 加速,先瓶颈分析再优化 |
| 跟踪框附着到错误目标上 | 两个目标交叉后轨迹被错误分配 | 引入外观特征、运动预测,提高检测器质量,或加入行人重识别模型 |
| 动作识别结果在起始帧噪声大 | 历史帧积累不够,时序窗口未填满 | 设置 Wrapper 机制,在前几帧做好“预热”逻辑,保证分类只发生在窗口完整时 |
排查这类问题不要先改模型,而是先用可视化脚本把中间结果一帧一帧保存下来。很多错误是一眼能看出来的,比如检测框跳来跳去、标签 ID 串到了另一个人身上。可视化永远是视频理解调试的第一工具。
7. 最佳实践与工程建议
7.1 先建立评估标准,再优化组件
多目标跟踪常用的评价指标是 MOTA(多目标跟踪准确率)、IDF1(ID F1 分数)和 HOTA。2D 人体姿态估计常用 OKS(目标关键点相似度)和基于 OKS 的 mAP。SlowFast 则直接看动作分类准确率和时序检测的 mAP/mAR。
在组合系统里,光看单个模块指标往往不够,还需要关注“端到端指标”。比如“动作识别准确率是否受跟踪 ID Switch 影响”“姿态噪声是否让 SlowFast 出现误判”。建议先在验证集上保存一份中间结果,再逐步定位误差来源。
7.2 标注与数据管理是视频任务的重中之重
视频理解项目常常死在数据上,而不是模型上。要注意:
- 标注粒度需要统一,是帧级标签还是时间区间动作标签?
- 目标 ID 是否在跨帧时保持一致?
- 关键点遮挡时标注了哪些点?是否需要标注不可见点?
如果是从公开数据集开始,建议优先选择 MOTChallenge 数据集系列做跟踪、COCO Keypoints 和 MPII 做姿态预训练、Kinetics-Sounds 或 UCF101 做动作识别预训练。不同的数据集的类别体系不同,直接套用会出现标签分布偏差。
7.3 模块组合时要非常小心“信息泄漏”
这是一个科研新常常容易踩的坑:如果在训练动作识别模型时,你的跟踪/姿态模块已经看到了测试集视频,那么结果会虚高。正确做法是固定检测器、跟踪器的权重与参数,在训练集上生成离线中间特征,再将测试集按同样处理方式单独通过。不要在离线特征生成时把整段视频的统计量算进归一化,更不要把测试片段混进训练裁剪样本。
7.4 小模型起步,大模型调优
实际做实验时,很多同学上来就选最大网络。建议刚入门的顺序是:
- 先用 YOLOv8n + 简易 Ian IoU 跟踪把整个代码链路跑通。
- 再把跟踪器升级成 ByteTrack 或 DeepSORT。
- 随后接入姿态估计,在少量样本上做可视化调试。
- 最后再把 SlowFast 的输入阶段换成训练好的骨架或裁剪序列,集中在动作分类任务上做优化。
这样每一步的错误都是可控的,每跑通一步都积累一次正反馈。
7.5 关于时序模型的安全注意事项
如果你想在监控场景、人体动作隐私相关的数据上做实验,要特别注意数据来源授权与脱敏处理。视频数据通常包含人脸、步态、行为习惯等生物特征,在公开平台发布Demo、开源数据集时要注意隐去敏感身份信息。涉及真实生产环境的模型部署,一定要在受控测试环境验证,并在数据采集与标注阶段获得合法授权。
8. 进一步学习路线
从后置模块这个定位来看,视频理解的知识线其实可以拉得很长。
第一阶段,先搞定纯粹的目标检测与跟踪。你要能不看代码手写出 IoU 匹配、卡尔曼滤波预测、匈牙利算法的调用过程。推荐用 MOTChallenge 的公开数据,把 SimpleTracker 扩展成 ByteTrack,观察 MOTA、IDF1 指标随阈值变化。
第二阶段,进入姿态估计。先用现成模型跑通 top-down 流程,再尝试 bottom-up 模型,理解不同方法为什么在不同拥挤程度下表现不同。此时要留意 OKS 的判断逻辑,并亲手写一段关键点坐标的反向映射代码。
第三阶段,真正进入时序建模。建议先从 I3D 看起,理解 3D 卷积如何把空间、时间编码在一起,然后看 SlowFast 如何用双路径拆解时空信息。OpenMMLab 的 MMAction2 提供了统一接口,可以拿 Kinetics 子集或自采视频做一个小规模实验,记录不同时序长度带来的影响。
第四阶段,可以思考“科研增长点”。多目标跟踪结果如何在时序模型中贡献不确定性?姿态热图置信度能不能替代坐标送入时序模型?现有 SlowFast 是不是对大动作更敏感、对细微动作不敏感?这些方向在真实场景中都还有很大的优化空间。
如果你想上手做实验,最好找一段包含多人行走、一人挥手、一人坐下、被遮挡等事件的公开监控视频。先把它完整跑一遍:检测→跟踪→姿态→SlowFast,观察每个模块输出的可视化结果,然后针对出错的那一帧去反查上一级模块的问题。这种“从错误倒推模块”的调试方式,会比闷头调参更有收获。
视频理解并不是某个模型单打独斗的领域,它的核心本事恰恰是组合能力:把检测、跟踪、姿态估计、时序建模这些“单个都能跑”的模块,变成一条能真正理解世界的流水线。而掌握这些后置模块,才是吃透视频理解的开始。