帧间预测是H264标准中的一种基于时间冗余的压缩方法。因为视频流相邻帧存在时间相关性,相邻帧的视频内容差异不大,因此可以通过前后相邻帧的数据预测当前宏块内容,从而达到压缩数据目的。
帧间预测处理的基本单元是宏块内的最小子分块,比如一个宏块如果分为16个P_4x4块,则需要对每个4x4块分别做帧间预测。
帧间预测的输入为:
宏块分块索引mbPartIdx、子宏块分块索引subMbPartIdx,这两个变量主要用于确定当前处理分块在图像的位置;
当前分块的大小,包括亮度分量和色度分量(partWidth、partHeight、partWidthC、partHeightC);
亮度和色度的运动矢量mvL0、mvL1、mvCL0、mvCL1;
参考帧索引refIdxL0、refIdxL1(宏块头信息中解析得到);
加权预测参数logWD、w0、w1、o0、o1;
输出为partWidthx partHeight大小的亮度预测块和partWidthCx partHeightC大小的色度预测块。
帧间预测流程可分为三部:
- 参考帧选择,根据参考帧队列refPicListX和当前宏块参考帧索引refIdxLX,找到参考帧 refPicLX;
- 非整数像素插值,对于非整数像素 mvLX 需要对参考帧 refPicLX 做像素插值处理,得到预测像素块 predPart
- 采样点加权预测,预测像素块和权重通过相应运算得到最终预测像素块。
1. 参考帧选择
参考帧选择过程如下:
- 当前slice为场格式(field_pic_flag=1),则参考帧队列RefPicListX里的成员是参考场或参考帧中的一场。由于当前宏块一定是场宏块,所以参考场为RefPicListX[refIdxLX];
- 当前slice为帧格式(field_pic_flag=0),参考帧队列RefPicListX里的成员是参考帧或参考场对。参考帧的选择与当前宏块类型相关。
1)如果当前宏块为帧宏块,参考帧为RefPicListX[refIdxLX];
2)如果当前宏块为场宏块,参考场对为RefPicListX[refIdxLX/2],参考帧的选择由refIdxLX决定。refIdxLX%2=0,则参考场选择参考场对中与当前宏块长类型相同的场,否则选择类型相反的场。
2.非整数像素插值
H264为提高预测精度,减少编码码流,支持1/4像素精度预测。对于非整数像素mv帧间预测时需要对像素插值。
根据预测块在图像坐标位置,mv,参考帧RefPicLX,可以得到预测块在对应参考帧上的坐标(xInt,yInt)和1/4像素偏移(xFrac,yFrac)
亮度分量按以下公式计算:
xIntL = xAL + ( mvLX[ 0 ] >> 2 ) + xL
yIntL = yAL + ( mvLX[ 1 ] >> 2 ) + yL
xFracL = mvLX[ 0 ] & 3
yFracL = mvLX[ 1 ] & 3
YUV420格式色度分量按以下公式计算:
xIntC = ( xAL / SubWidthC ) + ( mvCLX[ 0 ] >> 3 ) + xC
yIntC = ( yAL / SubHeightC ) + ( mvCLX[ 1 ] >> 3 ) + yC
xFracC = mvCLX[ 0 ] & 7
yFracC = mvCLX[ 1 ] & 7
2.1 亮度插值
亮度插值过程的输入有:预测亮度块位置坐标(xInt,yInt)和1/4像素偏移(xFrac,yFrac),以及参考帧refPicLX。输出为预测亮度块。
以G为宏块中待预测像素在参考帧的对应像素点,如果1/4像素偏移(xFrac,yFrac)不为0,则需要进行像素插值。
位置b和h分别对应1/2像素,计算方式为
b = Clip1 ( E − 5 * F + 20 * G + 20 * H − 5 * I + J + 16 ) >> 5
h = Clip1 ( A − 5 * C + 20 * G + 20 * M − 5 * R + T +16) >> 5
1/2像素位置j有两种计算方式,其计算结果一样:
j = Clip1 (cc − 5 * dd + 20 * h1 + 20 * m1 − 5 * ee + ff+512)>>10,
j = Clip1 (aa − 5 * bb + 20 * b1 + 20 * s1 − 5 * gg + hh+512)>>10
1/4像素位置a, c, d, n, f, i, k, q由行或列中相邻的整数像素点和1/2像素点求均值得到:
a = ( G + b + 1 ) >> 1
c = ( H + b + 1 ) >> 1
d = ( G + h + 1 ) >> 1
n = ( M + h + 1 ) >> 1
f = ( b + j + 1 ) >> 1
i = ( h + j + 1 ) >> 1
k = ( j + m + 1 ) >> 1
q = ( j + s + 1 ) >> 1
1/4像素位置e,g,p,r由相邻两个1/2像素求均值得到
e = ( b + h + 1 ) >> 1
g = ( b + m + 1 ) >> 1
p = ( h + s + 1 ) >> 1
r = ( m + s + 1 ) >> 1.
2.2 色度插值
色度分量插值只与相邻4个整数像素点相关,计算公式如下:
predPartLXC[ xC, yC ] = ( ( 8 − xFracC ) * ( 8 − yFracC ) * A + xFracC * ( 8 − yFracC ) * B + ( 8 − xFracC ) * yFracC * C + xFracC * yFracC * D + 32 ) >> 6
3. 加权预测
帧间预测是基于视频序列亮度不发生变化这一前提,才能获得较好的编码性能。实际上经常能遇到亮度变化的场景,比如镜头淡入淡出、整体或局部光源改变,在这些场景中,简单帧间运动补偿效果并不理想,实际编码中可能会选择帧内预测。加权预测就是为了应对亮度变化场景。
H264标准中定义了两种加权预测模式:显示模式(explicit)和隐式模式(implicit),p slice只有显示模式,B slice有显示模式和隐式模式。显示模式中,加权系数在slice header中定义。隐式模式中,加权系数由参考图像的时间位置推导,越接近当前图像系数越大,反之越小。
3.1 默认加权预测
默认加权预测也就是不做加权预测。
- 前向或后向预测时,最终预测块数据等于前向或后向预测数据,predPart=predPartLX;
- 双向预测时,最终预测块数据等于前向和后向预测数据的均值,predPart=(predPartL0+predPartL1+1)>>1;
3.2 加权预测
显式加权预测和隐式加权预测主要区别在于预测参数是否显式编码在码流中,其加权预测过程是完全一样的。
预测过程中用到的参数包括logWD、前向参考帧的权重w0、后向参考帧权重w1、前向参考帧加权预测o0、后向参考帧加权预测o1。其中亮度分量和色度分量加权预测过程一样,但各自参数不一样。
- 前向或后向加权预测
if( logWD >= 1 )
predPart = Clip1( ( ( predPartL0* w0 + 2^logWD − 1 ) >> logWD ) + o0 )
else
predPart = Clip1( predPartL0[ x, y ] * w0 + o0 ) - 双向加权预测
predPart = Clip1( ( ( predPartL0 * w0 + predPartL1 * w1 + 2^logWD ) >> ( logWD + 1 ) ) + ( ( o0 + o1 + 1 ) >> 1 ) )
3.3 加权预测参数推导
预测加权参数推导分为两种情况:显式预测和隐式预测。
3.3.1显式加权预测参数
P slice的weighted_pred_flag=1或B slice的weighted_bipred_flag=1使用显式加权预测。此时slice header中编码了预测权重参数。
- 计算中间变量refIdxL0WP、refIdxL1WP;
如果当前slice为MBAFF,并且当前宏块为场宏块,则
refIdxL0WP=refIdxL0>>1, refIdxL1WP=refIdxL1>>1;
否则refIdxL0WP=refIdxL0, refIdxL1WP=refIdxL1 - 按下面公式得到亮度加权预测参数;
logWD=luma_log2_weight_denom
w0=luma_weight_l0[refIdxL0WP]
w1=luma_weight_l1[refIdxL1WP]
o0=luma_offset_l0[refIdxL0WP]
o1=luma_offset_l1[refIdxL1WP]
色度加权预测参数
logWD=chroma_log2_weight_denom
w0= chroma _weight_l0[refIdxL0WP][iCbCr]
w1= chroma _weight_l1[refIdxL1WP] [iCbCr]
o0= chroma _offset_l0[refIdxL0WP] [iCbCr]
o1= chroma _offset_l1[refIdxL1WP] [iCbCr]
3.3.2隐式加权预测参数
B slice并且weighted_bipred_flag=2时使用隐式加权预测参数,此时码流中不编码任何加权预测参数,推导过程如下:
以下几个参数是固定值,logWD=5,o0=0,o1=0;
W0、w1需要根据参考帧位置计算得到;
-
先计算当前宏块所在帧/场currPicOrField,前向参考帧/场pic0和后向参考帧/场pic1;
- 如果当前slice不为场格式,当前宏块是场宏块
currPicOrField是指当前图像中的一场,并且该场与当前宏块有同样场属性;
pic0是指前向参考帧RefPicListL0[refIdxL0/2]中的一场,并满足下面条件:如果refIdxL0%2=0,该场与当前宏块有同样场属性;否则,该场与当前宏块有相反场属性;
pic1是指后向参考帧RefPicListL1[refIdxL1/2]中的一场,并满足下面条件:如果refIdxL1%2=0,该场与当前宏块有同样场属性;否则,该场与当前宏块有相反场属性; - 否则,currPicOrField是当前帧,pic0是RefPicListL0[refIdxL0],pic1是RefPicListL1[refIdxL1];
- 如果当前slice不为场格式,当前宏块是场宏块
-
通过变量currPicOrField, pic0, pic1计算变量 tb, td, tx,DistScaleFactor
tb = Clip3( -128, 127, DiffPicOrderCnt( currPicOrField, pic0 ) )
td = Clip3( -128, 127, DiffPicOrderCnt( pic1, pic0 ) )
tx = ( 16 384 + Abs( td / 2 ) ) / td
DistScaleFactor = Clip3( -1024, 1023, ( tb * tx + 32 ) >> 6 )
-
计算权重w0、w1.
- 如果满足以下条件,w0=w1=32
DiffPicOrderCnt( pic1, pic0 ) = 0 或 1 ;
pic1 和pic0 都被标记为"用于长期参考" ;
( DistScaleFactor >> 2 ) < −64 or ( DistScaleFactor >> 2 ) > 128, - 否则w0 = 64 − (DistScaleFactor >> 2) ;
w1C = DistScaleFactor >> 2
- 如果满足以下条件,w0=w1=32