视频编码中编码块划分的原理和实现方式

本文分享视频编码中编码块划分的基本原理和代码实现方式。

CTU划分

现在的视频编码都是基于块进行的,将一帧视频划分成不同的块,然后对每个块再分别进行编码处理。由于原始YUV格式视频有3个通道,一个亮度通道Y,两个色度通道UV,这里块的划分以亮度通道Y为例,色度通道类似。

在H.264中,一帧图像首先被划分为大小相同的16×16的块,称为宏块(Marco Block,MB),宏块还可以进一步划分,划分方式如下。所以H.264支持7种尺寸的宏块,16×16,16×8,8×16,8×8,8×4,4×8,4×4,最小的宏块尺寸为4×4

视频编码中编码块划分

H.265里块的划分更加灵活尺寸也更多变,一帧图像首先被划分为64×64大小的编码树单元(Coding Tree Unit,CTU),一个CTU由一个亮度编码树块(Coding Tree Block,CTB),和两个对应的色度CTB及相应的句法元素构成。对于亮度CTB其按四叉树的方式向下划分,最大为64×64,最小为8×8,划分方式如下。

视频编码中编码块划分

可见1个64×64的CTB可以划分为4个32×32的CTB,每个32×32的CTB又可以划分为4个16×16的CTB,每个16×16的CTB可划分为4个8×8的CTB。最小的亮度CTB为8×8,所以最多只能划分3层。四叉树划分这种划分方式的表示也很容易,只要给出其划分深度就能知道块的大小。

CTB的划分在有的图像上还有问题,例如对于高清视频,每帧图像分辨率为1920×1080,若划分为64×64的块,则每行有30块,而1080/64=16.875不是整数,所以最下边1行块必须进一步划分,使整帧图像划分为30x17CTBs,如下图所示。

视频编码中编码块划分

PU划分

当CTU划分成CU后,每个CU还要进行预测、变换等。当进行预测时,CU还要继续划分为不同的预测单元(Predict Unit,PU)。(关于预测是怎么进行的会在后面的文章中介绍,现在只需要知道预测分为帧内预测和帧间预测两类)。PU是进行预测的基本单元,一个CU内的所有PU的预测方式相同都为帧内预测或都为帧间预测。且CU到PU只允许一层划分,其划分方式如下。

视频编码中编码块划分

一共8种划分方式,4种对称划分,2Nx2N(即不划分,整个CU就是一个PU),2NxN, Nx2N, NxN,和4种不对称划分,2NxnU, 2NxnD, nLx2N, nRx2N,不对称划分都是在1/4处进行划分,例如32×32的块进行2NxnU划分会分成一个32×8的块和一个32×24的块,H.265规定只有在亮度CU尺寸大于等于16×16时才允许不对称划分。

帧间预测的CU划分成PU可以按上面8种任意模式划分。而帧内预测的CU若尺寸大于8×8则只能按2Nx2N模式划分,若帧内预测CU尺寸等于8×8可以按NxN划分成4个4×4的PU,此时对应的两个色度PU也为4×4而不是2×2因为H.265里最小的块为4×4。

TU划分

当CU完成预测后,就要进行变换,CU会划分成不同的变换单元(Transform Unit,TU)。TU是进行变换和量化的基本单元,和PU类似TU也是在CU基础上划分,但是PU和TU的划分互不影响。TU的划分方式和CTU类似,也是四叉树划分,因为CU完成预测后CU内的值不再是像素值而是残差值,所以CU按四叉树方式划分成TU后会形成一个残差四叉树(Residual Quad Tree,RQT),CU是树根,TU是树叶,由于CU和TU都是按四叉树划分形成的,所以CU和TU都是方形的。

视频编码中编码块划分

上图中红色边缘的块就是划分成的TU,TU最大尺寸为64×64,最小为4×4,但是由于DCT变换运算的最大尺寸为32×32,所以64×64的TU隐含着必须进一步划分成4个32×32的TU。同样若亮度TU为4×4其对应的2个色度TU也是4×4而不进一步划分。

下面是一个划分实例。

视频编码中编码块划分
视频编码中编码块划分

编码块的代码实现

如何用代码实现编码块的划分呢?这里以H.265/HEVC的官方参考软件HM-16.18的实现为例进行讲解,HM编译安装方法参考。首先通过递归实现CTU到CU的四叉树划分,然后再CU里遍历每种PU划分方式选择最优PU划分模式。具体实现代码在TEncCu.cpp里的xCompressCU()函数里,为了节省篇幅只保留了相关部分代码。

#if AMP_ENC_SPEEDUP
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth DEBUG_STRING_FN_DECLARE(sDebug_), PartSize eParentPartSize )
#else
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth )
#endif
{
  ......
              // 选择帧间模式, NxN, 2NxN, and Nx2N
       if( rpcBestCU->getSlice()->getSliceType() != I_SLICE )
      {
         // 2Nx2N, NxN

         if(!( (rpcBestCU->getWidth(0)==8) && (rpcBestCU->getHeight(0)==8) ))  //!<块尺寸不能为8x8
        {
           if( uiDepth == sps.getLog2DiffMaxMinCodingBlockSize() && doNotBlockPu)   //!<只有当CU是最小块时才进行NxN划分
          { //!<计算帧间NxN模式代价并比较
             xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug)   );
             rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
          }
        }

         if(doNotBlockPu)
        { //!<计算帧间Nx2N模式代价并比较
           xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_Nx2N DEBUG_STRING_PASS_INTO(sDebug) );
           rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
           if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_Nx2N )
          {
             doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
          }
        }
         if(doNotBlockPu)
        {
           xCheckRDCostInter     ( rpcBestCU, rpcTempCU, SIZE_2NxN DEBUG_STRING_PASS_INTO(sDebug) );
           rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
           if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxN)
          {
             doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
          }
        }
 //!<尝试非对称尺寸
         //! Try AMP (SIZE_2NxnU, SIZE_2NxnD, SIZE_nLx2N, SIZE_nRx2N)
         if(sps.getUseAMP() && uiDepth < sps.getLog2DiffMaxMinCodingBlockSize() )
        {
#if AMP_ENC_SPEEDUP
           Bool bTestAMP_Hor = false, bTestAMP_Ver = false;

#if AMP_MRG
           Bool bTestMergeAMP_Hor = false, bTestMergeAMP_Ver = false;

           deriveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver, bTestMergeAMP_Hor, bTestMergeAMP_Ver);
#else
           deriveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver);
#endif

           //! Do horizontal AMP
           if ( bTestAMP_Hor )
          {
             if(doNotBlockPu)
            {//2NxnU划分
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU DEBUG_STRING_PASS_INTO(sDebug) );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnU )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
             if(doNotBlockPu)
            {//2NxnD划分
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD DEBUG_STRING_PASS_INTO(sDebug) );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnD )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
          }
#if AMP_MRG
           else if ( bTestMergeAMP_Hor )
          {
             if(doNotBlockPu)
            {
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU DEBUG_STRING_PASS_INTO(sDebug), true );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnU )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
             if(doNotBlockPu)
            {
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD DEBUG_STRING_PASS_INTO(sDebug), true );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnD )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
          }
#endif

           //! Do horizontal AMP
           if ( bTestAMP_Ver )
          {
             if(doNotBlockPu)
            {//nLx2N划分
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N DEBUG_STRING_PASS_INTO(sDebug) );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_nLx2N )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
             if(doNotBlockPu)
            {
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nRx2N DEBUG_STRING_PASS_INTO(sDebug) );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
            }
          }
#if AMP_MRG
           else if ( bTestMergeAMP_Ver )
          {
             if(doNotBlockPu)
            {//进行nLx2N划分
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N DEBUG_STRING_PASS_INTO(sDebug), true );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
               if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_nLx2N )
              {
                 doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
              }
            }
             if(doNotBlockPu)
            {
               xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nRx2N DEBUG_STRING_PASS_INTO(sDebug), true );
               rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
            }
          }
          ..................
//帧内模式
xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) ); //!<计算帧内2Nx2N预测模式代价
         rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
         if( uiDepth == sps.getLog2DiffMaxMinCodingBlockSize() )
        {
           if( rpcTempCU->getWidth(0) > ( 1 << sps.getQuadtreeTULog2MinSize() ) )
          {//只有最小尺寸8x8才进行NxN划分,因为按四叉树               //划分所以CU边长都是2的幂次
             xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug)   );  //!<计算帧内NxN预测模式代价
             rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
          }
        }
      }
................................................
    //递归进行四叉树划分
   xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth );
............................
    ..................
}

在TypeDef.h里定义了各种划分模式:

//!<PU的8种划分方式,4种对称方式,4种非对称方式
enum PartSize
{
SIZE_2Nx2N           = 0,           ///< symmetric motion partition, 2Nx2N
SIZE_2NxN           = 1,           ///< symmetric motion partition, 2Nx N
SIZE_Nx2N           = 2,           ///< symmetric motion partition,   Nx2N
SIZE_NxN             = 3,           ///< symmetric motion partition,   Nx N
SIZE_2NxnU           = 4,           ///< asymmetric motion partition, 2Nx( N/2) + 2Nx(3N/2)
SIZE_2NxnD           = 5,           ///< asymmetric motion partition, 2Nx(3N/2) + 2Nx( N/2)
SIZE_nLx2N           = 6,           ///< asymmetric motion partition, ( N/2)x2N + (3N/2)x2N
SIZE_nRx2N           = 7,           ///< asymmetric motion partition, (3N/2)x2N + ( N/2)x2N
NUMBER_OF_PART_SIZES = 8
};

来源:公众号——Video Coding

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(1)

相关推荐

发表回复

登录后才能评论