HEVC学习(十二) —— CU的最终划分

相信会有不少人对如何确定CU最终的划分有所困惑(包括我在内,刚开始接触时也不知道该怎么做),我觉得很大的一个原因就是CU是递归划分的,这就导致在寻找确定最佳分割位置时比较困难。

 

其实,解决问题的办法说难也不难,关键在于思路的转换,既然对于xCompressCU中是如何保存划分模式的觉得难以理解,何不跳出这个小圈子寻找新的方法呢?

 

我们可以从解码器的角度来考虑,因为最终编码后的码流是要经过解码器解码的,解码器事先也是不知道CU到底最终是如何划分的。因此,可以推断,编码器必然会保存下这个信息,至少是提示信息。不妨参考encodeCU这个函数的实现,因为它是最终将信息编码成码流的函数。该函数调用的是xEncodeCU来完成实际工作,截取它当中其中一段对我们有用的代码:

  1. // We need to split, so don't try these modes.   
  2.   if(!bSliceStart&&( uiRPelX < pcSlice->getSPS()->getPicWidthInLumaSamples() ) && ( uiBPelY < pcSlice->getSPS()->getPicHeightInLumaSamples() ) )  
  3.   {  
  4.     m_pcEntropyCoder->encodeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );  
  5.   }  
// We need to split, so don't try these modes.
  if(!bSliceStart&&( uiRPelX < pcSlice->getSPS()->getPicWidthInLumaSamples() ) && ( uiBPelY < pcSlice->getSPS()->getPicHeightInLumaSamples() ) )
  {
    m_pcEntropyCoder->encodeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );
  }


不错,encodeSplitFlag就是用于编码CU分割信息的函数,它的实现如下:

  1. // Split mode   
  2. Void TEncEntropy::encodeSplitFlag( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth, Bool bRD )  
  3. {  
  4.   if( bRD )  
  5.   {  
  6.     uiAbsPartIdx = 0;  
  7.   }  
  8.   if( !bRD )  
  9.   {  
  10.     if( pcCU->getLastCUSucIPCMFlag() && pcCU->getIPCMFlag(uiAbsPartIdx) )  
  11.     {  
  12.       return;  
  13.     }  
  14.   }  
  15.   
  16.   m_pcEntropyCoderIf->codeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );  
  17. }  
// Split mode
Void TEncEntropy::encodeSplitFlag( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth, Bool bRD )
{
  if( bRD )
  {
    uiAbsPartIdx = 0;
  }
  if( !bRD )
  {
    if( pcCU->getLastCUSucIPCMFlag() && pcCU->getIPCMFlag(uiAbsPartIdx) )
    {
      return;
    }
  }

  m_pcEntropyCoderIf->codeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );
}


对我们有用的函数是最后的codeSplitFlag函数,它的实现如下:

  1. Void TEncSbac::codeSplitFlag   ( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth )  
  2. {  
  3.   if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )  
  4.     return;  
  5.     
  6.   UInt uiCtx           = pcCU->getCtxSplitFlag( uiAbsPartIdx, uiDepth );  
  7.   UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;  
  8.     
  9.   assert( uiCtx < 3 );  
  10.   m_pcBinIf->encodeBin( uiCurrSplitFlag, m_cCUSplitFlagSCModel.get( 0, 0, uiCtx ) );  
  11.   DTRACE_CABAC_VL( g_nSymbolCounter++ )  
  12.   DTRACE_CABAC_T( "\tSplitFlag\n" )  
  13.   return;  
  14. }  
Void TEncSbac::codeSplitFlag   ( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth )
{
  if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )
    return;
  
  UInt uiCtx           = pcCU->getCtxSplitFlag( uiAbsPartIdx, uiDepth );
  UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;
  
  assert( uiCtx < 3 );
  m_pcBinIf->encodeBin( uiCurrSplitFlag, m_cCUSplitFlagSCModel.get( 0, 0, uiCtx ) );
  DTRACE_CABAC_VL( g_nSymbolCounter++ )
  DTRACE_CABAC_T( "\tSplitFlag\n" )
  return;
}


可以看到最为有用的一句:

  1. UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;  
  UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;


也就是说,通过判断pcCU->getDepth( uiAbsPartIdx )是否大于uiDepth来确定当前CU是否还要继续分割,后者我们知道,是当前CU的深度,那么前者呢?自然就是在xCompressCU中确定下来的当前CU的最佳分割模式。至此,我们最想获得的信息就在这里。

 

经过上述分析后,一句话总结获取CU最佳划分的方法:在HM中调用完xCompressCU之后(至少也应该是compressCU调用完它之后,此时最佳PU为m_ppcBestCU[0]),在调用encodeCU之前(也可以之后,这个只要保证pcCU没被修改过即可),对compressCU的参数pcCU进行类似语句: pcCU->getDepth( uiAbsPartIdx ),即可获得Z order为uiAbsPartIdx的4x4块的深度,如果把整个CU每个4x4块的深度确定下来,那么它的划分自然也就确定下来了。

 

下图是我打印出的某一块CU在最大尺寸64 x 64,最大深度为4(即depth = 0 ~ 3)的某一种划分情况,每个数字代表对应位置的4x4块的实际深度,红线给出CU的最佳划分。

(转载请注明出处http://blog.csdn.net/hevc_cjl/article/details/8275260。)

相信会有不少人对如何确定CU最终的划分有所困惑(包括我在内,刚开始接触时也不知道该怎么做),我觉得很大的一个原因就是CU是递归划分的,这就导致在寻找确定最佳分割位置时比较困难。

 

其实,解决问题的办法说难也不难,关键在于思路的转换,既然对于xCompressCU中是如何保存划分模式的觉得难以理解,何不跳出这个小圈子寻找新的方法呢?

 

我们可以从解码器的角度来考虑,因为最终编码后的码流是要经过解码器解码的,解码器事先也是不知道CU到底最终是如何划分的。因此,可以推断,编码器必然会保存下这个信息,至少是提示信息。不妨参考encodeCU这个函数的实现,因为它是最终将信息编码成码流的函数。该函数调用的是xEncodeCU来完成实际工作,截取它当中其中一段对我们有用的代码:

  1. // We need to split, so don't try these modes.   
  2.   if(!bSliceStart&&( uiRPelX < pcSlice->getSPS()->getPicWidthInLumaSamples() ) && ( uiBPelY < pcSlice->getSPS()->getPicHeightInLumaSamples() ) )  
  3.   {  
  4.     m_pcEntropyCoder->encodeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );  
  5.   }  
// We need to split, so don't try these modes.
  if(!bSliceStart&&( uiRPelX < pcSlice->getSPS()->getPicWidthInLumaSamples() ) && ( uiBPelY < pcSlice->getSPS()->getPicHeightInLumaSamples() ) )
  {
    m_pcEntropyCoder->encodeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );
  }


不错,encodeSplitFlag就是用于编码CU分割信息的函数,它的实现如下:

  1. // Split mode   
  2. Void TEncEntropy::encodeSplitFlag( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth, Bool bRD )  
  3. {  
  4.   if( bRD )  
  5.   {  
  6.     uiAbsPartIdx = 0;  
  7.   }  
  8.   if( !bRD )  
  9.   {  
  10.     if( pcCU->getLastCUSucIPCMFlag() && pcCU->getIPCMFlag(uiAbsPartIdx) )  
  11.     {  
  12.       return;  
  13.     }  
  14.   }  
  15.   
  16.   m_pcEntropyCoderIf->codeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );  
  17. }  
// Split mode
Void TEncEntropy::encodeSplitFlag( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth, Bool bRD )
{
  if( bRD )
  {
    uiAbsPartIdx = 0;
  }
  if( !bRD )
  {
    if( pcCU->getLastCUSucIPCMFlag() && pcCU->getIPCMFlag(uiAbsPartIdx) )
    {
      return;
    }
  }

  m_pcEntropyCoderIf->codeSplitFlag( pcCU, uiAbsPartIdx, uiDepth );
}


对我们有用的函数是最后的codeSplitFlag函数,它的实现如下:

  1. Void TEncSbac::codeSplitFlag   ( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth )  
  2. {  
  3.   if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )  
  4.     return;  
  5.     
  6.   UInt uiCtx           = pcCU->getCtxSplitFlag( uiAbsPartIdx, uiDepth );  
  7.   UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;  
  8.     
  9.   assert( uiCtx < 3 );  
  10.   m_pcBinIf->encodeBin( uiCurrSplitFlag, m_cCUSplitFlagSCModel.get( 0, 0, uiCtx ) );  
  11.   DTRACE_CABAC_VL( g_nSymbolCounter++ )  
  12.   DTRACE_CABAC_T( "\tSplitFlag\n" )  
  13.   return;  
  14. }  
Void TEncSbac::codeSplitFlag   ( TComDataCU* pcCU, UInt uiAbsPartIdx, UInt uiDepth )
{
  if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )
    return;
  
  UInt uiCtx           = pcCU->getCtxSplitFlag( uiAbsPartIdx, uiDepth );
  UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;
  
  assert( uiCtx < 3 );
  m_pcBinIf->encodeBin( uiCurrSplitFlag, m_cCUSplitFlagSCModel.get( 0, 0, uiCtx ) );
  DTRACE_CABAC_VL( g_nSymbolCounter++ )
  DTRACE_CABAC_T( "\tSplitFlag\n" )
  return;
}


可以看到最为有用的一句:

  1. UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;  
  UInt uiCurrSplitFlag = ( pcCU->getDepth( uiAbsPartIdx ) > uiDepth ) ? 1 : 0;


也就是说,通过判断pcCU->getDepth( uiAbsPartIdx )是否大于uiDepth来确定当前CU是否还要继续分割,后者我们知道,是当前CU的深度,那么前者呢?自然就是在xCompressCU中确定下来的当前CU的最佳分割模式。至此,我们最想获得的信息就在这里。

 

经过上述分析后,一句话总结获取CU最佳划分的方法:在HM中调用完xCompressCU之后(至少也应该是compressCU调用完它之后,此时最佳PU为m_ppcBestCU[0]),在调用encodeCU之前(也可以之后,这个只要保证pcCU没被修改过即可),对compressCU的参数pcCU进行类似语句: pcCU->getDepth( uiAbsPartIdx ),即可获得Z order为uiAbsPartIdx的4x4块的深度,如果把整个CU每个4x4块的深度确定下来,那么它的划分自然也就确定下来了。

 

下图是我打印出的某一块CU在最大尺寸64 x 64,最大深度为4(即depth = 0 ~ 3)的某一种划分情况,每个数字代表对应位置的4x4块的实际深度,红线给出CU的最佳划分。

(转载请注明出处http://blog.csdn.net/hevc_cjl/article/details/8275260。)

猜你喜欢

转载自blog.csdn.net/chenxiuli0810/article/details/8491745