VTM6.0中xCheckRDCostMergeTriangle2Nx2N()函數是幀間預測技術中Merge模式中TPM技術的主函數的入口,想要了解TPM在代碼中的實現,這個函數是啃定了,我只是大概看了一下該函數的大致上的流程以及其中三角Merge列表的構建、加權預測、60種三角形組合模式的率失真代價選擇的大致過程。今天將所看的代碼分享給大家,如過代碼中有理解錯的地方,還請指正,謝謝大家~,關於三角預測的原理部分可以參考博客:H.266/VVC相關技術學習筆記:幀間預測中的TPM技術(Triangle partition Mode)
還是老套路,用文字過一下該函數的大致流程,再將加了註釋的代碼附在後面:
1、首先進行一些初始化的操作,定義一些變量、存放預測值以及殘差的buffer、RD代價列表之類
2、定義三角Merge單向預測列表,是從常規的Merge列表中派生出來的,然後調用getTriangleMergeCandidates()函數,獲取三角形預測的單向Merge候選列表,關於該函數的細節,請參考博客: H.266/VVC代碼學習筆記16:VTM6.0中的getTriangleMergeCandidates()函數
3、遍歷6個三角Merge候選 ,運動補償出每個候選的單向預測值。
4、遍歷60種組合模式(2×6×5),進行亮度分量的SAD——Cost,進行粗選操作,通過調用weightedTriangleBlk()函數計算亮度分量下的每種三角組合下的加權預測值,關於該函數細節具體參考博客:H.266/VVC代碼學習筆記17:VTM6.0中的xWeightedTriangleBlk()函數
每遍歷一個組合模式,需要去通過調用updateCandList()函數更新三角模式的RD候選模式列表triangleRdModeList以及三角代價列表tianglecandCostList。遍歷更新完之後,triangleRdModeList列表中的候選的Cost是從小到大排序的,RD列表的最大長度爲3,也就是從60組合中RDCost出3個較優的候選
5、使用SATD-cost限制候選列表的數量,在上一步SAD粗選完之後對RD候選列表的容量左進一步的限制,減少RD候選列表的範圍,在3個裏再縮減,可以縮減爲0,1,2,3中任意一個可能
6、對那些經過SAD以及進一步數量限制篩選出的較優的候選進行遍歷,逐個計算出相應色度分量的加權預測值。(注意:在之前用亮度分量進行篩選,篩選完之後再去對相應的色度進行加權。個人認爲考慮到兩方面的原因:一是亮度分量的可靠性更強,用亮度篩選出的候選比較真實可靠。二是爲了節省複雜度,只需要用亮度分量去進行篩選就足夠了,不需要再去求色度分量)
7、開始正式的HAD_Cost的細選過程。遍歷需要進行SATD細選的列表中的每一個候選,進行SATD_Cost的比較,得到cost最小的那個候選作爲最優的三角模式。然後再去與其餘的Merge模式去進行率失真代價的比較,選出最優的一種三角組合模式,再在外層與其他Merge模式一起去競爭。這其中調用xEncodeInterResidual()函數進行幀間殘差編碼,這與之前的xCheckRDCostMerge2Nx2N()函數的學習中所調用的一樣:H.266/VVC代碼學習筆記14:xCheckRDCostMerge2Nx2N()函數
以上就是xCheckRDCostMergeTriangle2Nx2N()函數的大致流程,關於這部分代碼的詳細註釋附在下面,供大家參考使用:
void EncCu::xCheckRDCostMergeTriangle2Nx2N( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
{
const Slice &slice = *tempCS->slice;
const SPS &sps = *tempCS->sps;
if (slice.getMaxNumTriangleCand() < 2)
return;
CHECK( slice.getSliceType() != B_SLICE, "Triangle mode is only applied to B-slices" );
tempCS->initStructData( encTestMode.qp, encTestMode.lossless );//初始化數據結構
bool trianglecandHasNoResidual[TRIANGLE_MAX_NUM_CANDS];//60個三角形預測候選是否有殘差
for( int mergeCand = 0; mergeCand < TRIANGLE_MAX_NUM_CANDS; mergeCand++ )
{
trianglecandHasNoResidual[mergeCand] = false;//都初始化爲false
}
bool bestIsSkip = false;
uint8_t numTriangleCandidate = TRIANGLE_MAX_NUM_CANDS;//三角候選數量爲60,P1預測塊有6種MV的選擇*P2預測塊有5種MV的選擇*兩種分區劃分模式(對角和反對角線)=60
uint8_t triangleNumMrgSATDCand = TRIANGLE_MAX_NUM_SATD_CANDS;//三角預測用於SATD比較的候選數量最大爲3
PelUnitBuf triangleBuffer[TRIANGLE_MAX_NUM_UNI_CANDS];//三角形的緩存單元數最大爲6,三角預測的單向Merge列表長度爲6
PelUnitBuf triangleWeightedBuffer[TRIANGLE_MAX_NUM_CANDS];//三角加權預測值的緩存爲60個
static_vector<uint8_t, TRIANGLE_MAX_NUM_CANDS> triangleRdModeList;//三角形預測的RD的模式列表,首先粗選選出的組合候選放入到最終的RDCost候選列表中
static_vector<double, TRIANGLE_MAX_NUM_CANDS> tianglecandCostList;//三角形預測的候選代價列表,首先粗選選出的組合候選的代價放入到最終的代價列表中
uint8_t numTriangleCandComb = slice.getMaxNumTriangleCand() * (slice.getMaxNumTriangleCand() - 1) * 2;//三角預測模式的組合數量:6*5*2=60
DistParam distParam;
const bool useHadamard = !encTestMode.lossless && !tempCS->slice->getDisableSATDForRD();
m_pcRdCost->setDistParam( distParam, tempCS->getOrgBuf().Y(), m_acMergeBuffer[0].Y(), sps.getBitDepth( CHANNEL_TYPE_LUMA ), COMPONENT_Y, useHadamard );
const UnitArea localUnitArea( tempCS->area.chromaFormat, Area( 0, 0, tempCS->area.Y().width, tempCS->area.Y().height) );//開闢編碼CU的空間
const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda(encTestMode.lossless);
MergeCtx triangleMrgCtx;//定義三角Merge單向預測列表,是從常規的Merge列表中派生出來的
{
CodingUnit cu( tempCS->area );
cu.cs = tempCS;
cu.predMode = MODE_INTER;//預測模式爲幀間模式
cu.slice = tempCS->slice;//指向當前的Slice
cu.tileIdx = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
cu.triangle = true;//是三角預測模式
cu.mmvdSkip = false;//不是MMVDSkip模式
cu.GBiIdx = GBI_DEFAULT;//雙向預測權重索引
PredictionUnit pu( tempCS->area );//爲當前的CU開闢臨時緩存空間
pu.cu = &cu;
pu.cs = tempCS;
pu.regularMergeFlag = false;//不是regular模式
//獲取三角形預測的單向Merge候選列表
PU::getTriangleMergeCandidates( pu, triangleMrgCtx );
const uint8_t maxNumTriangleCand = pu.cs->slice->getMaxNumTriangleCand();//三角預測的最大候選數
//遍歷6個三角Merge候選
for (uint8_t mergeCand = 0; mergeCand < maxNumTriangleCand; mergeCand++)
{
triangleBuffer[mergeCand] = m_acMergeBuffer[mergeCand].getBuf(localUnitArea);
triangleMrgCtx.setMergeInfo( pu, mergeCand );//設置三角Merge候選的初始信息
PU::spanMotionInfo( pu, triangleMrgCtx );
if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( pu ) ) ) )
{
// Do not use this mode
tempCS->initStructData( encTestMode.qp, encTestMode.lossless );//初始化數據結構
return;
}
m_pcInterSearch->motionCompensation( pu, triangleBuffer[mergeCand] );//運動補償獲得每個候選的單向預測值
}
}
triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, numTriangleCandComb);//需要進行SATDCost比較的三角Merge候選的數量
{
CodingUnit &cu = tempCS->addCU( tempCS->area, partitioner.chType );//定義CU
partitioner.setCUData( cu );//初始化CU
cu.slice = tempCS->slice;//定義片的臨時緩存
cu.tileIdx = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
cu.skip = false;
cu.predMode = MODE_INTER;//幀間模式
cu.transQuantBypass = encTestMode.lossless;
cu.chromaQpAdj = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
cu.qp = encTestMode.qp;
cu.triangle = true;//三角模式爲真
cu.mmvdSkip = false;//不使用Skip模式
cu.GBiIdx = GBI_DEFAULT;//雙向預測權重索引爲默認值
PredictionUnit &pu = tempCS->addPU( cu, partitioner.chType );//添加PU
if( abs(g_aucLog2[cu.lwidth()] - g_aucLog2[cu.lheight()]) >= 2 )//如果當前CU的寬高比大於等於2,則只有一種劃分方向,共30種模式
{
numTriangleCandidate = 30;
}
else
{
numTriangleCandidate = TRIANGLE_MAX_NUM_CANDS;//最大三角候選的數量爲60
}
numTriangleCandidate = min(numTriangleCandidate, numTriangleCandComb);//從三角候選數量和三角的結合候選數量中挑一個最小的
//遍歷60種候選,進行SAD——Cost,進行粗選操作
for( uint8_t mergeCand = 0; mergeCand < numTriangleCandidate; mergeCand++ )
{
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir;//獲取三角的劃分方向
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;//獲取第一個三角分區的候選索引,6箇中選一個
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;//獲取第二個三角分區的候選索引,剩下的5箇中選一個
//將上面三個變量傳給當前PU
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true;//Merge模式可用
pu.regularMergeFlag = false;//regularMerge模式不可用
triangleWeightedBuffer[mergeCand] = m_acTriangleWeightedBuffer[mergeCand].getBuf( localUnitArea );//三角權重的臨時緩存
triangleBuffer[candIdx0] = m_acMergeBuffer[candIdx0].getBuf( localUnitArea );//第一個分區的預測值的臨時緩存
triangleBuffer[candIdx1] = m_acMergeBuffer[candIdx1].getBuf( localUnitArea );//第二個分區的預測值的臨時緩存
//亮度分量加權過程
m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_LUMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
distParam.cur = triangleWeightedBuffer[mergeCand].Y();//當前組合模式的三角亮度權重
Distortion uiSad = distParam.distFunc( distParam );//計算當前組合模式失真的SAD
uint32_t uiBitsCand = m_triangleIdxBins[splitDir][candIdx0][candIdx1];//當前組合模式的總比特數(殘差+頭信息+語法元素)
double cost = (double)uiSad + (double)uiBitsCand * sqrtLambdaForFirstPass;//計算SAD代價
updateCandList( mergeCand, cost, triangleRdModeList, tianglecandCostList
, triangleNumMrgSATDCand );//更新RD候選列表,將cost較小的放入列表中
}
//遍歷更新完之後,triangleRdModeList列表中的候選的Cost是從小到大排序的,RD列表的最大長度爲3,也就是從60組合中RDCost出3個較優的候選
// limit number of candidates using SATD-costs
//使用SATD-cost限制候選列表的數量,在上一步SAD粗選完之後對RD候選列表的容量左進一步的限制,減少RD候選列表的範圍,在3個裏再縮減,可以縮減爲0,1,2,3中任意一個可能
for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
{
//如果當前RD候選的Cost大於一定的閾值,則就跳出,將前面那些在Cost閾值範圍之內的候選保留在列表中,後面那些超出Cost閾值的候選被排除在外
if( tianglecandCostList[i] > MRG_FAST_RATIO * tianglecandCostList[0] || tianglecandCostList[i] > getMergeBestSATDCost() )
{
triangleNumMrgSATDCand = i;//STAD候選的數量保留爲那些在閾值之內的候選數量
break;
}
}
// perform chroma weighting process
//執行色度加權過程
//對那些經過SAD以及進一步數量限制篩選出的較優的候選進行遍歷(注意:用亮度分量進行篩選,篩選完之後再去對相應的色度進行加權。個人認爲考慮到兩方面的原因:一是亮度分量的可靠性更強,用亮度篩選出的候選比較真實可靠。二是爲了節省複雜度,只需要用亮度分量去進行篩選就足夠了,不需要再去求色度分量)
for( uint8_t i = 0; i < triangleNumMrgSATDCand; i++ )
{
uint8_t mergeCand = triangleRdModeList[i];//RdModeList列表中的第幾個候選
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir;//劃分方向
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;//分區一的候選
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;//分區二的候選
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true;//標誌Merge模式可用
pu.regularMergeFlag = false;//標誌regular_Merge模式不可用
//色度分量加權過程
m_pcInterSearch->weightedTriangleBlk( pu, splitDir, CHANNEL_TYPE_CHROMA, triangleWeightedBuffer[mergeCand], triangleBuffer[candIdx0], triangleBuffer[candIdx1] );
}
//再初始化數據結構
tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
}
//重新獲取三角預測模式的數量,縮減之後的triangleNumMrgSATDCand和RD列表的尺寸中取最小
triangleNumMrgSATDCand = min(triangleNumMrgSATDCand, (uint8_t)triangleRdModeList.size());
m_bestModeUpdated = tempCS->useDbCost = bestCS->useDbCost = false;//是否是最優的Cost
//這裏開始正式的HAD_Cost的細選過程
{
uint8_t iteration;
uint8_t iterationBegin = 0;//迭代從0開始
if (encTestMode.lossless)//如果測試的候選模式無損,則迭代一次
{
iteration = 1;
}
else//否則迭代兩次
{
iteration = 2;
}
//外層的迭代次數
for (uint8_t noResidualPass = iterationBegin; noResidualPass < iteration; ++noResidualPass)
{
//遍歷需要進行SATD細選的列表中的每一個候選,進行SATD_Cost的比較,得到cost最小的那個候選作爲最優的三角模式。然後再去與其餘的Merge模式去進行率失真代價的比較
for( uint8_t mrgHADIdx = 0; mrgHADIdx < triangleNumMrgSATDCand; mrgHADIdx++ )
{
uint8_t mergeCand = triangleRdModeList[mrgHADIdx];
if ( ( (noResidualPass != 0) && trianglecandHasNoResidual[mergeCand] )
|| ( (noResidualPass == 0) && bestIsSkip ) )
{
continue;
}
bool splitDir = m_triangleModeTest[mergeCand].m_splitDir;
uint8_t candIdx0 = m_triangleModeTest[mergeCand].m_candIdx0;
uint8_t candIdx1 = m_triangleModeTest[mergeCand].m_candIdx1;
CodingUnit &cu = tempCS->addCU(tempCS->area, partitioner.chType);
partitioner.setCUData(cu);//設置CU的數據
cu.slice = tempCS->slice;
cu.tileIdx = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
cu.skip = false;
cu.predMode = MODE_INTER;
cu.transQuantBypass = encTestMode.lossless;
cu.chromaQpAdj = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
cu.qp = encTestMode.qp;
cu.triangle = true;
cu.mmvdSkip = false;
cu.GBiIdx = GBI_DEFAULT;
PredictionUnit &pu = tempCS->addPU(cu, partitioner.chType);
pu.triangleSplitDir = splitDir;
pu.triangleMergeIdx0 = candIdx0;
pu.triangleMergeIdx1 = candIdx1;
pu.mergeFlag = true;
pu.regularMergeFlag = false;
//重新設置三角預測運動信息
PU::spanTriangleMotionInfo(pu, triangleMrgCtx, splitDir, candIdx0, candIdx1 );
if( m_pcEncCfg->getMCTSEncConstraint() && ( !( MCTSHelper::checkMvBufferForMCTSConstraint( *cu.firstPU ) ) ) )
{
// Do not use this mode
tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
return;
}
//將之前得到的加權後的預測值直接拷貝過來。用於下面的殘差編碼(其中就進行了HAD的cost的計算以及比較)
tempCS->getPredBuf().copyFrom( triangleWeightedBuffer[mergeCand] );//得到三角預測的最終預測值
//編碼幀間殘差
xEncodeInterResidual( tempCS, bestCS, partitioner, encTestMode, noResidualPass, ( noResidualPass == 0 ? &trianglecandHasNoResidual[mergeCand] : NULL ) );
if (m_pcEncCfg->getUseFastDecisionForMerge() && !bestIsSkip)
{
bestIsSkip = bestCS->getCU(partitioner.chType)->rootCbf == 0;
}
tempCS->initStructData(encTestMode.qp, encTestMode.lossless);
}// end loop mrgHADIdx
}
}
if ( m_bestModeUpdated && bestCS->cost != MAX_DOUBLE )//選出最優的一種三角組合模式,再在外層與其他Merge模式一起去競爭
{
xCalDebCost( *bestCS, partitioner );
}
}