现在的视频编码都是基于块进行的,将一帧视频划分成不同的块,然后对每个块再分别进行编码处理。由于原始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,如下图所示。
当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。
当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里定义了各种划分模式
//!感兴趣的扫码关注微信公众号Video Coding