H.265的块划分技术

时间:2024-03-16 07:36:01

灵活的块划分技术给H.265带来了很高的性能提升,相比于H.264而言,在参考软件中利用递归的方式实现了块的四叉树划分,

H.265标准中对于编码单元有四个概念CTU, CU,PU,TU

概念定义如下:

(1)   编码树单元(CTU)和编码树块(CTB)结构:

在之前的标准中,编码层的核心是宏块,一个宏块包含一个16×16的亮度块,以及对于常用的4:2:0采样格式来说还包含两个8×8的色度块;而在HEVC中类似的结构为编码树单元(CTU),其尺寸由编码器进行指定且可以比传统的宏块大。一个CTU包含一个亮度CTB和两个对应的色度CTB及句法元素。一个L×L的亮度CTB的L可以设置为16,32或者64。一般来说,L越大,可以获得越好的压缩性能。HEVC支持使用树结构和类四叉树的标志来将CTB划分成更小的块。

(2)   编码单元(CU)和编码块(CB):

CTU的四叉树句法指定了它所属的亮度和色度CB的尺寸和位置。四叉树的根与CTU相关联。因此,亮度CB的最大尺寸为其所属的亮度CTB的尺寸。对于一个CTU来说,其亮度CB和色度CB的划分标志都是使用的同一个。一个亮度CB通常和两个色度CB及它们相关的句法共同组成一个编码单元(CU)。一个CTB可能只包含一个CU,也可能被划分成多个CU,每个CU包含着与之相关联的预测单元(PU)和变换单元(TU)。

(3)   预测单元(PU)和预测块(PB):

决定一个图像区域是以帧间还是帧内方式进行预测是在CU层进行的。一个PU划分结构的根在CU层。根据基本的预测类型,亮度CB和色度CB可以继续进行划分并利用其它的亮度PB和色度PB进行预测。HEVC支持多种PB尺寸,最大为64×64到最小4×4。

(4)   变换单元(TU)和变换块(TB):

预测残差以块变换的方式进行编码。一个变换单元树结构的根在CU层。亮度CB残差的尺寸可能与亮度变换块TB的尺寸相等,也可能会被划分成更小的亮度TB。色度CB的情况也是一样的。定义了与离散余弦变换(DCT)类似的整数变换的基本函数提供给尺寸为4×4,8×8,16×16,32×32的TB。对于尺寸为4×4且残差由帧内预测得到的TB来说,一种以离散正弦变换(DST)为基础的整数变换可供采用。


      划分关系为:CTU可以四叉树划分为四个CU也可以不划分,这是根据率失真代价决定的,

                              一个CU可以划分成多个PU,265有8中划分模式(如下图),具体选哪一种,在划分中是根据率失真代价决定的。

             H.265的块划分技术


      变换单元TU是在CU的基础上划分的,跟PU没有任何关系,采用四叉树划分方式,具体划分有率失真代价决定,下图给出了某个CU划分成TU的结构。

H.265的块划分技术

:率失真代价表达式为:D+lambda*R   D为量化失真,R为编码的比特数,lambda为乘子,由配置文件决定,这部分属于率失真优化技术范畴,率失真优化问题是编码界公认的难题,至今还没有很好的解决,lambda的选取直接影响着编码性能,这方面我的导师(朱策老师)在这方面有较大的成就,有不少相关的论文,想了解的可以去我们实验室(视频通信与智能计算实验室)网站下载:http://www.avc2-lab.net/,愿意从事视频编解码研究的,可以考虑来我们实验室,朱策老师因在视频通信和视频压缩领域的贡献获得IEEE FELLOW和IET FELLOW 的荣誉。

在HM代码中实现块划分的函数为:compressCtu,具体调用xcompressCU完成划分模式的决策,具体函数过程解析转载一遍比较详细的博客。

现将内容转载如下

转载地址:http://blog.sina.com.cn/s/blog_9f945ced0101p3ut.html

1.xCompressCU 函数的调用


在编码一个片的函数CompressSlice 函数中有这个几行代码

      // run CU encoder
      m_pcCuEncoder->compressCU( pcCU ); 就是一个LCU的编码,包括CU的划分,PU模式的决定,TU的划分
      m_pcCuEncoder->encodeCU( pcCU );  这里可以看出来pcCU是存储着需要编码的信息。

2.进入这个函数

Void TEncCu::xcompressCU( TComDataCU*& rpcCU )
{
   // initialize CU data
   m_ppcBestCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() ); 

//这里的 m_ppcBestCU[0]和m_ppcTempCU[0]都是记录模式信息的,至于如何记录我们一点点的来看。由//于是递归的划分CU,这里容易绕晕啊。

   m_ppcTempCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() );

   // analysis of CU
   xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 ); 

//这里进入xCompressCU函数,这里的第三个参数是 CU的分割深度。64x64的深度为0,以此类推,
//最小的CU为8x8深度为3。至于如何将最优的模式信息赋值到pcCU里,我们后面跟踪代码可以看到。

这里首先要弄清一个概念,就是CU的划分是递归的

第一步  CU的大小为64x64, 搜索最优的PU的划分得到最优的预测模式,进行TU的划分 
 
