帧间预测利用时间上的相似性。视频序列中,相邻的帧通常内容非常相似,只是发生了微小的移动。帧间预测就是利用 时间相关性 来消除数据冗余。

其基本原理是:不再编码完整的当前帧,而是将其与已经编码好的参考帧(通常是前一帧或后一帧)进行比较,找到当前帧中某个区域在参考帧中的最佳匹配块。然后,只需编码这个位置偏移信息(运动矢量)和两者之间的细微差异(残差)。

核心思想:运动估计与运动补偿,帧间预测包含两个基本步骤:

  1. 运动估计: 对于当前要编码的块,在已经编码好的一个或多个参考帧(Reference Frames)的某个搜索窗口内,寻找一个与之最匹配的块。这个寻找的过程就是运动估计。
  2. 运动补偿 找到了最佳匹配块后,我们就能得到一个指向该匹配块的指针,这个指针就是运动矢量。MV描述了当前块相对于参考块的位置偏移。编码器只需将这个MV和两者之间的残差进行编码。解码器端则根据收到的MV,从参考帧中取出对应的预测块,再加上解码后的残差,就还原出当前块。这个根据MV生成预测块的过程就是运动补偿。

编码器传输的是 MVResidual。由于MV+Residual的数据量远小于原始块的数据量,压缩得以实现。

实际不会传输MV,而是MVD,及MV与MVP的差值。

块划分

帧间预测中,一个16x16的宏块可以有以下几种划分模式:

  • 16x16: 整个宏块使用一个MV。适用于大面积、运动单一的区域。
  • 16x8: 上下两个16x8的块,各有一个MV。
  • 8x16: 左右两个8x16的块,各有一个MV。
  • 8x8: 宏块划分为4个8x8的子块。每个8x8子块还可以进一步划分为更小的尺寸:
    • 8x8: 每个8x8子块有一个MV。
    • 8x4: 上下两个8x4的块,各有一个MV。
    • 4x8: 左右两个4x8的块,各有一个MV。
    • 4x4: 4个4x4的块,各有一个MV。
      Mode 1: 16x16             Mode 2 & 3: 16x8 & 8x16
   +-----------------+        +-----------------+ +---------------+
   |                 |        |      16x8       | |      |      |
   |      16x16      |        +-----------------+ |      |      |
   |       (1 MV)    |        |      16x8       | | 8x16 | 8x16 |
   |                 |        |      (2 MVs)    | |      |      |
   +-----------------+        +-----------------+ +---------------+

      Mode 4: 8x8 and its sub-partitions
   +-------+-------+
   |  8x8  |  8x8  |          For each 8x8 sub-block, it can be further
   | (Sub) | (Sub) |          partitioned into 8x8, 8x4, 4x8, or 4x4.
   +-------+-------+          This provides maximum flexibility for
   |  8x8  |  8x8  |          complex motion.
   | (Sub) | (Sub) |          (Total MVs can range from 4 to 16)
   +-------+-------+

编码器会尝试所有这些划分方式,通过RDO选择最优的一种。

高精度运动矢量

物体的运动很少是完美的整数像素移动。为了更精确地进行预测,H.264支持亚像素精度的运动矢量,包括

  • 亮度块:

    • 半像素 (1/2 pixel)

    • 四分之一像素 (1/4 pixel)

  • 色度块:

    • 八分之一像素 (1/8 pixel)

这意味着参考块可以位于参考帧的非整数像素位置上。解码器和编码器需要通过插值 (Interpolation) 来生成这些亚像素位置的像素值。

  • 亮度块半像素插值:
    • 通过对周围的整数像素进行6抽头FIR滤波器(6-tap FIR filter)来生成。
    • 例如,水平半像素点 b 是通过对其左右各3个整数像素 (A, B, C, D, E, F) 进行滤波计算得到的: b = (A - 5B + 20C + 20D - 5E + F + 16) >> 5
    • 对角线上的半像素点(例如,水平和垂直都偏移半像素)则是在垂直(或水平)方向上,对已经计算出的水平(或垂直)半像素点进行同样的滤波操作。
  • 亮度块四分之一像素插值:
    • 在计算出半像素值后,四分之一像素点通过对周围的整数像素点和半像素点进行简单的线性插值(平均值)来得到。
    • 例如,一个1/4像素点的值是其相邻的整数像素点和半像素点的平均值。
  • 色度块八分之一像素插值:
    • 通过相邻的4个整数像素双线性内插得到。

亚像素技术能显著减少预测残差。

MVP

编码器并不直接编码MV,而是编码MV与MVP之间的差值——MVD (Motion Vector Difference)。由于相邻块运动的高度相关性,MVD通常很小,从而能被高效地熵编码。

\[\text{MVD} = \text{MV} - \text{MVP}\]

解码器则执行逆过程,从码流中解码出MVD,然后计算出与编码器完全一样的MVP,两者相加得到最终的MV。

\[\text{MV} = \text{MVD} + \text{MVP}\]

因此,编解码器必须保证MVP的计算方法完全一致。

中值预测

