跳过正文
  1. Posts/

视频不等于视频模型:从抽帧 2D CNN 到 Conv3D 的选择逻辑

·4816 字·10 分钟

以前做 CV 内容审核的时候,训练数据里有大量视频。色情视频审核是一个很典型的场景——输入是一段几十秒甚至几分钟的视频,输出是一个二分类:这段视频是否包含色情内容。

但最近回忆起那段经历,我发现一件当时觉得理所当然、现在重新想却很有意思的事:虽然输入是视频,当时实际的做法却是先抽帧,把视频拆成若干张图片,然后按照普通的图片分类问题来做。

也就是说,整套系统从头到尾都没有把视频当作视频来处理。

与此同时,我又记得当时有一种叫 Conv3D 的东西,印象里是专门用来处理视频的。如果有专门的视频模型,为什么实际业务里反而没怎么用?Conv3D 到底在做什么?什么时候才真正需要它?

刚好前几天刚重新过了一遍 Conv2D 的归纳偏置,这次就接着往时间维度上走。

最朴素的方案:把视频退化成图片
#

先看当时实际在用的方案。整个 pipeline 非常直白:

抽帧 + 2D CNN pipeline

拿到一段视频之后,每隔若干帧(或者每隔若干毫秒)抽出一帧图片。每张图片独立送进一个 2D CNN,得到一个 frame-level 的 score。最后对所有帧的 score 做一个 aggregation(比如取 max、top-k 均值、或者简单平均),输出 video-level 的 prediction。

模型实际上完全没有"理解"视频。它只是在独立理解很多张图片。

但在色情内容审核这个场景里,这可能完全够用。原因很简单:如果一段视频里某几帧画面本身就包含了非常强的色情信息,那么只要某一帧的 score 足够高,整个视频的判断就基本确定了。

1frame_10    normal
2frame_50    normal
3frame_90    explicit
4frame_130   explicit

任务只是在问"这个视频是否包含色情内容",不需要知道 frame_50 到 frame_90 之间发生了什么过渡。一帧已经有足够强的判别信号。

用一个极简的近似来帮助理解这种任务结构:

1video_score ≈ max(frame_score)

当然实际系统不会都这么粗暴,但这个近似暴露了一个关键事实:对于某些任务,label 背后的信息几乎完全可以从单帧的静态视觉内容中提取,不依赖帧与帧之间的时间关系。

那问题就来了——什么时候这个近似会彻底失效?

从 Conv2D 到 Conv3D:多了一个时间轴
#

前一篇文章详细讲了 Conv2D 的输入输出形状。标准的 2D 卷积吃一张图片:

1[B, C, H, W]

B 是 batch,C 是 channel(RGB 就是 3),H 和 W 是空间尺寸。

视频和图片的区别在于多了一个时间维度。把连续 T 帧图片堆在一起,就得到一个 video clip:

1[B, C, T, H, W]

T 就是 time / frame 这个轴。

Conv2D 的 kernel 只在空间上滑动:

1K_h × K_w

Conv3D 的 kernel 则同时在时间和空间上滑动:

1K_t × K_h × K_w

比如一个 3 × 3 × 3 的 Conv3D kernel,它在每个位置上覆盖 3 帧时间、3 行高度、3 列宽度的一个小立方体。

Conv2D vs Conv3D kernel 形状对比

我最开始看到 Conv3D 的时候,直觉上以为它是用来处理"三维物体"的——比如医学影像里的 3D 体积数据。后来才意识到,Conv3D 的第三个维度通常就是时间。它的直觉不是"在三维空间里做卷积",而是"同时在时间轴和空间轴上做局部卷积"。

Conv3D 算时序建模吗
#

知道了 Conv3D 在时间轴上也有卷积窗口之后,我产生的下一个疑问是:这算时序建模吗?

我对时序建模的第一反应是 RNN。RNN 的时间信息是通过 recurrent state 一步一步向后传递的:

