CTU劃分
如今的視頻編碼都是基於塊進行的,將一幀視頻劃分紅不一樣的塊,而後對每一個塊再分別進行編碼處理。因爲原始YUV格式視頻有3個通道,一個亮度通道Y,兩個色度通道UV,這裏塊的劃分以亮度通道Y爲例,色度通道相似。c++
在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,劃分方式以下。
less
可見1個64x64的CTB能夠劃分爲4個32x32的CTB,每一個32x32的CTB又能夠劃分爲4個16x16的CTB,每一個16x16的CTB可劃分爲4個8x8的CTB。最小的亮度CTB爲8x8,因此最多隻能劃分3層。四叉樹劃分這種劃分方式的表示也很容易,只要給出其劃分深度就能知道塊的大小。
ide
CTB的劃分在有的圖像上還有問題,例如對於高清視頻,每幀圖像分辨率爲1920x1080,若劃分爲64x64的塊,則每行有30塊,而1080/64=16.875不是整數,因此最下邊1行塊必須進一步劃分,使整幀圖像劃分爲30x17CTBs,以下圖所示。函數
PU劃分
當CTU劃分紅CU後,每一個CU還要進行預測、變換等。當進行預測時,CU還要繼續劃分爲不一樣的預測單元(Predict Unit,PU)。(關於預測是怎麼進行的會在後面的文章中介紹,如今只須要知道預測分爲幀內預測和幀間預測兩類)。PU是進行預測的基本單元,一個CU內的全部PU的預測方式相同都爲幀內預測或都爲幀間預測。且CU到PU只容許一層劃分,其劃分方式以下。ui
一共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。atom
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都是方形的。spa
上圖中紅色邊緣的塊就是劃分紅的TU,TU最大尺寸爲64x64,最小爲4x4,可是因爲DCT變換運算的最大尺寸爲32x32,因此64x64的TU隱含着必須進一步劃分紅4個32x32的TU。一樣若亮度TU爲4x4其對應的2個色度TU也是4x4而不進一步劃分。
.net
下面是一個劃分實例。
代碼實現
如何用代碼實現編碼塊的劃分呢?這裏以H.265/HEVC的官方參考軟件HM-16.18的實現爲例進行講解,HM編譯安裝方法參考。首先經過遞歸實現CTU到CU的四叉樹劃分,而後再CU裏遍歷每種PU劃分方式選擇最優PU劃分模式。具體實現代碼在TEncCu.cpp裏的xCompressCU()函數裏,爲了節省篇幅只保留了相關部分代碼。
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth DEBUG_STRING_FN_DECLARE(sDebug_), PartSize eParentPartSize )
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth )
{
......
// 選擇幀間模式, 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() )
{
Bool bTestAMP_Hor = false, bTestAMP_Ver = false;
Bool bTestMergeAMP_Hor = false, bTestMergeAMP_Ver = false;
deriveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver, bTestMergeAMP_Hor, bTestMergeAMP_Ver);
deriveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver);
//! 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;
}
}
}
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;
}
}
}
//! 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 );
}
}
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 );
............................
..................
}
//!<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(gh_17eb8f3e0fe7)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。