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值.
-
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 是相同的。
現在我們已經得到最優模式, 所以現在就是要要對最優模式下的預測值進行如下操作:
-
將宏塊划為16個4x4塊, 然后對每一個塊進行整數DCT變換, 結果保存在M0[4][4][4][4]中.
-
然后從M0[4][4][4][4]中取出16個DC系數,組成矩陣M4[4][4], 進行Hadamard變換, 結果保存在M4[4][4](相當於替換)中.
-
接下來對變換后的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數組中了.
-
然后對變換/量化后的DC系數M4進行反Hamard變換.變換的過程類似正向變換.
注意: Intra16x16與其他塊不同, 這兒是先進行逆Hadamard變換,而不是反量化, 在另一篇的分析文章中有提到原因.
-
進行反量化, 對M4中的數據進行反量化, 保存在M0數組中對應的DC系數的位置. 這樣下面對AC系數進行處理完成之后就可以對整個4x4塊進行反變換/反量化了.
-
接下來對16個4x4塊的AC系數M0分別進行處理:
先對15個AC系數進行量化和反量化: 量化后的結果保存在了ACLevel和ACRun中, 與DC系數類似, 這樣我們就得到了整個4x4的所有的DC系數和AC系數的levelRun值, 為CAVLC做好了准備; 接着進行反量化, 其結果保存在了M0中.
然后對M0中的值進行反DCT變換, 結果保存在了M0中, 此時M0中的數據就是一個4x4塊的殘差值(這個值其實就是解碼器中得到的殘差值)(事實上,M0中是包含了整個宏塊,即16個4x4塊的殘差)
-
然后將M0的數據放在M1數組中
-
最后利用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