帧内预测之函数Intra16x16_Mode_Decision的分析与理解

2011年9月5日13:47:04

帧内预测之Intra16x16中的4种模式选择

在JM8.6中对应的函数是Intra16x16_Mode_Decision, 该函数包括3部分:

intrapred_luma_16x16:计算4种模式的预测值

find_sad_16x16: 计算SATD值作为代价,从而得到最优的模式

dct_luma_16x16:对于所选出的最优模式进行DCT变换/量化和反DCT变换和量化

下面对这三个函数进行详细分析一下:

(1)intrapred_luma_16x16

这个函数其实很简单, 都是对应着标准来写的.

Intra16x16有vertical, horizontal,DC和plane共4种预测模式,

对于每一幅图像的第一个宏块, 由于上边和左边都没有可以参考的宏块,所以 在程序中实现时是默认其预测值都为128, 即整个第一个宏块的预测块为每一个像素都为128

这样预测完成时, 最后的预测值都保存在img(ImageParameters)结构的mprr_2(int mprr_2[5][16][16];)数组中

 

(2)find_sad_16x16

在实际的代码中, 其实是使用SATD值作为选择标准的, 而不是使用的SAD值.

在该函数中有几个比较重要的变量, 这几个变量对于理解这个函数很重要.

Int current_intra_sad_2是保存当前SATD值和的一个临时变量.

Int best_intra_sad2是保存的至今为止最优的SATD值,

int * intra_mode是对应最优SATD值的模式, 因为最后需要使用这个模式, 所以使用指针, 将这个值传出函数外.

 

Int M1[16][16]:当前宏块对应的残差:原始值-预测值

int M0[4][4][4][4]:这个数组比较有意思,表示16x16中16个4x4个子块残差,2,4表示4x4坐标,1,3表示4x4中像素坐标. 这样做主要是进行Hadamard变换方便的需要(本文猜测)

int M4[4][4]:这个数组主要用于计算DC系数的Hadamard变换

int M3[4]:这个数组主要是Hadamard变换中间所需要的一个数组, 联系h.264中的碟形算法和Hadamard矩阵的特点, 这其实是矩阵的某些值的和或差. 进行矩阵乘法的结果是:

 

1 1 1 1

1 1 -1 -1

1 -1 -1 1

1 -1 1 -1

* 

[ w00, w01, w02, w03]

[ w10, w11, w12, w13]

[ w20, w21, w22, w23]

[ w30, w31, w32, w33]

[ w00 + w10 + w20 + w30, w01 + w11 + w21 + w31, w02 + w12 + w22 + w32, w03 + w13 + w23 + w33]

[ w00 + w10 - w20 - w30, w01 + w11 - w21 - w31, w02 + w12 - w22 - w32, w03 + w13 - w23 - w33]

[ w00 - w10 - w20 + w30, w01 - w11 - w21 + w31, w02 - w12 - w22 + w32, w03 - w13 - w23 + w33]

[ w00 - w10 + w20 - w30, w01 - w11 + w21 - w31, w02 - w12 + w22 - w32, w03 - w13 + w23 - w33]

对上面表格的第二行的矩阵进行调整一下可以看到,

[ w00 + w30 + w20 + w10, w01 + w31 + w21 + w11, w02 + w32 + w22 + w12, w03 + w33 + w23 + w13]

[ w00 + w30 - (w20 + w10), w01 + w31 - (w21+ w11), w02 + w32 - (w22+w12), w03 + w33 - (w23 + w13)]

[ w00 - w30 - (w20 - w10), w01 - w31 - (w21- w11), w02 - w32 - (w22 - w12), w03 - w33 - (w23 - w13)]

[ w00 - w30 + w20 - w10, w01 - w31 + w21 - w11, w02 - w32 + w22 - w12, w03 - w33 + w23 - w13]

 

我们可以发现, (1)先对列进行的变换, 每一列都是第0,3元素的和或差, 第1,2元素的和或差

代码中的实现为:

for (j=0;j<4;j++)                            //+++++++++++++++

{                                            //++

       M3[0]=M0[0][ii][j][jj]+M0[3][ii][j][jj];//++

       M3[1]=M0[1][ii][j][jj]+M0[2][ii][j][jj];//++

       M3[2]=M0[1][ii][j][jj]-M0[2][ii][j][jj];//++

      M3[3]=M0[0][ii][j][jj]-M0[3][ii][j][jj];//++ 第一次一维Hadamard变换

                        //++

       M0[0][ii][j][jj]=M3[0]+M3[1];            //++

       M0[2][ii][j][jj]=M3[0]-M3[1];            //++

       M0[1][ii][j][jj]=M3[2]+M3[3];            //++

       M0[3][ii][j][jj]=M3[3]-M3[2];            //++

}     

这儿我们可以清楚的看到, ii和jj是宏块中的4x4块的坐标, j是对应每一个宏块中像素的纵坐标, 所以一个for循环这儿就是完成一列的计算, 这段代码块正好吻合上面的矩阵计算.

 

用同样的方法可以分析对行的计算,

[ w00, w01, w02, w03]

[ w10, w11, w12, w13]

[ w20, w21, w22, w23]

[ w30, w31, w32, w33]

* 

1 1 1 1

1 1 -1 -1

1 -1 -1 1

1 -1 1 -1

[ w00 + w01 + w02 + w03, w00 + w01 - w02 - w03, w00 - w01 - w02 + w03, w00 - w01 + w02 - w03]

[ w10 + w11 + w12 + w13, w10 + w11 - w12 - w13, w10 - w11 - w12 + w13, w10 - w11 + w12 - w13]

[ w20 + w21 + w22 + w23, w20 + w21 - w22 - w23, w20 - w21 - w22 + w23, w20 - w21 + w22 - w23]

[ w30 + w31 + w32 + w33, w30 + w31 - w32 - w33, w30 - w31 - w32 + w33, w30 - w31 + w32 - w33]

对应的代码如下:

for (i=0;i<4;i++)                            //++++++++++++++++++++++++++++++

{                                            //++

        M3[0]=M0[i][ii][0][jj]+M0[i][ii][3][jj];//++

        M3[1]=M0[i][ii][1][jj]+M0[i][ii][2][jj];//++

        M3[2]=M0[i][ii][1][jj]-M0[i][ii][2][jj];//++

        M3[3]=M0[i][ii][0][jj]-M0[i][ii][3][jj];//++

        //++ 第二次一维Hadamard变换

        M0[i][ii][0][jj]=M3[0]+M3[1];            //++

        M0[i][ii][2][jj]=M3[0]-M3[1];            //++

        M0[i][ii][1][jj]=M3[2]+M3[3];            //++

        M0[i][ii][3][jj]=M3[3]-M3[2];            //++

        for (j=0;j<4;j++)                        //||

            if ((i+j)!=0)                            //||

       current_intra_sad_2 += abs(M0[i][ii][j][jj]);    //++ 变换后的AC残差值取绝对值求和作为代价

}     

 

现在为止, 我们应该搞明白了如何进行Hadamard变换了.

下面我们接着分析一下该函数是如何运作的:

该函数的主要代码是包含在一个大的循环里边的, 这个循环其实就是对Intra16x16的4种模式进行的循环, 目的就是找出代价SATD最小的模式. 循环中是对一个宏块进行的计算.

1) 先把这个宏块分为16个4x4的小块, 然后对每一个4x4小块进行Hadamard变换, 将变换后的4x4矩阵的AC系数的绝对值累加起来得到代价,保存在current_intra_sad_2中, 这样就得到了16个4x4的小块的所有的AC系数的绝对值和

2) 取出该16个4x4小块的DC系数, 组成一个4x4的矩阵, 然后对该矩阵进行Hadmard变换, 将变换得到的系数的绝对值的和累加到current_intra_sad_2中, 这样我们就得到了整个宏块的SATD值.