$$ h_t = f(x_t, h_{t-1}) $$

每个 time step 的输出依赖前一步的隐藏状态。信息沿着时间轴逐步流动。

Conv3D 处理时间的方式完全不同。它是在时间轴上做 sliding window:

1[t0 t1 t2]
2   [t1 t2 t3]
3      [t2 t3 t4]

每个窗口看 \(K_t\) 帧的局部邻域,用一组可学习的权重做卷积。和 RNN 的逐步递推相比,它更像是在时间轴上跑一个局部特征检测器。

所以两者可以这样区分:

1RNN   → recurrent temporal modeling
2Conv3D → convolutional / local temporal modeling

Conv3D 确实在建模时间,但方式和 RNN 完全不同——它是局部的、并行的,而不是逐步递推的。

这里我觉得有一个很容易产生的误解需要拆掉:“时序建模"是一类问题,不是一种特定的网络结构。RNN、Conv3D、Transformer 用的机制天差地别,但只要网络能利用时间维度上的信息来辅助预测,就算是在做时序建模。不要因为见 RNN 见得多,就把"序列模型"和"recurrent"画等号。

一个直观的例子:手在移动
#

为了把 Conv3D 的时间建模能力说得更具体,举一个最简单的例子。

假设连续三帧画面里有一只手:

1t0        t1        t2
2
3  ●          ●          ●
4
5x=10     x=15     x=20

如果对每一帧独立做 Conv2D,模型在每一帧上都可以识别出"这里有一只手”。但它不知道这只手正在向右移动。因为 Conv2D 看到的是三张彼此无关的图片,每张图片里手出现在不同位置,但模型没有任何机制把三帧串起来。

Conv3D 则不同。它的 kernel 同时覆盖了时间和空间,在一个局部时间窗口里能看到:

1前一帧:左侧出现 pattern
2当前帧:中间出现 pattern
3下一帧:右侧出现 pattern

这个空间位置随时间的系统性变化,就是一个 motion pattern。Conv3D 可以通过学习 kernel 权重来检测这种模式——本质上和 Conv2D 学习 spatial edge 是一回事,只不过这里检测的"边缘"是跨时间的。

Temporal Receptive Field
#

Conv2D 有一个经典概念:感受野(receptive field)。网络越深,单个输出点在原始输入图像上的有效覆盖范围越大。

Conv3D 的感受野也类似,只是同时在两个维度上增长:

1更深的网络
2→ 更大的 spatial receptive field
3+
4更大的 temporal receptive field

低层 Conv3D 能检测到的,可能只是非常短暂的局部变化——比如一个像素在两帧之间的位移,或者一小块纹理的闪烁。中层开始能组合出更长时间跨度的动作片段。到了高层,理论上可以捕获更复杂的行为模式。

1低层:edge / texture / 相邻帧之间的短 motion
2中层:局部动作片段(手抬起、头转动)
3高层:更复杂的行为 pattern(走路、挥手)

这就是所谓的 spatiotemporal feature learning——空间和时间上的特征同步地层级组合。

不过有一点要明确:Conv3D 的 temporal receptive field 增长仍然受制于 kernel 大小和网络深度。 它并不意味着模型天然就能理解几分钟的长视频上下文。一段 16 帧的 clip 加上几层 Conv3D,能捕获的时间范围可能也就几秒。长视频理解是另一个量级的问题。

为什么不把 16 帧拼成 48 个 channel
#

到这里,我曾经想过一个问题:如果一个 video clip 有 16 帧 RGB 图像,那不就是 16 × 3 = 48 个 channel 吗?直接把它当成一张 48 通道的"图片",用 Conv2D 不行吗?

数学上当然可以。输入变成:

1[B, 48, H, W]

然后正常走 Conv2D。

但问题出在第一层卷积之后。

Conv2D 的第一层会把 48 个 input channel 做线性组合,输出 \(C_{out}\) 个 channel:

1[B, 48, H, W]  →  Conv2D  →  [B, C_out, H', W']

经过这一层之后,时间结构已经被折叠进了 channel mixing。后续的所有层看到的都是一个普通的二维特征图,时间轴消失了。后续层没有办法继续在"第 3 帧和第 5 帧之间"做局部时间卷积,因为这个维度已经不存在了。

方案 A:帧拼通道 + Conv2D(时间轴在首层被彻底折叠消失)

Conv3D 不同:

1[B, C, T, H, W]  →  Conv3D  →  [B, C', T', H', W']

输出里 T 维度仍然保留着。后续层可以继续在时间轴上做卷积,逐层组合更长的 temporal pattern。

方案 B:Conv3D(时间轴 T 逐层显式保留,支持深层时序特征组合)

这个区别的本质是:Conv3D 把"时间"作为一个显式的结构维度保留在整个网络的计算图中,而不是在第一层就把它压扁。

为什么 Conv3D 在业务里反而没那么常见
#

搞清楚 Conv3D 的能力之后,回到最开始的问题:如果 Conv3D 确实能建模时间,为什么我印象中做内容审核时反而很少用它?

这个问题的答案不是简单一句"因为太贵"。

很多 label 根本不需要 temporal information
#

做内容审核时,常见的检测目标包括:色情内容、logo、二维码、人脸、特定物体、敏感场景。

仔细想想这些 label 的定义——色情内容的判别信息几乎完全在单帧画面里。一张截图已经足够让人判断,不需要看前后帧。logo 和二维码更不用说,它们就是静态的视觉模式。人脸检测本质上是空间结构识别。

这些任务更接近的是:

1appearance recognition

而不是:

1motion understanding

Conv3D 带来的 temporal modeling 能力,对于这类任务的增量可能非常有限。label 本身不依赖时间信息,模型能从时间轴中学到的额外信号就很微弱。

Conv3D 确实很贵
#

从 ML Systems 和 Serving 的角度看,Conv3D 的成本不只是"多一个维度"那么简单。

Conv2D 一次前向处理的输入是 \(C \times H \times W\)。Conv3D 是 \(C \times T \times H \times W\)。T 通常是 16 或 32 帧。这直接意味着:

  • FLOPs 大幅增加。
  • activation memory 随 T 线性增长,导致 batch size 被迫降低。
  • memory bandwidth 压力增大。
  • 训练的 throughput 下降。
  • 线上 serving 的每次推理成本更高。

而且视频处理还有一个图片场景不存在的前置成本——视频解码:

1H264 / H265
23decode 16~32 frames
45resize + crop
67assemble clip tensor
89Conv3D forward

对比抽帧方案:

1video
23稀疏抽帧(每秒 1~2 帧)
45image batch
672D CNN forward(可以复用已有的图片分类模型和 serving 基础设施)

后者在工程上更加友好。图片分类模型的 serving 基础设施是现成的,单帧推理的 latency 可控,还可以根据业务需求灵活调整抽帧密度。

视频存在大量 temporal redundancy
#

这一点我觉得是最容易被忽视的。

一段日常视频的连续几十帧中,绝大多数 spatial information 几乎不变:

1这是一个人         → 每帧都一样
2背景是一间房       → 每帧都一样
3衣服是蓝色         → 每帧都一样
4桌上有一杯咖啡     → 每帧都一样

快速变化的通常只有:

1手的位置
2身体姿态
3运动方向和速度

也就是说,视频的帧间冗余(temporal redundancy)非常高。暴力地对每一帧都用完整的 spatiotemporal model 做特征提取,实际上在重复计算大量几乎相同的 spatial features。

这就引出一个很自然的工程问题:temporal information 确实有价值,但有没有必要为每一帧都支付完整的 spatial feature extraction 成本?

后来视频理解领域的很多架构演进,本质上都在尝试更高效地利用时间信息。比如 SlowFast Networks 的思路:用两条并行的路径处理视频:

