视频编码中编码块划分

编码块划分

CTU划分

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

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

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

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

CTB的划分在有的图像上还有问题,例如对于高清视频,每帧图像分辨率为1920x1080,若划分为64x64的块,则每行有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处进行划分,例如32x32的块进行2NxnU划分会分成一个32x8的块和一个32x24的块,H.265规定只有在亮度CU尺寸大于等于16x16时才允许不对称划分。

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

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最大尺寸为64x64,最小为4x4,但是由于DCT变换运算的最大尺寸为32x32,所以64x64的TU隐含着必须进一步划分成4个32x32的TU。同样若亮度TU为4x4其对应的2个色度TU也是4x4而不进一步划分。

下面是一个划分实例。

代码实现

如何用代码实现编码块的划分呢?这里以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

 

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值