3) 同时, 通过循环比较就可以得到最优的SATD值.

  1. dct_luma_16x16

dct_luma_16x16 是专门针对 intra16*16 的亮度分量进行的。因为 intra16*16 的亮度分量对 DC 系数要做 Hadamard 变换。而 dct_luma 不包含 Hadamard 变换,所以 JM 干脆将此时对亮度分量的处理单独放到 dct_luma_16x16 中。dct_luma_16x16 中整数 DCT 变换那些操作跟 dct_luma 是相同的。

现在我们已经得到最优模式, 所以现在就是要要对最优模式下的预测值进行如下操作:

  1. 将宏块划为16个4x4块, 然后对每一个块进行整数DCT变换, 结果保存在M0[4][4][4][4]中.
  2. 然后从M0[4][4][4][4]中取出16个DC系数,组成矩阵M4[4][4], 进行Hadamard变换, 结果保存在M4[4][4](相当于替换)中.
  3. 接下来对变换后的DC系数M4, 进行量化, 量化后的结果保存在了两部分中:

a. 一部分是保存在了M4中, 即用量化后的数去替换掉量化前的值, 这么做主要是为了下面进行反量化和反变换要用.

b. 另一部分是最终要用于最后的编码, 保存了DCLevel和DCRun, 这主要是是为了下一步的CAVLC熵编码.DCLevel是非零的量化值, 而DCRun是对应非零量化值之前的0的个数. 从下面的定义可以看到

int* DCLevel = img->cofDC[0][0];int* DCRun = img->cofDC[0][1];

int cofDC[3][2][65];//3=yuv, 2=level+run, 65是为了使空间充足

所以经过这个函数后我们得到的第一个结果就是DCT变换和量化后的结果保存在了img结构的cofDC数组中了.

  1. 然后对变换/量化后的DC系数M4进行反Hamard变换.变换的过程类似正向变换.

注意: Intra16x16与其他块不同, 这儿是先进行逆Hadamard变换,而不是反量化, 在另一篇的分析文章中有提到原因.

  1. 进行反量化, 对M4中的数据进行反量化, 保存在M0数组中对应的DC系数的位置. 这样下面对AC系数进行处理完成之后就可以对整个4x4块进行反变换/反量化了.
  2. 接下来对16个4x4块的AC系数M0分别进行处理:

先对15个AC系数进行量化和反量化: 量化后的结果保存在了ACLevel和ACRun中, 与DC系数类似, 这样我们就得到了整个4x4的所有的DC系数和AC系数的levelRun值, 为CAVLC做好了准备; 接着进行反量化, 其结果保存在了M0中.

然后对M0中的值进行反DCT变换, 结果保存在了M0中, 此时M0中的数据就是一个4x4块的残差值(这个值其实就是解码器中得到的残差值)(事实上,M0中是包含了整个宏块,即16个4x4块的残差)

  1. 然后将M0的数据放在M1数组中
  2. 最后利用M1中的残差数据和mprr_2中的预测数据进行计算可以得到重建的宏块, 保存在enc_picture(StorablePicture)结构的imgY中.

所以, Intra16x16_Mode_Decision函数既完成了模式选择也得到了重建图像,还得到了变换量化后的系数.

 

最后, 我们将这个函数的过程简单的梳理一下:

a. 对整个宏块进行整数DCT变换, 结果在M0[4][4][4][4]中

b. 将16个DC系数保存在M4[4][4]中,然后进行Hadamard变换, 结果保存在M4中

c.对M4中的DC系数进行量化,结果仍在M4中

d. 对M4中的量化后的DC系数进行反Hadamard变换, 结果仍保存在M4中

e. 对M4中结果进行反量化, 结果存在M0数组对应的DC系数处

f. 对M0中的AC系数进行量化和反量化, M0中对应的AC系数处存储的是反量化的结果

