状态:草稿 (2026-10-16),部分实测。 球模型(BlurBall)在各个输入大小下的延迟是通过家庭实验室的 8GB 笔记本级单 GPU 以单次运行 Pod 测得的(预热 20 次后 50 次 × 5 组的中位数)。跳过帧和 ROI 是对笔记本 CPU 合成轨迹的模拟(
code/05_skip_frames.py, 1秒上下)。实际乒乓球视频中的跳帧准确度尚未实测。 系列「阅读视觉模型 3」第 5 篇。光流的原理在 阅读视觉模型 2 的第 5 篇中,球追踪模型的训练在同系列的 第 9 篇中。
目录
1. 直觉:相邻帧几乎相同
在 30fps 视频中,相邻两帧相差 33ms。在此期间,背景保持不变,人移动了几个像素。然而,如果每帧都从头运行繁重的模型,几乎是在重复进行 30 次相同的计算。本文中的所有方法都旨在减少这种冗余。
| 减少的内容 | 方法 | 代表 |
|---|---|---|
| 维度(帧数) | 偶尔检测,间隙追踪 | SORT, MediaPipe, BlazePose |
| 维度(特征) | 仅从关键帧提取特征并复用 | Deep Feature Flow |
| 空间(分辨率) | 按场景选择输入大小 | AdaScale |
| 空间(区域) | 仅关注预测位置周围 | 裁剪 ROI |
2. 偶尔检测,间隙追踪
Bewley 等人的 SORT (2016) 接收每帧的检测结果,利用 匀速卡尔曼滤波预测下一个位置,并通过 IoU(匈牙利算法)将预测框与新的检测框进行匹配。在 2016 年左右,追踪部分在单个 CPU 核心上以 260Hz 运行(第 4.3 节,据推测不包含检测时间)。在相同的追踪器中仅更换检测器,MOT 准确度(MOTA)就从 15.1 提高到了 34.0(表 1)。这意味着追踪的质量最终由检测决定,而追踪器本身几乎是免费的。
利用这“几乎是免费”的特性,我们可以 偶尔 进行检测。MediaPipe 的姿态追踪仅在第一帧和追踪丢失人体时运行检测器,其余帧则根据上一帧的关节位置裁剪出人体周围并仅运行关节模型(官方文档)。BlazePose (2020) 通过这种方式在 2017 年手机的单个 CPU 核心上使用 Lite 模型达到了约 30 FPS(表 1)。
卡尔曼滤波的预测步骤只有一行。
상태 x = [위치, 속도], 다음 예측 x' = F·x, F = [[1, Δt], [0, 1]] (등속 가정)
검출이 오면 예측과 검출을 불확실성 비율로 섞어 고친다(갱신 단계)
亲自尝试:在乒乓球中跳帧
匀速假设在球 改变方向的瞬间 会失效。乒乓球大约每 0.5 秒在球台上弹跳一次或被球拍击中一次。我们构建了一个 512×288 画面、包含重力·弹跳·击球的合成拉力赛(60fps,1,161 帧,29 次弹跳·40 次击球),每 k 帧运行一次检测器(位置噪声 σ=1px),并用最后两次检测的匀速预测填补间隙。
| k(检测间隔) | 检测成本 | 位置误差 中位数 · p95 | 弹跳·击球前后 3 帧的中位数 · p95 | 偏离超过 10px 的帧 |
|---|---|---|---|---|
| 1 | 100% | 1.2 · 2.5px | 1.1 · 2.3px | 0.0% |
| 2 | 50% | 1.5 · 5.4px | 1.7 · 20.1px | 3.8% |
| 3 | 33% | 1.9 · 29.3px | 2.2 · 46.8px | 9.6% |
| 4 | 25% | 2.4 · 48.7px | 3.3 · 75.7px | 15.7% |
| 6 | 17% | 5.8 · 92.5px | 7.2 · 83.6px | 34.0% |
仅看中位数的话,即使每 3 帧检测一次,误差也在 2px 左右,看起来还不错。但长尾(p95)在弹跳和击球前后达到了 46.8px,超过了球直径(在 512 宽度下约 5px)的九倍。 误差偏偏集中在我们最关心的事件(弹跳)周围。 这就是为什么在人体姿态等平滑运动对象中行之有效的“偶尔检测”,很难直接应用於比分判定依赖于弹跳的乒乓球。
3. 关键帧:复用特征
Zhu 等人的 Deep Feature Flow (2017) 将跳过的内容单位从“检测结果”改为了“深度特征”。繁重的特征网络(ResNet-101)仅在关键帧上运行,中间帧则由轻量光流网络计算出的流动方向对关键帧特征进行变形(warp),然后只运行轻量头部。全过程端到端共同训练。
| 任务(论文表 4) | 关键帧间隔 | 每帧 | Deep Feature Flow |
|---|---|---|---|
| ImageNet VID 视频检测 | 10 | mAP 73.9, 4.05 fps | mAP 73.1, 20.25 fps(5.0倍) |
| Cityscapes 分割 | 5 | mIoU 71.1, 1.52 fps | mIoU 69.2, 5.60 fps(3.7倍) |
(2013 年单张数据中心 GPU。)牺牲 1–2 点准确度,换来了 3.7–5 倍的提速。如果使用更轻量的光流网络,速度最高可达约 10 倍(41.26 fps),但 mAP 下降到了 69.5(第 5.2 节)。由于光流假设“在单帧期间移动很小”,因此它不适用于在一帧内飞过数倍直径的球(见下文第 4 节的表格)。
4. 分辨率:缩减输入
计算量与像素数量成正比,即宽×高。将宽度减半,计算量就会变为四分之一。但代价是较小的物体会变得更小。假设乒乓球台长 2.74m 占画面宽度的 70%,我们计算了球(40mm)的大小以及在一帧内的移动距离(球速 10m/s)。
| 画面宽度 | 球直径 | 单帧移动距离(60fps / 30fps) | 像素数(相比 512×288) |
|---|---|---|---|
| 1920 | 19.6px | 81.8 / 163.5px | 14.1倍 |
| 1280 | 13.1px | 54.5 / 109.0px | 6.3倍 |
| 768 | 7.8px | 32.7 / 65.4px | 2.3倍 |
| 512 | 5.2px | 21.8 / 43.6px | 1 |
| 256 | 2.6px | 10.9 / 21.8px | 0.25倍 |
无论在哪种分辨率下,球在一帧内都会移动 其直径的 4.2 倍(60fps)至 8.3 倍(30fps) 。因此,球模型不会只看单帧,而是同时观察多张帧,甚至将模糊痕迹作为线索(同系列 2 的第 9 篇)。
我们的实测:四种输入大小的球模型
我们在实验 GPU 上仅改变输入大小,对 TT Score 的球模型(BlurBall,3 帧窗口,149 万个参数)进行了测试。由于该模型是全卷积的,因此可以在保持权重不变的情况下更改输入大小(由于是在 512×288 下训练的,因此其他大小的 准确度未经验证)。
| 输入 | FP32 | FP16 | 像素(相比 512×288) | 时间(相比 512×288,FP32) |
|---|---|---|---|---|
| 256×144 | 4.07ms | 4.69ms | 0.25倍 | 0.27倍 |
| 512×288 | 15.09ms | 9.77ms | 1 | 1 |
| 768×432 | 41.90ms | 24.10ms | 2.25倍 | 2.78倍 |
| 1024×576 | 82.24ms | 49.30ms | 4倍 | 5.45倍 |
(批大小 1,单个窗口,FP16 使用 autocast。预热 20 次后 250 次的中位数,五组的中位数彼此在 0.1ms 以内相同。)
可以看出两点。
- 大尺寸的变慢速度超过了像素增加的速度。 1024×576 的像素是 4 倍,但时间是 5.45 倍。由于该模型一路保持分辨率并携带数十个通道的特征图,当输入变大时,中间张量似乎无法放入 GPU 缓存中(原因为推测,未进行性能分析实测)。
- 小尺寸下 FP16 反而更慢(4.07 对比 4.69ms)。当计算量太少,以至于 GPU 等待工作的时间(调用内核、转换格式)占了大部分时间时,降低精度就毫无意义了。我们将在第 3 篇和第 6 篇中再次探讨这一点。
如果 30fps 的预算(33ms)仅用于推理,甚至可以容纳 768×432 的 FP16(24ms)。但正如第 1 篇中所看到的,预算中必须包含预处理、传输、后处理以及其他会话的份额,因此 512×288(FP16 9.8ms)是一个留有余量的大小。该数值也是球模型的训练分辨率。
按场景选择大小:AdaScale
Chin 等人的 AdaScale (2019) 在检测器特征之上附加了一个小型回归器,以预测 下一帧要使用的输入大小(短边 600、480、360、240)。只有大物体的场景可以输入较小的尺寸,甚至可以减少误报并提高准确度。在 ImageNet VID 上,速度提升了 1.6 倍,同时 mAP 从 74.2 提高到了 75.5(R-FCN,2017 年消费者高端 GPU,75 → 47ms)。回归器本身大约为 2ms。在乒乓球中,由于球一直很小,很难直接照搬这种想法,但它可以用于“当前场景是看不到球的休息时间,所以用小尺寸、低频率”的形式(未实测)。
5. ROI:仅预测位置周围
如果追踪预测了下一个位置,我们就可以只将该位置周围的小窗口输入模型。如果不是整个画面,而是 96×96 的窗口,像素占比仅为 6%。但代价是,如果预测出错导致球在窗口之外,就会丢失目标,此时必须重新查看整个画面。在第 2 节的合成拉力赛中,我们在每帧检测(σ=1px)的同时,使用前两个估计值预测了窗口位置。
| 窗口 | 像素占比 | 球在窗口外(检测失败 0%) | 球在窗口外(检测失败 10%) | 其中弹跳和击球前后(失败 10%) |
|---|---|---|---|---|
| 32×32 | 0.7% | 3.71% | 4.66% | 11.95% |
| 64×64 | 2.8% | 2.50% | 2.85% | 7.36% |
| 96×96 | 6.2% | 0.35% | 0.78% | 1.84% |
| 128×128 | 11.1% | 0.35% | 0.60% | 1.61% |
球飞出窗口的瞬间同样集中在方向改变的瞬间。由于这种模拟没有遮挡、发球开始(球突然出现在手中)以及相机抖动,实际应用中可能会更频繁地出错(未实测)。此外,像 BlurBall 这样同时观察多帧的模型,如果裁剪窗口,也必须裁剪前后帧的相同位置,因此如果窗口跟随球移动,“同一位置的变化”这一线索就会变模糊。
6. 局限性
- 第 2 节和第 5 节使用的是合成轨迹。其中并未包含真实视频中的遮挡、模糊,以及球飞出画面外又进入的情况。
- 第 4 节中按输入大小进行的测量仅测量了时间。我们并未测量小球模型在不同于训练大小时的准确度。
- 论文中的数值是各自基于 GPU 和数据得出的,因此倍数之间无法直接对比。
7. 与我们项目的联系
- TT Score 会在每一帧中运行小球模型。 正如第 2 节的表格所示,如果跳过帧,就会在边界周围出错。因此,我们将开销较小的那部分设为「偶尔」运行。寻找球台在比赛开始时进行一次(EfficientTAM,第 4 篇),之后每一帧都通过光流进行追踪,仅在发生剧烈晃动时重新寻找。即使每一帧都进行光流和剪辑判断,每帧耗时也约为 1.7ms(TT Score 性能文档的实际测量,安静节点)。
- 输入大小为 512×288。 手机会将相机画面缩小到这个尺寸再发送。正如第 4 节的表格所示,如果比这更大,就会迅速超出预算;如果比这更小,小球就会变得小于 3px。对于竖着拿的手机,如果调整到这个尺寸,屏幕宽度会缩减至 162px,小球几乎会消失(性能文档中各条件下的测试显示小球可见率为 5%)。这就是拍摄指南建议横屏拍摄的原因。
- 我们目前还没有使用 ROI。必须先权衡丢失小球后重新寻找的成本,以及裁剪窗口会导致多帧线索模糊的问题(未实际测量)。
下一篇文章将探讨当多个人共用一个 GPU 时,批处理(Batching)会对延迟和吞吐量产生怎样的影响。
TT 寄语。 小球在一帧内可以飞过自身四倍的距离,并且总是在弹起的瞬间改变方向。所以我的眼睛一刻也没有闭上——只是偶尔看一眼球台!
参考文献
- Bewley, A. et al. (2016). Simple Online and Realtime Tracking. ICIP 2016. https://arxiv.org/abs/1602.00763
- Bazarevsky, V. et al. (2020). BlazePose: On-device Real-time Body Pose tracking. CVPR Workshop. https://arxiv.org/abs/2006.10204
- MediaPipe Pose 文档(旧版解决方案)。 https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/pose.md
- Zhu, X. et al. (2017). Deep Feature Flow for Video Recognition. CVPR 2017. https://arxiv.org/abs/1611.07715
- Chin, T.-W., Ding, R., Marculescu, D. (2019). AdaScale: Towards Real-time Video Object Detection Using Adaptive Scaling. SysML 2019. https://arxiv.org/abs/1902.02910
- 我们的测量:本系列实测(原始数据见代码库外的研究记录),TT Score 性能文档第 2.12 和 2.14 节
- 示例代码与输出:此文件夹中的
code/05_skip_frames.py,code/05_skip_frames.out.txt