H.264中整數DCT變換,量化,反量化,反DCT究竟是如何實現的?(無程式碼,無真相)
H.264中採用的是整數DCT變換,在實現的時候,該變換和量化又雜糅在一起,那麼這些錯綜複雜的關係究竟是怎樣糾纏的呢?在參考H.264樂園論壇會員cs1860wd的帖子和H.264 and MPEG-4 VIDEO COMPRESSION(第一版)這本書後,基於帖子和書上的講解,給出相應的實現程式碼,並驗證程式碼的正確性.
還是以foreman視訊第一幀第一個巨集塊第一個4*4塊為例. 下面給出畫素值:
====================== Y Data ======================
+----------------+----------------+----------------+----------------+
| 43,216,254,249
| 49,198,193,211,|228,205,213,185,|211,207,186,248,|198,203,208,183,|
| 48,194,177,171,|197,173,185,136,|191,195,138,179,|142,176,177,135,|
| 46,214,225,169,|177,189,198,160,|203,208,177,165,|173,196,191,156,|
+----------------+----------------+----------------+----------------+
| 41,185,208,180,|203,228,226,200,|214,226,225,227,|228,225,224,210,|
| 31,130,173,178,|215,230,221,212,|220,229,227,228,|229,227,226,226,|
| 29,119,194,216,|211,213,219,222,|225,223,220,219,|218,218,218,218,|
| 25,126,219,224,|217,224,227,227,|227,226,225,224,|220,220,221,222,|
+----------------+----------------+----------------+----------------+
| 26,131,215,223,|226,225,225,225,|225,226,223,219,|221,221,219,220,|
| 30,136,216,226,|223,224,225,225,|224,221,217,221,|222,219,220,226,|
| 30,136,216,227,|224,224,225,223,|221,218,221,216,|211,224,224,211,|
| 29,135,217,225,|222,221,222,222,|221,209,181,155,|186,210,186,164,|
+----------------+----------------+----------------+----------------+
| 29,134,216,224,|226,230,230,227,|206,177,146,113,|149,162,147,150,|
| 29,135,219,231,|225,201,190,185,|163,144,153,140,|127,143,165,184,|
| 30,139,210,192,|165,142,134,133,|143,141,129,138,|150,178,201,207,|
| 30,125,166,145,|144,154,132,111,|118,161,175,180,|204,214,213,209,|
+----------------+----------------+----------------+----------------+
該塊的預測值為128(16個位置都是128),之前分析過,在JM8.6中,這一塊在編碼端和解碼端的QDCT均為:
9 -12 -11 -5
3 -3 1 0
3 -1 -2 1
0 0 0 0
且用H.264visa從碼流中得到的DCT係數為:(注意解碼端的DCT與編碼端的DCT必然不同)
====================== Y Data ======================
+------------------------+------------------------+------------------------+------------------------+
| 2304,-3840,-2816,-1600
| 960,-1200, 320, 0,| 0, 0, 320, 0,| -640, -800, 0, 0,| 960, -800, 320, 0,|
| 768, -320, -512, 320,| 512, -640, 0, -320,| -768, 320, -512, 320,| 768, 0, 256, 0,|
| 0, 0, 0, 0,| 0, 0, 0, 0,| 0, 400, 0, 0,| -320, 0, 0, 0,|
+------------------------+------------------------+------------------------+------------------------+
|-1024,-5120,-1792, -640,| 2560, -640, -256, 0,| 512, 0, 0, 0,| 0, 0, 0, 0,|
| 0, 1200, -640, -400,| -320, 400, 0, 0,| 640, 0, 0, 0,| 320, 0, 0, 0,|
| 512, 0, -256, 0,| 0, 0, 0, 0,| 0, 0, 0, 0,| 0, 0, 0, 0,|
| 320, 0, 0, 0,| -320, 0, 0, 0,| 0, 0, 0, 0,| 0, 0, 0, 0,|
+------------------------+------------------------+------------------------+------------------------+
| 0, 320, 0, 0,| 0, 0, 0, 0,| -768, 640, 0, 0,| 512, 0, -256, 0,|
| 0, 0, 0, 0,| 0, 0, 0, 0,| 640, -800, 0, 0,| -640, -400, 320, 0,|
| 0, 0, 0, 0,| 0, 0, 0, 0,| -256, 320, 0, 0,| 0, 320, 0, 0,|
| 0, 0, 0, 0,| 0, 0, 0, 0,| 320, -400, 0, 0,| -320, 0, 0, 0,|
+------------------------+------------------------+------------------------+------------------------+
| -512, 640, -256, 0,| 0, 0, 0, 0,| 1024, -320, -256, 0,| 1024, -320, 0, 0,|
| 960,-1200, 320, 0,| 960, -800, 320, 0,|-1280, 800, 0, 400,| -640, 400, 0, 0,|
| -512, 320, 0, 0,| 0, 0, -256, 0,| 0, 0, -256, 0,| 512, 640, 0, 0,|
| 0, 0, 0, 0,| -320, 0, 0, 0,| -640, 800, 0, 0,| 0, 0, 0, 0,|
+------------------------+------------------------+------------------------+------------------------+
下面根據變換,量化,反量化和反變換公式給出如下程式,看看是否與上述資料一致.(需要特別指出的是:JM8.6中並不是這麼實現的,但本質是相同的,最後結果也一樣.)
#include <iostream>
#include <cmath>
#define BLOCK_SIZE 4
using namespace std;
int QP = 28; // 量化引數
//原始的YUV矩陣
int orgYUV[BLOCK_SIZE][BLOCK_SIZE] =
{
43, 216, 254, 249,
49, 198, 193, 211,
48, 194, 177, 171,
46, 214, 225, 169
};
//預測值矩陣
int predYUV[BLOCK_SIZE][BLOCK_SIZE] =
{
128, 128, 128, 128,
128, 128, 128, 128,
128, 128, 128, 128,
128, 128, 128, 128
};
int D[BLOCK_SIZE][BLOCK_SIZE]; //中間矩陣
int Di[BLOCK_SIZE][BLOCK_SIZE]; //中間矩陣
int W[BLOCK_SIZE][BLOCK_SIZE]; //核矩陣
int Z[BLOCK_SIZE][BLOCK_SIZE]; //QDCT矩陣
int Wi[BLOCK_SIZE][BLOCK_SIZE]; //Wi矩陣
int Xi[BLOCK_SIZE][BLOCK_SIZE]; //解碼的殘差矩陣
//Cf矩陣
int Cf[BLOCK_SIZE][BLOCK_SIZE]=
{
1, 1, 1, 1,
2, 1, -1, -2,
1,-1, -1, 1,
1,-2, 2, -1
};
//Ci矩陣
int Ci[BLOCK_SIZE][BLOCK_SIZE]=
{
2, 2, 2, 1,
2, 1, -2, -2,
2, -1, -2, 2,
2, -2, 2, -1
};
//MF矩陣
int MF[6][3]=
{
13107, 5243, 8066,
11916, 4660, 7490,
10082, 4194, 6554,
9362, 3647, 5825,
8192, 3355, 5243,
7282, 2893, 4559
};
//Qstep矩陣
int V[6][3]=
{
10, 16, 13,
11, 18, 14,
13, 20, 16,
14, 23, 18,
16, 25, 20,
18, 29, 23
};
//矩陣轉置
void matrixTransform(int a[][BLOCK_SIZE])
{
int i, j, tmp;
for(i = 0; i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
if(i < j)
{
tmp = a[i][j];
a[i][j] = a[j][i];
a[j][i] = tmp;
}
}
}
}
//矩陣求差
void matrixSubtract(int a[][BLOCK_SIZE],int b[][BLOCK_SIZE])
{
int i, j;
for(i = 0;i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
a[i][j] -= b[i][j];
}
}
}
//矩陣求積
void matrixMultiply(int a[][BLOCK_SIZE],int b[][BLOCK_SIZE],int c[][BLOCK_SIZE])
{
int i, j, k;
for(i = 0; i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
c[i][j] = 0;
for(k = 0; k < BLOCK_SIZE; k++)
{
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
//矩陣顯示
void matrixShow(int a[][BLOCK_SIZE])
{
int i, j;
cout << "*****************************" << endl;
for(i = 0; i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
cout << a[i][j] << "\t";
}
cout << endl;
}
cout << "*****************************" << endl << endl;
}
//求QDCT
void quantizeDCT(int W[][BLOCK_SIZE])
{
// QP決定了qbits和f, QP和位置(i, j)共同決定了mf
int qbits = 15 + floor(QP / 6);
int f = (int)( pow(2.0, qbits) / 3 );
int mf;
int i, j, k;
for(i = 0; i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
//以下均依公式實現
//(0, 0), (2, 0), (0, 2), (2, 2) mf為 MF[QP % 6][0]
//(1, 1), (3, 1), (1, 3), (3, 3) mf為 MF[QP % 6][1];
// other positions mf為 MF[QP % 6][2];
if((0 == i || 2 == i) && (0 == j || 2 == j))
k = 0;
else if((1 == i || 3 == i) && (1 == j || 3 == j))
k = 1;
else
k = 2;
mf = MF[QP % 6][k];
Z[i][j] = ( abs(W[i][j]) * mf + f ) >> qbits;
if(W[i][j] < 0)
Z[i][j] = -Z[i][j];
}
}
}
//求Wi(即解碼端的DCT) (Z為QDCT)
void reverseQuantize(int Z[][BLOCK_SIZE])
{
int t = floor(QP / 6);
int f = (int)pow(2, t);
int v;
int i, j, k;
for(i = 0; i < BLOCK_SIZE; i++)
{
for(j = 0; j < BLOCK_SIZE; j++)
{
//以下均依公式實現
if((0 == i || 2 == i) && (0 == j || 2 == j))
k = 0;
else if((1 == i || 3 == i) && (1 == j || 3 == j))
k = 1;
else
k = 2;
v = V[QP % 6 ][k];
Wi[i][j] = Z[i][j] * v * f;
}
}
}
int main()
{
matrixSubtract(orgYUV, predYUV);
cout << "Residual matrix is" << endl;
matrixShow(orgYUV);
//此時orgYUV變為殘差矩陣
matrixMultiply(Cf, orgYUV, D);
matrixTransform(Cf);
matrixMultiply(D, Cf, W);
//得到的W即為核
cout << "Matrix Core(W) is" << endl;
matrixShow(W);
//利用核W來得到QDCT(Z)
quantizeDCT(W);
cout << "Matrix QDCT(Z) is" << endl;
matrixShow(Z);
//利用QDCT(Z)得到解碼端的DCT(Wi).(Wi與編碼端DCT必然不同)
reverseQuantize(Z);
cout << "Matrix W'(解碼端DCT) is" << endl;
matrixShow(Wi);
//利用Wi得到解碼的殘差矩陣Xi
matrixMultiply(Ci, Wi, Di);
matrixTransform(Ci);
matrixMultiply(Di, Ci, Xi);
int i,j;
for(i = 0;i < 4; i++)
{
for(j = 0; j < 4; j++)
{
Xi[i][j] = int( Xi[i][j] / 256.0 + 0.5 );
}
}
cout << "Matrix Xi(解碼端殘差) is" << endl;
matrixShow(Xi);
return 0;
}
結果為:(可以看出,結果中的QDCT和解碼端的DCT都與之前的資料吻合,從而證明上面程式的實現是正確的.)
Residual matrix is
*****************************
-85 88 126 121
-79 70 65 83
-80 66 49 43
-82 86 97 41
*****************************
Matrix Core(W) is
*****************************
609 -1255 -685 -560
277 -476 113 -73
175 -159 -119 98
-14 -13 4 1
*****************************
Matrix QDCT(Z) is
*****************************
9 -12 -11 -5
3 -3 1 0
3 -1 -2 1
0 0 0 0
*****************************
Matrix W'(解碼端DCT) is
*****************************
2304 -3840 -2816 -1600
960 -1200 320 0
768 -320 -512 320
0 0 0 0
*****************************
Matrix Xi(解碼端殘差) is
*****************************
-77 88 132 110
-80 63 67 77
-82 62 48 39
-79 87 93 32
*****************************
下面,簡要看看整數DCT變換的原理.(關於整數DCT變換公式的推倒,請參考餘兆明的《影象編碼標準H.264技術》).
DCT變換為: DCT = A * X * At
量化為: QDCT = floor(DCT/Qstep)
反量化為: DCT‘ = QDCT * Qstep
反DCT為: X’ = At * DCT‘ * A
但在H.264中採用的是整數DCT, 在JM8.6中的實現方式也很有講究:( .* 表示矩陣對應元素相乘)
整數DCT變化為: DCT = (Cf * X *Cft) .*Ef
量化為: QDCT = floor(DCT/Qstep)
反量化為: DCT‘ = QDCT * Qstep
整數反DCT變換為: X’ = Ci * (DCT‘ .* Ei) *Ci
在實現的時候,經常不直接得到DCT係數,而是將變換和量化結合在一起實現. 整數DCT變換有很多好處:沒有除法,沒有浮點數,所以高效且準確,而且減少了矩陣運算.
最後感慨一下:如果上面的程式用matlab來模擬,就簡單多了,matlab太適合處理矩陣問題了.