1Slow pathway → 低帧率,捕获 semantic / appearance
2Fast pathway → 高帧率,轻量级网络,专注捕获 motion

Slow pathway 用很少的帧(比如每隔 16 帧取一帧)做重型的 spatial 特征提取;Fast pathway 用高帧率但很窄的通道数来追踪运动信息。两条路径各有分工,最后融合。

这里不展开 SlowFast 的细节,只是用它来说明一件事:降低 temporal modeling 的成本,是视频架构后续演进的核心驱动力之一。 这反过来也佐证了 naive Conv3D 在工程上确实不便宜。

什么任务才真正需要 temporal modeling
#

回到选择的问题。什么时候抽帧 + 2D CNN 就够了,什么时候必须做 temporal modeling?

我发现有一个非常简单的判断标准:看 label 描述的是"状态"还是"状态变化"。

第一类任务,label 描述的是某种静态状态或视觉属性:

1色情内容    → 画面里有没有色情元素
2logo        → 画面里有没有某个 logo
3二维码      → 画面里有没有二维码
4人脸        → 画面里有没有人脸
5场景分类    → 画面是室内还是室外

随机截一帧,大概率就能获得足够的判别信息。这些 label 对应的是 appearance,不依赖时间。

第二类任务,label 描述的是状态的变化过程:

1挥手   vs  手一直举着
2坐下   vs  已经坐着
3摔倒   vs  原本就躺着
4打开门 vs  门本来就是开的
5投篮   vs  拿着篮球站着
状态识别 vs 状态变化识别

单看一帧,“正在坐下"和"已经坐着"可能画面几乎一样——都是一个人在椅子上的姿态。单看一帧,“正在摔倒"和"躺在地上"也可能看起来差不多。要区分它们,必须看到:

1before → during → after

“坐下"的定义本身就包含了时间维度上的变化:从站着到坐着的过渡过程。没有时间信息,这个 label 在逻辑上就无法被定义。

这就是 temporal modeling 真正不可替代的场景。

写在最后
#

回头看这整条线索,从"为什么做审核用抽帧"到"Conv3D 是什么"再到"为什么业务里不常用”,我觉得最值得记住的不是 Conv3D 的 kernel shape 或者某个具体架构的参数配置,而是一个关于模型选择的基本判断框架:

不要根据输入数据的模态直接选择模型。

拿到视频数据,第一反应不应该是"输入是视频,所以我要用视频模型”。真正应该问的是:为了预测这个 label,模型需要利用输入中的哪些 statistical structure?

如果 label 基本由某一帧的静态视觉信息决定,那么抽帧 + 2D CNN 很可能已经是一个完全合理的 production solution。工程简单、成本可控、效果够用。

如果 label 的定义本身依赖 before → during → after 的时间变化过程,那么 temporal modeling 才是真正不可替代的——不管这个 temporal modeling 是用 Conv3D、RNN、还是 Transformer 来实现。

模型结构应该匹配的,不是输入数据的格式,而是 label 对输入信息的依赖结构。

参考资料
#

  • MIT 6.S191 Lecture 3: Convolutional Neural Networks https://introtodeeplearning.com
  • Tran, D. et al., “Learning Spatiotemporal Features with 3D Convolutional Networks,” ICCV 2015 (C3D)
  • Feichtenhofer, C. et al., “SlowFast Networks for Video Recognition,” ICCV 2019

相关文章

9 年后重温 CNN:剥掉算子细节后,真正留下了什么

最近在重新过 MIT 6.S191 Lecture 3(卷积神经网络)。2017 年刚接触 CV 那会儿,CNN 算是吃饭的家伙,每天都在调。后来精力逐渐转到 ML Infra,成天跟 GPU 显存、通信拓扑和算子优化打交道,卷积网络的很多具体细节就慢慢生疏了——写个 nn.Conv2d 时 weight 的四维形状到底怎么排、Kaiming 初始化的方差怎么推、感受野怎么算,冷不丁被问到,还得在脑子里卡壳一下。