无论是P帧还是B帧,MVP的基本计算逻辑都源于空间相邻块的运动矢量。H.264标准选择了三个候选位置:左侧(A)、上方(B)和右上/左上(C)。

      D' B  C
      A  P
  • P: 当前需要计算MVP的N_x_N分区。
  • A: 紧邻P左上角像素左侧的那个分区。
  • B: 紧邻P左上角像素上方的那个分区。
  • C: 紧邻P右上角像素上方的那个分区(右上邻居)。
  • D’: 紧邻P左上角像素左上方的那个分区(左上邻居,作为C的备选)。

核心算法:MVP是邻居A, B, C的运动矢量的分量中值 (component-wise median)。 即:

  • MVP.x = median(MV_A.x, MV_B.x, MV_C.x)

  • MVP.y = median(MV_A.y, MV_B.y, MV_C.y)

这里的复杂性在于如何确定 A, B, C 的可用性以及当它们不可用时的处理规则。

候选MV的选取与可用性判断

在获取 A, B, C 的MV之前,必须遵循以下规则,这些规则是所有细节的核心:

  1. 参考帧必须一致:这是最重要的规则。一个邻居块的MV只有在它指向与当前块相同的参考帧 (ref_idx) 时,才能作为候选MV。如果邻居块的 ref_idx 不同,它的MV对于当前预测是无意义的。
  2. 分区边界确定
    • 邻居A取自P左边界的p(-1, 0)所在的块。
    • 邻居B取自P上边界的p(0, -1)所在的块。
    • 邻居C取自P上边界的p(width-1, -1)所在的块。
  3. 可用性判断:如果一个邻居块满足以下任一条件,则认为其不可用
    • 它位于当前Slice之外(例如,当前块在图像或Slice的左/上边界)。
    • 它是一个帧内编码 (Intra-coded) 的块(它没有MV)。
  4. 候选替换规则
    • 如果候选C不可用,则尝试使用候选D’(左上邻居,位于p(-1, -1))。
    • 如果B或C(或其备选D’)与A属于同一个宏块,且当前宏块的划分方式为16x8,且当前分区是下方的16x8分区,则B和C的MV会被特殊处理(通常取上方16x8分区的MV)。这是为了处理宏块内部的预测依赖。
  5. 只有一个候选可用:如果A, B, C中只有一个可用,那么MVP就直接等于那个可用的MV,不再进行中值计算。

P帧的MVP计算

P帧的逻辑相对简单,因为它只有一个参考列表 List 0。

对于当前分区(假设其参考帧为 ref_idx_p):

  1. 检查左邻居A
    • 如果A可用,且其 ref_idx 也等于 ref_idx_p,则 MV_A 就是A的MV。否则 MV_A 不存在。
  2. 检查上邻居B
    • 如果B可用,且其 ref_idx 也等于 ref_idx_p,则 MV_B 就是B的MV。否则 MV_B 不存在。
  3. 检查右上邻居C(以及其备选D’):
    • 同样的逻辑,如果C(或D’)可用且 ref_idx 匹配,则 MV_C 就是C(或D’)的MV。否则 MV_C 不存在。
  4. 计算中值
    • 将所有可用的MV(MV_A, MV_B, MV_C)收集起来。
    • 如果存在两个或三个可用的MV,则计算它们的分量中值得到最终的MVP。
    • 如果只存在一个可用的MV,则MVP就等于它。
    • 如果一个可用的MV都没有,则MVP为(0,0)。

特殊情况:Skip Mode的MVP P帧的Skip宏块(16x16)的MVP计算逻辑与上述类似,但如果A或B有一个不可用,或者它们的MV或ref_idx为零,MVP就直接是(0,0)。否则,正常进行中值计算。


B帧的MVP计算细节

B帧因为有 List 0 和 List 1 两个列表,情况变得复杂。MVP的计算必须针对每个列表独立进行

单向预测

  • 当一个B帧分区采用L0预测时:其MVP的计算过程与P帧完全相同。在检查邻居A, B, C时,只会考虑那些也采用L0预测(且ref_idx_L0匹配)或双向预测的邻居,并使用它们的L0运动矢量 MV_L0 作为候选。一个只采用L1预测的邻居对于L0预测是无效的。
  • 当一个B帧分区采用L1预测时:其MVP的计算过程也与P帧相同,但所有检查都是针对 List 1 的。即,检查邻居的 ref_idx_L1 是否匹配,并使用它们的L1运动矢量 MV_L1 作为候选。

双向预测

当一个B帧分区采用双向预测时,它需要计算两个MVP:一个用于List 0 (MVP_L0),一个用于List 1 (MVP_L1)。

  • MVP_L0的计算:
    • 遵循与P帧完全相同的逻辑,但只从邻居块中提取MV_L0作为候选(邻居可以是L0预测或双向预测)。
  • MVP_L1的计算:
    • 遵循与P帧完全相同的逻辑,但只从邻居块中提取MV_L1作为候选(邻居可以是L1预测或双向预测)。

解码器会分别解码出 MVD_L0MVD_L1,然后计算: MV_L0 = MVD_L0 + MVP_L0 MV_L1 = MVD_L1 + MVP_L1

