幀內預測之函數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

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM