• 帧内预测之函数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

     

     

     

     

  • 相关阅读:
    WinAPI: DrawFrameControl 绘制控件
    WinAPI: SetPixel 和 SetPixelV 设置设备环境中指定位置的颜色
    WinAPI: GetSystemInfo 获取系统信息
    WinAPI: GetDiskFreeSpace 获取磁盘组织与容量信息
    WinAPI: GetDiskFreeSpaceEx 获取磁盘容量信息
    分享:Afinal 0.3.5 发布,Android快速开发框架
    海量数据多路归并排序的c++实现(归并时利用了败者树)
    当TransferEncoding遇上ContentEncoding_虚拟现实_百度空间
    chunked 编码 解码 c算法 yaneng的专栏 博客频道 CSDN.NET
    败者树 多路平衡归并外部排序 Dreaming.O的专栏 博客频道 CSDN.NET
  • 原文地址:https://www.cnblogs.com/xkfz007/p/2616481.html
Copyright © 2020-2023  润新知