3. 直接模式 (Direct Mode)

Direct模式是一个特例,它的MV不是通过解码MVD得到的,而是完全由解码器推导,因此它自身不使用MVP。但它的推导过程又分为两种:

  • 时间直接模式 (Temporal Direct)
    • 完全不使用空间邻居A, B, C。
    • MV_L0MV_L1是通过缩放其后向参考帧(List1[0])中同位块的MV得到的。这个过程与我们讨论的空间MVP预测无关。
  • 空间直接模式 (Spatial Direct)
    • 这种模式会使用我们讨论的MVP计算逻辑
    • 它的MV_L0MV_L1直接等于通过邻居A, B, C计算出的MVP_L0MVP_L1
    • 也就是说,在这种模式下,MVD被认为是(0,0),解码器计算出MVP_L0MVP_L1后,就直接将它们作为最终的MV_L0MV_L1

P帧预测

P帧只参考在它显示顺序之前的I帧或P帧来进行预测。

参考帧列表

P帧只有一个参考帧列表,称为 List 0。这个列表里存放的是已经编码完成、可供参考的过去的帧。

P帧的宏块预测模式

对于P帧中的每一个宏块,编码器会通过率失真优化(RDO)来决定使用以下哪种模式:

  • 帧间预测模式 (Inter Modes): 这是P帧的主要模式。
    • 编码器会尝试所有合法的块划分(16x16, 16x8, 8x16, 8x8及其子划分)。
    • 对于每个划分出的子块,编码器会在 List 0 的所有参考帧中进行运动搜索,找到一个最佳匹配块。
    • 这个过程会产生两个关键信息:
      1. 参考帧索引 (ref_idx): 指明用的是List 0中的第几帧。
      2. 运动矢量 (MV): 指明当前块相对于参考块的位移。
    • 数据编码:编码器需要将宏块的划分方式、每个子块的 ref_idx运动矢量差 (MVD),以及预测后的残差写入码流。
  • 帧内预测模式 (Intra Modes):
    • 如果在参考帧中找不到好的匹配(例如,场景切换或全新出现的物体),帧间预测的残差会非常大,代价反而会高于帧内预测。
    • 此时,编码器会放弃帧间预测,将该宏块作为I宏块进行编码,即使用Intra_4x4或Intra_16x16模式。这为P帧提供了处理画面剧烈变化的鲁棒性。
  • 跳过模式 (Skip Mode):
    • 适用于画面中静止或运动非常规律的区域(如背景)。
    • 触发条件:当一个16x16宏块的运动矢量(MV)与通过邻近块推导出的预测运动矢量(MVP)完全相同,并且经过变换和量化后的残差系数全部为零时,该宏块就可以被标记为Skip模式。
    • 编码优势:在Skip模式下,编码器几乎不需要传输任何信息,只需要1-2个比特来表示这是一个Skip宏块。解码器看到这个标记后,会自己推导出MVP作为当前的MV,然后直接从参考帧中复制对应的块,残差为零,无需叠加。这极大地节省了码率。

B帧预测

参考帧列表

B帧拥有两个参考帧列表:

  • List 0: 包含显示顺序在当前帧之前的参考帧。
  • List 1: 包含显示顺序在当前帧之后的参考帧。

B帧的宏块预测模式

对于B帧中的宏块,编码器会评估以下所有可能的预测方式,选择代价最小的一种:

  • L0预测 (前向预测):
    • 与P帧的Inter模式完全相同。只在List 0中寻找最佳匹配。
    • 为每个子块产生一个 ref_idx (for List 0) 和一个MV。
  • L1预测 (后向预测):
    • 只在List 1中寻找最佳匹配。
    • 为每个子块产生一个 ref_idx (for List 1) 和一个MV。这对于预测新出现但会被遮挡的物体非常有效。
  • 双向预测 (Bi-predictive):
    • 对于一个子块,编码器同时在List 0和List 1中各寻找一个最佳匹配
    • 这会产生两组运动信息:(ref_idx_L0, MV_L0) 和 (ref_idx_L1, MV_L1)。
    • 最终的预测块P,是由前向预测块P0和后向预测块P1加权平均得到的:P = (P0 + P1) / 2
  • 直接模式 (Direct Mode):
    • 类似于P帧的Skip模式,是一种极低码率的模式,但更加复杂和强大。它不传输MV,而是由解码器自行推导。
    • 时间直接模式 (Temporal Direct): 这是最常用的模式。它利用后向参考帧(List 1中的第0个)中、与当前块位置相同的那个块的运动矢量(记为 MV_col)来推导当前块的MV。它假设物体的运动是线性的,然后根据B帧在L0和L1参考帧之间的时间位置,按比例缩放MV_col,从而计算出前向MV(MV_L0)和后向MV(MV_L1)。
    • 空间直接模式 (Spatial Direct): 从当前B帧中空间相邻的宏块(左边、上边、左上)的MV来推导当前块的MV。
    • 如果推导出MV后,残差也为零,则称之为B_Skip模式,其效率与P帧的Skip模式相当。
  • 帧内预测模式 (Intra Modes): 与P帧一样,作为最后的备选方案。