getInterMergeCandidates()函數是幀間預測技術中一個非常重要的函數,它是Merge列表的構造函數,所有的Merge模式都要先構造一個Merge候選列表,然後根據不同的模式在此regularMerge列表的基礎上去延伸出相對應的Merge列表,就比如MMVD的Merge列表需要在常規Merge列表的基礎上去選擇兩個初始MV,然後構造MMVDMerge。
普通Merge列表的構造方式大致如下:
我們可以根據下圖所示去構造候選列表:
①首先檢查空域相鄰的塊是否有可用的MV,並檢查冗餘,檢查順序順序爲:A1——>B1——>B0——>A0——>B2。空域相鄰最多選出4個候選。
② 再檢查時域相鄰的塊,並檢查冗餘,檢查順序如下:BR——>CT。時域相鄰最多選出1個候選。時域的候選具體可以參考TMVP技術:H.266/VVC相關技術學習筆記:VVC中TMVP技術(時域Merge列表的構建)
③如果空域時域檢查完後,Merge列表仍沒有填滿5個(如果已經填滿了5個,則不使用HMVP方法),則採用基於歷史的參考空間候選技術(HMVP),同時也要檢查冗餘,該技術我後續會講一下的。H.266/VVC相關技術學習筆記:幀間預測中的HMVP技術(基於歷史的MV預測)
④當上述的三種填充方法填充完之後,如果填滿剛好5個或者仍不足5個,則使用成對組合平均法去填充Merge列表,同時仍要檢查冗餘,但是成對組合平均法最多隻能填充一個候選。該技術具體如下:
H.266/VVC相關技術學習筆記:VVC中成對平均候選技術(用於Merge列表的構建)
⑤如果上述4種方法都沒能填滿候選列表,則最後用零矢量(0,0)補滿候選列表。
關於幀間預測技術的基本原理的原文鏈接如下:
H.266/VVC相關技術學習筆記:視頻編碼中幀間預測技術的基本原理
具體的在VTM6.0版本中的代碼如下:關於整個列表構造的流程我都在代碼中有詳細的註釋,其中若有什麼不對的地方歡迎大家指正~
//構造幀間常規Merge的候選列表函數的入口,其餘的Merge模式的候選列表都要基於該常規Merge列表去構造或者直接使用常規的Merge列表
void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx,
int mmvdList,
const int& mrgCandIdx )
{
const CodingStructure &cs = *pu.cs;
const Slice &slice = *pu.cs->slice;
const uint32_t maxNumMergeCand = slice.getMaxNumMergeCand();//獲取Merge列表的長度(即最大候選數量)
const bool canFastExit = pu.cs->pps->getLog2ParallelMergeLevelMinus2() == 0;//候選快速退出
#if !JVET_L0090_PAIR_AVG
// this variable is unused if remove HEVC combined candidates
bool isCandInter[MRG_MAX_NUM_CANDS];
#endif
//這裏對候選列表中的每一個候選的MV進行初始化
for (uint32_t ui = 0; ui < maxNumMergeCand; ++ui)
{
#if !JVET_L0090_PAIR_AVG
isCandInter[ui] = false;
#endif
mrgCtx.GBiIdx[ui] = GBI_DEFAULT;//該參數是雙向預測權重的索引,每個候選都有5種權重去選擇
mrgCtx.interDirNeighbours[ui] = 0;//每個候選相鄰塊的幀間預測方向:前向、後向、雙向
mrgCtx.mrgTypeNeighbours [ui] = MRG_TYPE_DEFAULT_N;//每個候選MV所在相鄰塊的Merge類型
//每個Merge列表中有12個mvFieldNeighbours,總共存儲12個候選MV,每個候選相鄰塊可以提供一個前向MV和後向MV,可以自適應地選擇使用單向或者是雙向預測。
mrgCtx.mvFieldNeighbours[(ui << 1) ].refIdx = NOT_VALID;//每個候選所在相鄰塊區域的前向參考幀索引,標識該相鄰塊的前向參考幀是哪一個
mrgCtx.mvFieldNeighbours[(ui << 1) + 1].refIdx = NOT_VALID;//每個候選所在相鄰塊區域的後向參考幀索引,標識該相鄰塊的後向參考幀是哪一個
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[ui] = false;//MV是否使用1/2像素精度
#endif
}
mrgCtx.numValidMergeCand = maxNumMergeCand;//最大的可用候選MV的數量
// compute the location of the current PU 當前PU的中心位置
int cnt = 0;//候選計數器,記下已經列入候選列表中的候選綜述
const Position posLT = pu.Y().topLeft();//當前塊空域左上相鄰位置
const Position posRT = pu.Y().topRight();//當前塊空域右上相鄰位置
const Position posLB = pu.Y().bottomLeft();//當前塊空域左下相鄰位置
MotionInfo miAbove, miLeft, miAboveLeft, miAboveRight, miBelowLeft;//定義五個空域相鄰位置的運動信息
//首先遍歷left左邊(A1)相鄰塊
const PredictionUnit* puLeft = cs.getPURestricted( posLB.offset( -1, 0 ), pu, pu.chType );//獲取左塊
const bool isAvailableA1 = puLeft && isDiffMER( pu, *puLeft ) && pu.cu != puLeft->cu && CU::isInter( *puLeft->cu );//標誌A1塊是否可用
//如果左塊可用
if( isAvailableA1 )
{
miLeft = puLeft->getMotionInfo( posLB.offset(-1, 0) );//獲取左塊的運動信息
#if !JVET_L0090_PAIR_AVG
isCandInter[cnt] = true;
#endif
// 獲取幀間預測方向
mrgCtx.interDirNeighbours[cnt] = miLeft.interDir;//獲取候選相鄰塊的預測方向賦給當前編碼塊
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = miLeft.useAltHpelIf;//使用1/2像素精度
#endif
mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeft->cu->GBiIdx : GBI_DEFAULT;//如果是雙向預測,則自適應選擇權重係數,否則默認idx爲2
// 從左塊獲取前向MV
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miLeft.mv[0], miLeft.refIdx[0]);
if (slice.isInterB())//如果是B幀,則獲取後向MV
{
mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miLeft.mv[1], miLeft.refIdx[1]);
}
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;
}
//提前終止
if (cnt == maxNumMergeCand)//如果當前候選總數等於最大的Merge候選數
{
return;
}
//遍歷above上邊(B1)相鄰塊,以下流程同左塊的處理一樣,可以參考左塊的處理過程
const PredictionUnit *puAbove = cs.getPURestricted( posRT.offset( 0, -1 ), pu, pu.chType );
bool isAvailableB1 = puAbove && isDiffMER( pu, *puAbove ) && pu.cu != puAbove->cu && CU::isInter( *puAbove->cu );
if( isAvailableB1 )
{
miAbove = puAbove->getMotionInfo( posRT.offset( 0, -1 ) );
if( !isAvailableA1 || ( miAbove != miLeft ) )
{
#if !JVET_L0090_PAIR_AVG
isCandInter[cnt] = true;
#endif
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAbove.interDir;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = miAbove.useAltHpelIf;
#endif
// get Mv from Above
mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAbove->cu->GBiIdx : GBI_DEFAULT;
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAbove.mv[0], miAbove.refIdx[0] );
if( slice.isInterB() )
{
mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAbove.mv[1], miAbove.refIdx[1] );
}
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
int spatialCandPos = cnt;
// above right
//遍歷右上(B0)相鄰塊,以下流程同左塊的處理一樣,可以參考左塊的處理過程
const PredictionUnit *puAboveRight = cs.getPURestricted( posRT.offset( 1, -1 ), pu, pu.chType );
bool isAvailableB0 = puAboveRight && isDiffMER( pu, *puAboveRight ) && CU::isInter( *puAboveRight->cu );
if( isAvailableB0 )
{
miAboveRight = puAboveRight->getMotionInfo( posRT.offset( 1, -1 ) );
if( !isAvailableB1 || ( miAbove != miAboveRight ) )
{
#if !JVET_L0090_PAIR_AVG
isCandInter[cnt] = true;
#endif
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAboveRight.interDir;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = miAboveRight.useAltHpelIf;
#endif
// get Mv from Above-right
mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveRight->cu->GBiIdx : GBI_DEFAULT;
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAboveRight.mv[0], miAboveRight.refIdx[0] );
if( slice.isInterB() )
{
mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveRight.mv[1], miAboveRight.refIdx[1] );
}
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
//left bottom
//遍歷左下(A0)相鄰塊,以下流程同左塊的處理一樣,可以參考左塊的處理過程
const PredictionUnit *puLeftBottom = cs.getPURestricted( posLB.offset( -1, 1 ), pu, pu.chType );
bool isAvailableA0 = puLeftBottom && isDiffMER( pu, *puLeftBottom ) && CU::isInter( *puLeftBottom->cu );
if( isAvailableA0 )
{
miBelowLeft = puLeftBottom->getMotionInfo( posLB.offset( -1, 1 ) );
if( !isAvailableA1 || ( miBelowLeft != miLeft ) )
{
#if !JVET_L0090_PAIR_AVG
isCandInter[cnt] = true;
#endif
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miBelowLeft.interDir;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = miBelowLeft.useAltHpelIf;
#endif
mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeftBottom->cu->GBiIdx : GBI_DEFAULT;
// get Mv from Bottom-Left
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miBelowLeft.mv[0], miBelowLeft.refIdx[0] );
if( slice.isInterB() )
{
mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miBelowLeft.mv[1], miBelowLeft.refIdx[1] );
}
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
// above left
//遍歷左上(B2)相鄰塊,以下流程同左塊的處理一樣,可以參考左塊的處理過程
if ( cnt < 4 )
{
const PredictionUnit *puAboveLeft = cs.getPURestricted( posLT.offset( -1, -1 ), pu, pu.chType );
bool isAvailableB2 = puAboveLeft && isDiffMER( pu, *puAboveLeft ) && CU::isInter( *puAboveLeft->cu );
if( isAvailableB2 )
{
miAboveLeft = puAboveLeft->getMotionInfo( posLT.offset( -1, -1 ) );
if( ( !isAvailableA1 || ( miLeft != miAboveLeft ) ) && ( !isAvailableB1 || ( miAbove != miAboveLeft ) ) )
{
#if !JVET_L0090_PAIR_AVG
isCandInter[cnt] = true;
#endif
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAboveLeft.interDir;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = miAboveLeft.useAltHpelIf;
#endif
mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveLeft->cu->GBiIdx : GBI_DEFAULT;
// get Mv from Above-Left
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAboveLeft.mv[0], miAboveLeft.refIdx[0] );
if( slice.isInterB() )
{
mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveLeft.mv[1], miAboveLeft.refIdx[1] );
}
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;
}
}
}
// early termination
if (cnt == maxNumMergeCand)
{
return;
}
//這裏開始時域相鄰塊的遍歷,遍歷時域上參考塊的右下(RB)以及中心(CT)位置的運動信息
if (slice.getEnableTMVPFlag() && (pu.lumaSize().width + pu.lumaSize().height > 12))
{
//>> MTK colocated-RightBottom右下同位塊
// offset the pos to be sure to "point" to the same position the uiAbsPartIdx would've pointed to
Position posRB = pu.Y().bottomRight().offset( -3, -3 );//右下角同位塊的位置:在當前亮度PU右下角的位置上再做適當的偏移
const PreCalcValues& pcv = *cs.pcv;
Position posC0;
Position posC1 = pu.Y().center();//中心同位塊爲當前亮度PU的中心位置
bool C0Avail = false;
if (((posRB.x + pcv.minCUWidth) < pcv.lumaWidth) && ((posRB.y + pcv.minCUHeight) < pcv.lumaHeight))
{
int posYInCtu = posRB.y & pcv.maxCUHeightMask;
if (posYInCtu + 4 < pcv.maxCUHeight)
{
posC0 = posRB.offset(4, 4);
C0Avail = true;
}
}
Mv cColMv;//當前PU的同位塊的MV
int iRefIdx = 0;//參考幀索引
int dir = 0;//預測方向
unsigned uiArrayAddr = cnt;//時域候選在候選隊列中的位置
bool bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC0, cColMv, iRefIdx ) )
|| getColocatedMVP( pu, REF_PIC_LIST_0, posC1, cColMv, iRefIdx );
//該函數進行時域上MV的比例伸縮調整,最終得到比例縮放過的當前PU的運動信息。
if (bExistMV)
{
dir |= 1;//和1按位或
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr].setMvField(cColMv, iRefIdx);//獲取同位塊的前向MV
}
if (slice.isInterB())//如果是B幀,則獲取同位塊的後向MV
{
bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_1, posC0, cColMv, iRefIdx ) )
|| getColocatedMVP( pu, REF_PIC_LIST_1, posC1, cColMv, iRefIdx );
if (bExistMV)
{
dir |= 2;
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr + 1].setMvField(cColMv, iRefIdx);
}
}
if( dir != 0 )
{
bool addTMvp = true;//爲true,則將TMVP(時域候選)添加到Merge列表中
if( addTMvp )
{
mrgCtx.interDirNeighbours[uiArrayAddr] = dir;
#if !JVET_L0090_PAIR_AVG
isCandInter [uiArrayAddr] = true;
#endif
mrgCtx.GBiIdx[uiArrayAddr] = GBI_DEFAULT;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[uiArrayAddr] = false;
#endif
if (mrgCandIdx == cnt && canFastExit)
{
return;
}
cnt++;//候選總數+1
}
}
}
// early termination
if (cnt == maxNumMergeCand)
{
return;
}
int maxNumMergeCandMin1 = maxNumMergeCand - 1;//空域和時域候選塊遍歷之後最多選出5個候選
if (cnt != maxNumMergeCandMin1)//如果當前候選列表中的候選數不足5個,則繼續接下來的MVP填充,HMVP以及空間平均候選
{
bool isAvailableSubPu = false;
unsigned subPuMvpPos = 0;
#if JVET_L0090_PAIR_AVG
bool isShared = false;
//接下進行HMVP候選的構造過程,然後將HMVP中的可用的候選從後向前依次填充到Merge列表中
bool bFound = addMergeHMVPCand(cs, mrgCtx, canFastExit
, mrgCandIdx
, maxNumMergeCandMin1, cnt
, spatialCandPos
, isAvailableSubPu, subPuMvpPos
, CU::isIBC(*pu.cu)
, isShared
);
#else
bool bFound = addMergeHMVPCand(slice, mrgCtx, isCandInter, canFastExit
, (mmvdList != 0 && mrgCandIdx != -1) ? (const int)mrgCandIdxIBC : mrgCandIdx
, maxNumMergeCandMin1, cnt, cnt, isAvailableSubPu, subPuMvpPos
, mmvdList
);
#endif
if (bFound)
{
return;
}
}
#if JVET_L0090_PAIR_AVG
// pairwise-average candidates
//HMVP之後進行成對平均候選(即組合平均候選)
{
//如果Merge列表還沒有被填滿,則進行組合平均候選過程
if (cnt > 1 && cnt < maxNumMergeCand)
{
mrgCtx.mvFieldNeighbours[cnt * 2].setMvField( Mv( 0, 0 ), NOT_VALID );//則用零MV初始化當前候選塊的前向MV
mrgCtx.mvFieldNeighbours[cnt * 2 + 1].setMvField( Mv( 0, 0 ), NOT_VALID );//則用零MV初始化當前候選塊的後向MV
// calculate average MV for L0 and L1 seperately
//計算L0和L1的平均MV
unsigned char interDir = 0;
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[cnt] = (mrgCtx.useAltHpelIf[0] == mrgCtx.useAltHpelIf[1]) ? mrgCtx.useAltHpelIf[0] : false;
#endif
for( int refListId = 0; refListId < (slice.isInterB() ? 2 : 1); refListId++ )
{
const short refIdxI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].refIdx;//第一個候選塊的MV索引
const short refIdxJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].refIdx;//第二個候選塊的MV索引
// both MVs are invalid, skip
if( (refIdxI == NOT_VALID) && (refIdxJ == NOT_VALID) )
{
continue;
}
interDir += 1 << refListId;
// both MVs are valid, average these two MVs
if( (refIdxI != NOT_VALID) && (refIdxJ != NOT_VALID) )
{
const Mv& MvI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;//第一個候選塊的MV
const Mv& MvJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;//第二個候選塊的MV
// average two MVs
//平均兩個候選塊的MV得到一個新的MV
Mv avgMv = MvI;
avgMv += MvJ;
roundAffineMv(avgMv.hor, avgMv.ver, 1);
mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );//組合後的新的MV就賦給當前的候選MV
}
// only one MV is valid, take the only one MV
//如果僅有其中一個候選塊MV是有效的,則只用該MV
else if( refIdxI != NOT_VALID )
{
Mv singleMv = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;
mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxI );
}
else if( refIdxJ != NOT_VALID )
{
Mv singleMv = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;
mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxJ );
}
}
mrgCtx.interDirNeighbours[cnt] = interDir;
if( interDir > 0 )
{
cnt++;//總的MV候選數量+1
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
}
#endif
uint32_t uiArrayAddr = cnt;
int iNumRefIdx = slice.isInterB() ? std::min(slice.getNumRefIdx(REF_PIC_LIST_0), slice.getNumRefIdx(REF_PIC_LIST_1)) : slice.getNumRefIdx(REF_PIC_LIST_0);
int r = 0;
int refcnt = 0;
//如果還沒有填充滿Merge列表,則使用零MV去進行最後的填充
while (uiArrayAddr < maxNumMergeCand)
{
#if !JVET_L0090_PAIR_AVG
isCandInter [uiArrayAddr ] = true;
#endif
mrgCtx.interDirNeighbours [uiArrayAddr ] = 1;
mrgCtx.GBiIdx [uiArrayAddr ] = GBI_DEFAULT;
mrgCtx.mvFieldNeighbours [uiArrayAddr << 1].setMvField(Mv(0, 0), r);
#if JVET_O0057_ALTHPELIF
mrgCtx.useAltHpelIf[uiArrayAddr] = false;
#endif
if (slice.isInterB())
{
mrgCtx.interDirNeighbours [ uiArrayAddr ] = 3;
mrgCtx.mvFieldNeighbours [(uiArrayAddr << 1) + 1].setMvField(Mv(0, 0), r);
}
if ( mrgCtx.interDirNeighbours[uiArrayAddr] == 1 && pu.cs->slice->getRefPic(REF_PIC_LIST_0, mrgCtx.mvFieldNeighbours[uiArrayAddr << 1].refIdx)->getPOC() == pu.cs->slice->getPOC())
{
mrgCtx.mrgTypeNeighbours[uiArrayAddr] = MRG_TYPE_IBC;
}
uiArrayAddr++;
if (refcnt == iNumRefIdx - 1)
{
r = 0;
}
else
{
++r;
++refcnt;
}
}
mrgCtx.numValidMergeCand = uiArrayAddr;
}