第二步  CU的大小为32x32, 第一个CU(按之子扫描顺序) 同上

第三步  CU的大小为16x16, 第一个CU  同上

第四步  CU的大小为8x8,   以此进行第一个CU,第二个CU,第三个CU和第四个CU的PU和TU的划分和最优    模式的选择。这里面完成每个CU后将这个的RD与前面进行累加。

第五步  返回到CU为 16x16的CU,将其RD-COST 与第四部记录的四个8X8的CU的RD-cost进行比较。决定    了这个16X16的最优的CU划分及最优的CU下的PU和CU的划分。

第六步 CU的大小为 16X16,第二个CU。重复 第四步第五步,可以得到第二个最优的16x16的CU的划分和    PU TU 的模式。 同时将改第二个CU的最优的RD-COST与5步得到的第一个16x16的CURD-COST     进行累加。

第七步 :同理完成第三个和第四个的16X16的CU的最优的划分和模式的选择,将其RD-COST累加。这样              我们就得到了分割为16X16最佳的RD-cost。

第八步 :返回到第二步,比较第一个32X32CU的RD-cost 和 分割为4个16X16的CU的RD-cost,得了第一     个32X32 CU的分割信息和最优的模式。

第九步:同理完成第二个32x32,第三个32X32和第四个32X32的最优的划分和模式选择。通过记录和累加  每一个32x32的RD-cost,与64x64的CU的RD-Cost进行比较。我们得到了最终的CU 的划分和每个  CU的最优的PU的划分及PU的预测模式以及TU的划分。
}

3.xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 )函数

现在就按照2中的过程来看一看这函数的流程:

首先进入该函数,CU的大小已经确定了为64x64,进行PU的划分和TU的划分

PU的划分将按下列顺序进行尝试:

帧间


xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N, bFMD )           skip 2NX2N

xCheckRDCostMerge2Nx2N( rpcBestCU, rpcTempCU, &earlyDetectionSkipMode );  Merge 2NX2N

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N, bFMD );   2NX2N  

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_NxN, bFMD  );   NXN  划分为4个PU

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_Nx2N, bFMD  );  Nx2N  划分为2个PU  

xCheckRDCostInter      ( rpcBestCU, rpcTempCU, SIZE_2NxN, bFMD  );

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU, bFMD );

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD, bFMD );   4种非对称的划分

xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N, bFMD );

这里在调用这个帧内RDcost函数时,rpcBestCU中始终存放的是当前CU下最优的PU的模式和划分信息的CU结构体。

帧内:

xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N );  2NX2N的划分。 PU为CU的大小。

xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN   );  当CU为最小的CU的时候,将尝试 分割为4个
        PU。

在RD-cost的函数的最后有这样一个函数

xCheckBestMode(rpcBestCU, rpcTempCU, uiDepth);

接下来就该递归的分割LCU,下面看看分割的判断
   if( bSubBranch && bTrySplitDQP && uiDepth < g_uiMaxCUDepth - g_uiAddCUDepth ) 条件为真进行分割
     {
  UChar       uhNextDepth         = uiDepth+1;
        TComDataCU* pcSubBestPartCU     = m_ppcBestCU[uhNextDepth];
      TComDataCU* pcSubTempPartCU     = m_ppcTempCU[uhNextDepth];

      for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ )
       {
       pcSubBestPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP );          
      pcSubTempPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP );  

      // 注意这里是用rpcTempCU对pcSubBestPartCU和pcSubTemPartC进行初始化,函数到此 rpcBestCU里面//还是当前CU64x64大小最优的PU信息。
//接下来是递归的调用自己

if ( rpcBestCU->isIntra(0) )
          {
            xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth, SIZE_NONE );
      //进入这个函数后,CU的大小为32X32 iPartUnitIdx 这里pcSubBestPartCU就和rpcBestCU一样始终放//着当前最优的预测的信息。
          }
             else
          {
              xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth, rpcBestCU->getPartitionSize(0) );
                }

  显然这里要做的是将4个最优的32X32CU的RD-cost累加。注意这里有一个语句是

  rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth ); 

跟进去一看这个函数的几个代码就知道这个语句就是完成4个划分的最优的信息的累加,以便和为分割前的CU的最优的预测模式的RD-cost进行比较也就是m_ppcBestCU进行比较。
      }
    }  
分割的循环

完成了四个分割,即2中的过程2中的第九步。这里就 rpcTempCU存储的是CU的4个划分的信息。

可以看见 xCheckBestMode( rpcBestCU, rpcTempCU, uiDepth);函数将未分割的CU和分割之后的4个CU进行比
较来决定是否进行CU的划分。

}

但是奇怪的是如何和最开始CompressCU的参数pcCU的参数联系上。这里有个函数。在最后面

  rpcBestCU->copyToPic(uiDepth);  

这个函数就是将得到的最优的PU的模式和预测信息,及CU的划分的信息赋值到pcCU中。

进去看这个函数,

TComDataCU*& rpcCU = m_pcPic->getCU( m_uiCUAddr );第一个语句中的rpcCU正好是CompressCU的参数。
下面给出某一次划分的示意图: