VVC帧间预测中扩展merge模式详解
扩展的Mrege模式
merge是在HEVC中提出的一种MV预测技术,利用时域和空域相邻块的MV对当前块的MV进行预测。HEVC中merge模式会为当前PU建立一个MV候选列表,列表中有5个候选列表,通过遍历这5个MV,计算率失真代价值最小的MV作为当前PU的MV,并在编码是编码该MV在候选列表中的索引值,解码端通过相同的方式构建候选列表,通过索引值就可以获得该MV。
HEVC中利用时域和空域相邻块构建MV列表,且列表中只有5个候选MV,VVC对此进行了扩展,候选类表merge list中最多可以有6个候选MV,候选MV有以下5中类型,构造的顺序如下图所示:
一、空域候选列表构建
空域候选列表的建立方式和HEVC中一样,如下图所示,B、C、D、E、F是当前编码块A的相邻块。VVC中空域最多提供4个候选MV。即最多使用这5个候选块中的4个候选块的运动信息,候选列表按照B、C、D、E、F的顺序建立,其中F是替补,只有前4个相邻块有一个或者多个不存在时(例如不在同一个Slice或者Tlie或者使用帧内编码时)才将F加入候选列表。当候选列表中加入B之后,后面新加入候选项时需要进行冗余性检验,以免新加入的候选项的运动信息和已有项相同。同时,为了减少计算复杂度,冗余性检查不会和已有的每一项进行比较,比较的方式如下面第二张图所示
二、时域候选列表的构建
时域候选列表的构建方式和HEVC一样,与空域的构建方式不一样,时域候选列表不能直接使用候选块的运动信息,需要根据位置关系进行相应比例地伸缩。如下图所示
其中tb表示当前图像和参考图像之间的距离(用POC度量),td是同位图像和其参考图像之间的距离。时域最多提供一个MV,计算方法如下所示:
如下图C0位置的候选MV由其同位CU的MV伸缩得到,如果C0的同位MV不可用,则用C1的同位CU的MV代替
三、基于历史信息构建MVP(HMVP)
HMVP的merge候选项在空域候选和时域候选之后加入merge候选列表。HMVP候选项来自于一个先进先出的表,这个表通过已经编码块的运动信息构建,每到新的一行CTU时,这个表就要重置,即清空操作。每遇到一个帧间编码的CU(非子块)时,它相关的运动信息就会加到这个表的最后一项成为一个新的HMVP候选项。
在VTM5中,HMVP表的大小为6。每当插入一个新的候选项时,首先要进行冗余性检查即检查待插入的项的运动信息和表中已有项的运动信息是否相同,如果不相同,则按照先进先出的规则进行插入操作;如果相同,则将相同的HMVP从维护的FIFO(先进先出)表中移除,其后所有的项都向前移动一位。
HMVP表构建好了之后,就可以将其中的HMVP候选项插入merge list中,对HMVP表中的候选项进行冗余性检查(即检查该项和merge list中的空域、时域候选项运动信息是否相同),检查通过即插入merge list中。
为了减少冗余性检查操作,进行了一下的简化操作:
- 用于构建merge list的HMVP候选项数量设为 (N<=4)?M:(8-N),其中N表示merge list中已有项的数目,M表示HMVP表中候选项数目
- 一旦merge list中候选项的数目达到了最大允许候选项数目减一(即6-1=5),则停止从HMVP生成merge候选项的过程
四、逐对的平均MVP
逐对平均候选项是指对merge list中的已有候选项按照预定义的组队关系计算平均值,预定义的最对关系为:
{(0,1),(0,2),(1,2),(0,3),(1,3),(2,3)},这里面的数字代表候选项在merge list中的索引值。L0和L1的平均向量分别单独计算,如果两个向量在某个列表中都有效则即使它们指向不同的参考图像也计算它们的平均,如果只有一个想来那个在某个列表中有效则直接它,如果某个列表中没有有效运动向量则该类表无效。
五、零向量
如果在计算完平均候选项之后 merge list还没有被填满,则用零向量把merge list填满
六、总结
以上五步就是merge list的构建过程,merge list的列表长度为3,首选寻找空域候选项(最多选择4个),然后寻找时域候选项(最多选择一个),然后选择基于历史信息的候选项,最后是平均候选项,如果merge list通过以上几个步骤,还未被填满,则使用两向量进行填充。在上面这些步骤当中,如果在中途某个步骤当中merge list被填满则,不进行后面的步骤。
- VVC中的merge list构建与HEVC中merge list构建最大的不同在于,HEVC中的merge list只包括空域候选项和时域候选项
- 当前CU的merge list构建完毕后,遍历它的6个候选项进行率失真代价计算,选择率失真代价最小的候选项直接作为当前CU的MV
七、VVC中扩展的Merge模式代码分析:
该文件所在位置
CommonLib->Source Files->PU->getInterMergeCandidates(……)
void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx,
int mmvdList,
const int& mrgCandIdx )
{
const CodingStructure &cs = *pu.cs; //当前CU所处的编码结构Slice or Tile
const Slice &slice = *pu.cs->slice; //当前PU中的Slice
const uint32_t maxNumMergeCand = slice.getPicHeader()->getMaxNumMergeCand(); //该变量表示Merge候选列表的最大长度
for (uint32_t ui = 0; ui < maxNumMergeCand; ++ui)
{
mrgCtx.BcwIdx[ui] = BCW_DEFAULT;
mrgCtx.interDirNeighbours[ui] = 0;
mrgCtx.mrgTypeNeighbours [ui] = MRG_TYPE_DEFAULT_N;
mrgCtx.mvFieldNeighbours[(ui << 1) ].refIdx = NOT_VALID;
mrgCtx.mvFieldNeighbours[(ui << 1) + 1].refIdx = NOT_VALID;
mrgCtx.useAltHpelIf[ui] = false;
}
mrgCtx.numValidMergeCand = maxNumMergeCand;
// compute the location of the current PU 计算当前PU的位置
int cnt = 0; //该变量用于记录当前Merge候选列表中MV的个数
const Position posLT = pu.Y().topLeft(); //当前PU左上方的位置
const Position posRT = pu.Y().topRight(); //当前PU右上方的位置
const Position posLB = pu.Y().bottomLeft(); //当前PU左下方的位置
MotionInfo miAbove, miLeft, miAboveLeft, miAboveRight, miBelowLeft; //记录当前PU5个相邻块的运动信息
//1. above 检查邻块C运动信息是否可用
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));
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAbove.interDir; //该变量表示merge list中已候选项的数目
mrgCtx.useAltHpelIf[cnt] = miAbove.useAltHpelIf;
// get Mv from Above从右上方块获得运动矢量
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAbove->cu->BcwIdx : BCW_DEFAULT; //,puAbove->cu->BcwIdx表示当前PU所属的CU维护的HMVP表中候选数目
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)
{
return;
}
cnt++;
}
// early termination 如果当前merge list中候选的数目已经达到了最大值,则停止merge list列表的构建
if (cnt == maxNumMergeCand)
{
return;
}
//2.left 检查邻块B运动信息是否可用
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);
if (isAvailableA1)
{
miLeft = puLeft->getMotionInfo(posLB.offset(-1, 0));
if (!isAvailableB1 || (miAbove != miLeft))
{
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miLeft.interDir;
mrgCtx.useAltHpelIf[cnt] = miLeft.useAltHpelIf;
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeft->cu->BcwIdx : BCW_DEFAULT;
// get Mv from Left
mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miLeft.mv[0], miLeft.refIdx[0]);
if (slice.isInterB())
{
mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miLeft.mv[1], miLeft.refIdx[1]);
}
if (mrgCandIdx == cnt)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
//3. above right 检查邻块D运动信息是否可用
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 ) )
{
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAboveRight.interDir;
mrgCtx.useAltHpelIf[cnt] = miAboveRight.useAltHpelIf;
// get Mv from Above-right
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveRight->cu->BcwIdx : BCW_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)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
//4.left bottom检查邻块E运动信息是否可用
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 ) )
{
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miBelowLeft.interDir;
mrgCtx.useAltHpelIf[cnt] = miBelowLeft.useAltHpelIf;
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puLeftBottom->cu->BcwIdx : BCW_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)
{
return;
}
cnt++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
// 5.above left 当空域候选cnt不够时,检查邻块F运动信息是否可用
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 ) ) )
{
// get Inter Dir
mrgCtx.interDirNeighbours[cnt] = miAboveLeft.interDir;
mrgCtx.useAltHpelIf[cnt] = miAboveLeft.useAltHpelIf;
mrgCtx.BcwIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3) ? puAboveLeft->cu->BcwIdx : BCW_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)
{
return;
}
cnt++;
}
}
}
// early termination
if (cnt == maxNumMergeCand)
{
return;
}
if (slice.getPicHeader()->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 );
const PreCalcValues& pcv = *cs.pcv;
Position posC0;
Position posC1 = pu.Y().center();
bool C0Avail = false;
#if JVET_O1143_MV_ACROSS_SUBPIC_BOUNDARY
bool boundaryCond = ((posRB.x + pcv.minCUWidth) < pcv.lumaWidth) && ((posRB.y + pcv.minCUHeight) < pcv.lumaHeight);
SubPic curSubPic = pu.cs->slice->getPPS()->getSubPicFromPos(pu.lumaPos());
if (curSubPic.getTreatedAsPicFlag())
{
boundaryCond = ((posRB.x + pcv.minCUWidth) <= curSubPic.getSubPicRight() &&
(posRB.y + pcv.minCUHeight) <= curSubPic.getSubPicBottom());
}
if (boundaryCond)
#else
if (((posRB.x + pcv.minCUWidth) < pcv.lumaWidth) && ((posRB.y + pcv.minCUHeight) < pcv.lumaHeight))
#endif
{
int posYInCtu = posRB.y & pcv.maxCUHeightMask;
if (posYInCtu + 4 < pcv.maxCUHeight)
{
posC0 = posRB.offset(4, 4);
C0Avail = true;
}
}
Mv cColMv;
int iRefIdx = 0;
int dir = 0;
unsigned uiArrayAddr = cnt;
bool bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC0, cColMv, iRefIdx, false ) )
|| getColocatedMVP( pu, REF_PIC_LIST_0, posC1, cColMv, iRefIdx, false );
if (bExistMV)
{
dir |= 1;
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr].setMvField(cColMv, iRefIdx);
}
if (slice.isInterB())
{
bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_1, posC0, cColMv, iRefIdx, false ) )
|| getColocatedMVP( pu, REF_PIC_LIST_1, posC1, cColMv, iRefIdx, false );
if (bExistMV)
{
dir |= 2;
mrgCtx.mvFieldNeighbours[2 * uiArrayAddr + 1].setMvField(cColMv, iRefIdx);
}
}
if( dir != 0 )
{
bool addTMvp = true;
if( addTMvp )
{
mrgCtx.interDirNeighbours[uiArrayAddr] = dir;
mrgCtx.BcwIdx[uiArrayAddr] = BCW_DEFAULT;
mrgCtx.useAltHpelIf[uiArrayAddr] = false;
if (mrgCandIdx == cnt)
{
return;
}
cnt++;
}
}
}
// early termination
if (cnt == maxNumMergeCand)
{
return;
}
int maxNumMergeCandMin1 = maxNumMergeCand - 1;
if (cnt != maxNumMergeCandMin1)
{
bool isGt4x4 = true;
bool bFound = addMergeHMVPCand(cs, mrgCtx, mrgCandIdx, maxNumMergeCandMin1, cnt
, isAvailableA1, miLeft, isAvailableB1, miAbove
, CU::isIBC(*pu.cu)
, isGt4x4
);
if (bFound)
{
return;
}
}
// pairwise-average candidates 对merge list前4个候选MV按照顺序进行逐对平均
{
if (cnt > 1 && cnt < maxNumMergeCand)
{
mrgCtx.mvFieldNeighbours[cnt * 2].setMvField( Mv( 0, 0 ), NOT_VALID );
mrgCtx.mvFieldNeighbours[cnt * 2 + 1].setMvField( Mv( 0, 0 ), NOT_VALID );
// calculate average MV for L0 and L1 seperately
unsigned char interDir = 0;
mrgCtx.useAltHpelIf[cnt] = (mrgCtx.useAltHpelIf[0] == mrgCtx.useAltHpelIf[1]) ? mrgCtx.useAltHpelIf[0] : false;
for( int refListId = 0; refListId < (slice.isInterB() ? 2 : 1); refListId++ )
{
const short refIdxI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].refIdx;
const short refIdxJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].refIdx;
// 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;
const Mv& MvJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;
// average two MVs
Mv avgMv = MvI;
avgMv += MvJ;
roundAffineMv(avgMv.hor, avgMv.ver, 1);
mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );
}
// only one MV is valid, take the only one 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++;
}
}
// early termination
if( cnt == maxNumMergeCand )
{
return;
}
}
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;
while (uiArrayAddr < maxNumMergeCand)
{
mrgCtx.interDirNeighbours [uiArrayAddr ] = 1;
mrgCtx.BcwIdx [uiArrayAddr ] = BCW_DEFAULT;
mrgCtx.mvFieldNeighbours [uiArrayAddr << 1].setMvField(Mv(0, 0), r);
mrgCtx.useAltHpelIf[uiArrayAddr] = false;
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;
}
【参考文献】
JVET-N1002
JVET-L0266
JVET-L0090
更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!