g. 对M0中的所有系数进行反DCT变换, 结果保存在M0中, 同时也存储到M1中

h. 利用M1中的残差数据和预测数据进行重建宏块

 

在这个函数中让人比较头疼的是量化和反量化的过程.下面我们来分析一下:

 

在毕书中一般系数的量化方式是:

 

对于经过Hadamard变换的DC系数进行的量化和反量化方式是:

这样我们可以对比代码中的实现方式:

对于DC系数的量化和反量化

量化

level= (abs(M4[i][j]) * quant_coef[qp_rem][0][0] + 2*qp_const)>>(q_bits+1);

反量化

M0[0][i][0][j] = (((M6[j]+M6[j1])*dequant_coef[qp_rem][0][0]<<qp_per)+2)>>2;

 

Ac系数的量化和 反量化

量化

level= ( abs( M0[i][ii][j][jj]) * quant_coef[qp_rem][i][j] + qp_const) >> q_bits;

反量化

M0[i][ii][j][jj]=sign(level*dequant_coef[qp_rem][i][j]<<qp_per,M0[i][ii][j][jj]);

 

其中 quant_coef是量化矩阵, dequant_coef是反量化矩阵

对比以上的两个,可以看到AC系数和DC系数在量化与反量化上的不同点.

 

在这儿强调一点2005的标准和毕厚杰的书中反量化的公式是不同的:
h.264官方中文版(4:2:0)
      亮度DC变换的反量化公式
如果QP'Y大于等于36,那么缩放的结果如下
dcYij = (fij*LevelScale(QP'Y%6,0,0))<<( QP'Y/6−6), i,j = 0..3
— 否则(QP'Y小于36),缩放结果如下
dcYij = (fij*LevelScale(QP'Y%6,0,0)+2^5− QP'Y/6)>>( 6 −QP'Y/6), i,j = 0..3
      
      2x2色度DC变换的反量化公式
dcC=( ( f * LevelScale(QP' % 6, 0, 0 ) )<< ( QP' / 6) ) >> 5, i,j = 0, 1
      
      ac4x4块的缩放
如果qP≥24,通过下述方式得到缩放后的结果。
dij = ( cij * LevelScale( qP % 6, i, j) ) << ( qP / 6 − 4), i,j = 0..3
否则(qP<24=),通过下述方式可以得到缩放后的结果。
dij = ( cij  * LevelScale( qP % 6, i, j )+ 2 ^3-qP/6) >>( 4− qP / 6 ),

而毕书中
      亮度DC变换的反量化公式
如果QP'Y大于等于12,那么缩放的结果如下
dcYij = (fij*LevelScale(QP'Y%6,0,0))<<( QP'Y/6−2), i,j = 0..3
— 否则(QP'Y小于12),缩放结果如下
dcYij = (fij*LevelScale(QP'Y%6,0,0)+2^1− QP'Y/6)>>(2 −QP'Y/6), i,j = 0..3
     
     2x2色度DC变换的反量化公式
QP'>=6
dcC= ( f * LevelScale(QP' % 6, 0, 0 ) )<< ( QP' / 6 -1) , i,j = 0, 1
QP'<6
dcC= ( f * LevelScale(QP' % 6, 0, 0 ) >>1) , i,j = 0, 1

   ac4x4块的缩放
dij = ( cij * LevelScale( qP % 6, i, j) ) <<  qP / 6 , i,j = 0..3

 

 

对于这个函数中,我有一点不是很明白:

就是最后在进行宏块重建的时候

max(0,(M1[i][j]+(img->mprr_2[new_intra_mode][j][i]<<DQ_BITS)+DQ_ROUND)>>DQ_BITS))

为什么要对预测值进行移位?firstime说在反量化的时候少做了一个动作, 但是我还没一直没有找到原因.

 

xkfz007

2011-9-6 09:59:58

 

 

 

 

posted @ 2012-07-31 11:07  Mr.Rico  阅读(...)  评论(...编辑  收藏