相比帧内预测,帧间预测要更加复杂,因为其本质上是提高更高压缩效率的技术,其中涉及到诸如亚像素插值、运动搜索、多参考帧管理、加权预测等等的知识点。这些知识内容繁多,加之笔者也没有深入的了解,因此本文的内容只会讲解少量知识点,而更多注重于结合代码去了解整个帧间预测的流程,同时建议读者从其他书籍获取更加详尽的讲解。
运动估计、运动补偿与运动矢量
首先要讲的是帧间预测中三个最基本的概念,其中运动估计和运动补偿常常令人混淆不清:
- 运动估计(Motion Estimation,ME):对当前编码块,在已编码的参考帧中利用运动搜索技术寻找最佳的匹配块,并计算出两个块的偏移量的过程。
- 运动矢量(Motion Vector,MV):上面计算出的这个偏移量就叫运动矢量,通常使用参考帧的块坐标减去当前块的坐标,x和y分量分开保存。可知每一个分块会对应一个MVx和一个MVy。
- 运动补偿(Motion Compensation,MC):利用运动矢量和某种帧间预测的方法,从而估计出当前块的像素值的过程。
由定义可以看出一般这三者的先后顺序会是:运动估计-运动矢量-运动补偿。
树状分块
树状分块其实就是我们熟知的宏块分割方式,一般可以分为16x16、两个16x8、两个8x16、四个8x8,当出现8x8分块时还可以进一步做子宏块划分,即8x8、两个8x4、两个4x8和四个4x4。需要注意的是只有帧间宏块才具备以上的全部分块方式,而帧内宏块只有16x16、8x8和4x4。
多参考帧
H.264运用了多参考帧技术,多个参考帧会维护在一个列表中。双向预测时有两个参考帧列表LIST_0和LIST_1,分别存放前向预测后后向预测所用的参考帧,如果是单向预测则只会用到其中一个。保存参考帧的是称为解码图像缓冲区(Decoded Picture Buffer,DPB)的结构,包含了短期参考帧、长期参考帧和非参考帧,默认情况下参考帧列表的最大长度为16。
MV预测
MV较多的情况下也可能占据较多的数据量,因此也必须对MV进行预测编码,最终编码的是实际MV与预测MV(MVp)的差值MVD,即MVD = MV-MVp。如图所示,蓝色的为当前块,可以是任意一种分块大小,首先获取它相邻的四个4x4块,然后再获取每个4x4块所属的分块(也可能是任意分块大小,而且有可能和当前块属于同一宏块),预测规则如下:
- 当前块是16x8时,若是在上部的16x8,则MVp为B的MV;若是在下部的16x8,则MVp为A的MV。前提是B/C可用,否则转入中值预测。
- 当前块是8x16时,若是在左部的8x16,则MVp为A的MV;若是在右部的8x16,则MVp为C的MV。前提是A/C可用,否则转入中值预测。
- 如果不是以上的分块,则MVp为A、B、C三者MV的中值。
对于中值预测的情况,还有以下规定:
- A、B、C均不可用时,不做预测,直接编码MV。
- A、B、C只有一个可用时,MVp为可用块的MV。
- A、B、C有两个可用时,不可用块的MV设为0,仍然取中值。
可用情况判断:
- A、B、C参考帧和当前块参考帧不同的时候不可用。
- A、B、C是帧内编码的块时不可用。
- C不可用时,若D可用则用D代替C(即D是备用的)。
Skip模式
Skip模式是对宏块的一种特殊的帧间编码模式,在P和B帧中使用这种模式的宏块分别叫P_Skip宏块和B_Skip宏块。Skip模式实质上就是除了标记自身为Skip宏块以外,不用编码任何的信息,可以极大的节省比特数。其原理是解码时直接使用预测的MVp作为其MV,用该MV进行运动补偿得到预测像素值,将预测像素值直接作为真正的像素值。该模式通常出现在连续的平坦区域或者连续的缓慢运动中。
P_Skip宏块的条件:
- 最佳模式为P_L0_16x16。
- 参考帧为LIST_0中的第一个。
- MVD为0.
- 系数全为0。
B_Skip宏块的条件:
- 最佳模式为B_Direct_16x16。
- 系数全为0。
B帧的预测模式
不同于P帧只有前向预测模式(L0),B帧根据利用的参考帧列表不同一共有四种预测模式:利用LIST_0的前向预测(L0);利用LIST_1的后向预测(L1)、利用LIST_0和LIST_1的双向预测(Bipred)和直接预测(Direct)。每个分块都可以独立从中选择一种预测模式,但有两种特殊情况:
- 对于16x8或8x16的分块来说不能使用直接预测模式。
- 8x8分块的预测模式应用到其所有的子块中,即8x4/4x8/4x4均与其相应的8x8块的预测模式相同。
Direct模式也是一种特殊的模式,其MVD和参考帧索引可以通过参考帧列表中的已编码帧计算得出,因为不需要编码这两项。有B_Direct_16x16和B_Direct_8x8两种,值得注意的是,标准中把B_Skip和Direct模式的宏块/块用同一个值标记,后面再通过是否有非零系数来区分两者。
相关代码
1 | //如果不是I帧 |
这段代码在md_high.c的encode_one_macroblock_high(),这个函数是模式决策的核心,然后再讨论。在这里主要判断帧类型,若是P/B帧,则分别对各种可用的帧间预测模式进行进行运动估计并完成决策。