基于子块的时间运动矢量预测
VVC支持基于子块的时间运动矢量预测(SbTMVP)方法。 类似于HEVC中的时间运动矢量预测(temporal motion vector prediction, TMVP),SbTMVP使用同位图片中的运动场来改善当前图片中CU的运动矢量预测和Merge模式。 SbTVMP和TMVP使用的相同的同位图片。 (同位块:相邻已编码图像中对应位置的块)
SbTMVP在以下两个主要方面与TMVP不同:
- TMVP预测CU级别的运动,但SbTMVP预测子CU级别的运动;
- TMVP直接从同位图片的同位块获取时间运动矢量(同位块是相对于当前CU的右下或中心块),而SbTMVP在从同位图片中获取时间运动信息之前应用了运动偏移 ,其中运动偏移来自当前CU的空间相邻块的运动矢量。
SbTMVP中使用的子CU大小固定为8x8,SbTMVP模式仅适用于宽度和高度都大于或等于8的CU。另外,SbTMVP Merge候选的编码逻辑与其他Merge候选的编码逻辑相同,即,对于P或B片中的每个CU,执行附加RD检查以决定是否使用SbTMVP候选。
SbTMVP分两步预测当前CU内子CU的运动矢量:
Step1:检查下图中的空域相邻块A1。 如果A1具有将同位图片用作其参考图片的运动矢量(A1可用且其参考图像就是当前图像的同位图像),则将该运动矢量选择为要应用的运动偏移。 如果未识别到此类运动,则将运动偏移设置为(0,0)。
Step2:利用①中A1块的运动偏移,定位当前CU的参考同位块的位置。然后将当前CU以及其对应的同位图像中的同位块分割为8*8的子块,对于每个子块,获取对应同位子块的运动信息( 8*8 子块中心位置处)。在确定同位子CU的运动信息之后,以与HEVC的TMVP过程类似的方式,将其转换为当前子CU的运动矢量和参考帧索引,其中应用时间运动缩放以将时间运动矢量的参考图片与当前CU的参考图片对齐。
SbtMVP和Affine Merge的关系
在VVC中,SbTMVP和Affine Merge的候选是共存的,将SbTVMP得到的候选和仿射Merge候选共同组成基于子块的Merge候选列表。 通过序列参数集(SPS)标志启用/禁用SbTVMP模式。 如果启用了SbTMVP模式,则将SbTMVP预测变量添加为基于子块的Merge候选列表的第一项,然后是仿射Merge候选项。 基于子块的Merge列表的大小在SPS中用信号通知,并且基于子块的Merge列表的最大允许大小为5。
SbTMVP的相关代码及注释如下所示:(基于VTM10.0)
// 基于子块的TMVP技术if (sbTmvpEnableFlag && slice.getPicHeader()->getEnableTMVPFlag()){MergeCtx mrgCtx &#61; *affMrgCtx.mrgCtx;bool tmpLICFlag &#61; false;CHECK( mrgCtx.subPuMvpMiBuf.area() &#61;&#61; 0 || !mrgCtx.subPuMvpMiBuf.buf, "Buffer not initialized" );mrgCtx.subPuMvpMiBuf.fill( MotionInfo() );int pos &#61; 0;// Get spatial MVconst Position posCurLB &#61; pu.Y().bottomLeft();MotionInfo miLeft;//leftconst PredictionUnit* puLeft &#61; cs.getPURestricted( posCurLB.offset( -1, 0 ), pu, pu.chType );const bool isAvailableA1 &#61; puLeft && isDiffMER(pu.lumaPos(), posCurLB.offset(-1, 0), plevel) && pu.cu !&#61; puLeft->cu && CU::isInter( *puLeft->cu );if ( isAvailableA1 ){ //A1块可用miLeft &#61; puLeft->getMotionInfo( posCurLB.offset( -1, 0 ) );// get Inter DirmrgCtx.interDirNeighbours[pos] &#61; miLeft.interDir;// get Mv from Left 获得A1块的MVmrgCtx.mvFieldNeighbours[pos <<1].setMvField( miLeft.mv[0], miLeft.refIdx[0] );if ( slice.isInterB() ){mrgCtx.mvFieldNeighbours[(pos <<1) &#43; 1].setMvField( miLeft.mv[1], miLeft.refIdx[1] );}pos&#43;&#43;;}mrgCtx.numValidMergeCand &#61; pos;// 获得子PU的时域MV预测isAvailableSubPu &#61; getInterMergeSubPuMvpCand(pu, mrgCtx, tmpLICFlag, pos, 0);if ( isAvailableSubPu ){// 将子块的时域MV添加到Affine Merge列表中for ( int mvNum &#61; 0; mvNum <3; mvNum&#43;&#43; ){affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand <<1) &#43; 0][mvNum].setMvField( mrgCtx.mvFieldNeighbours[(pos <<1) &#43; 0].mv, mrgCtx.mvFieldNeighbours[(pos <<1) &#43; 0].refIdx );affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand <<1) &#43; 1][mvNum].setMvField( mrgCtx.mvFieldNeighbours[(pos <<1) &#43; 1].mv, mrgCtx.mvFieldNeighbours[(pos <<1) &#43; 1].refIdx );}affMrgCtx.interDirNeighbours[affMrgCtx.numValidMergeCand] &#61; mrgCtx.interDirNeighbours[pos];affMrgCtx.affineType[affMrgCtx.numValidMergeCand] &#61; AFFINE_MODEL_NUM;affMrgCtx.mergeType[affMrgCtx.numValidMergeCand] &#61; MRG_TYPE_SUBPU_ATMVP;if ( affMrgCtx.numValidMergeCand &#61;&#61; mrgCandIdx ){return;}affMrgCtx.numValidMergeCand&#43;&#43;;// early terminationif ( affMrgCtx.numValidMergeCand &#61;&#61; maxNumAffineMergeCand ){return;}}}
其中&#xff0c;在getInterMergeSubPuMvpCand函数中获得子PU的MVP候选项
bool PU::getInterMergeSubPuMvpCand(const PredictionUnit &pu, MergeCtx& mrgCtx, bool& LICFlag, const int count, int mmvdList
)
{const Slice &slice &#61; *pu.cs->slice;const unsigned scale &#61; 4 * std::max(1, 4 * AMVP_DECIMATION_FACTOR / 4);const unsigned mask &#61; ~(scale - 1);// 获得同位图片const Picture *pColPic &#61; slice.getRefPic(RefPicList(slice.isInterB() ? 1 - slice.getColFromL0Flag() : 0), slice.getColRefIdx());Mv cTMv;if ( count ){if ( (mrgCtx.interDirNeighbours[0] & (1 <> ATMVP_SUB_BLOCK_SIZE, 1u);int numPartCol &#61; std::max(puSize.height >> ATMVP_SUB_BLOCK_SIZE, 1u);int puHeight &#61; numPartCol &#61;&#61; 1 ? puSize.height : 1 <> 1) &#43; tempX; centerPos.y &#61; puPos.y &#43; (puSize.height >> 1) &#43; tempY;clipColPos(centerPos.x, centerPos.y, pu);centerPos &#61; Position{ PosType(centerPos.x & mask), PosType(centerPos.y & mask) };// derivation of center motion parameters from the collocated CU// 从同位CU得出中心运动参数const MotionInfo &mi &#61; pColPic->cs->getMotionInfo(centerPos);if (mi.isInter && mi.isIBCmot &#61;&#61; false){mrgCtx.interDirNeighbours[count] &#61; 0;for (unsigned currRefListId &#61; 0; currRefListId <(bBSlice ? 2 : 1); currRefListId&#43;&#43;){RefPicList currRefPicList &#61; RefPicList(currRefListId); //参考帧列表索引if (getColocatedMVP(pu, currRefPicList, centerPos, cColMv, refIdx, true)) //从同位CU中获得MVP{// set as default, for further motion vector field spanningmrgCtx.mvFieldNeighbours[(count <<1) &#43; currRefListId].setMvField(cColMv, 0);mrgCtx.interDirNeighbours[count] |&#61; (1 <> 1) &#43; tempX;int yOff &#61; (puHeight >> 1) &#43; tempY;MotionBuf &mb &#61; mrgCtx.subPuMvpMiBuf;const bool isBiPred &#61; isBipredRestriction(pu);for (int y &#61; puPos.y; y cs->getMotionInfo(colPos);MotionInfo mi;found &#61; false;mi.isInter &#61; true;mi.sliceIdx &#61; slice.getIndependentSliceIdx();mi.isIBCmot &#61; false;if (colMi.isInter && colMi.isIBCmot &#61;&#61; false){for (unsigned currRefListId &#61; 0; currRefListId <(bBSlice ? 2 : 1); currRefListId&#43;&#43;){RefPicList currRefPicList &#61; RefPicList(currRefListId);if (getColocatedMVP(pu, currRefPicList, colPos, cColMv, refIdx, true)){mi.refIdx[currRefListId] &#61; 0;mi.mv[currRefListId] &#61; cColMv;found &#61; true;}}}if (!found){mi.mv[0] &#61; mrgCtx.mvFieldNeighbours[(count <<1) &#43; 0].mv;mi.mv[1] &#61; mrgCtx.mvFieldNeighbours[(count <<1) &#43; 1].mv;mi.refIdx[0] &#61; mrgCtx.mvFieldNeighbours[(count <<1) &#43; 0].refIdx;mi.refIdx[1] &#61; mrgCtx.mvFieldNeighbours[(count <<1) &#43; 1].refIdx;}mi.interDir &#61; (mi.refIdx[0] !&#61; -1 ? 1 : 0) &#43; (mi.refIdx[1] !&#61; -1 ? 2 : 0);if (isBiPred && mi.interDir &#61;&#61; 3){mi.interDir &#61; 1;mi.mv[1] &#61; Mv();mi.refIdx[1] &#61; NOT_VALID;}mb.subBuf(g_miScaling.scale(Position{ x, y } - pu.lumaPos()), g_miScaling.scale(Size(puWidth, puHeight))).fill(mi);}}}return true;
}
sbtMVP对应的mergetype是MRG_TYPE_SUBPU_ATMVP&#xff0c;相应的运动补偿函数为&#xff1a;
void InterPrediction::xSubPuMC( PredictionUnit& pu, PelUnitBuf& predBuf, const RefPicList &eRefPicList /*&#61; REF_PIC_LIST_X*/, const bool luma /*&#61; true*/, const bool chroma /*&#61; true*/)
{// compute the location of the current PU// 当前PU的位置和尺寸Position puPos &#61; pu.lumaPos();Size puSize &#61; pu.lumaSize();int numPartLine, numPartCol, puHeight, puWidth;{numPartLine &#61; std::max(puSize.width >> ATMVP_SUB_BLOCK_SIZE, 1u); // 行存在多少个子PUnumPartCol &#61; std::max(puSize.height >> ATMVP_SUB_BLOCK_SIZE, 1u); // 列存在多少个子PU//subPu 的宽度/高度&#xff0c;如果当前PU的宽度/高度大于8&#xff0c;则subPU的宽度/高度为8&#xff0c;否则subPU的宽度/高度和PU的宽度/高度相等puHeight &#61; numPartCol &#61;&#61; 1 ? puSize.height : 1 <affine;subPu.cu->affine &#61; false;// join sub-pus containing the same motionbool verMC &#61; puSize.height > puSize.width;int fstStart &#61; (!verMC ? puPos.y : puPos.x);int secStart &#61; (!verMC ? puPos.x : puPos.y);int fstEnd &#61; (!verMC ? puPos.y &#43; puSize.height : puPos.x &#43; puSize.width);int secEnd &#61; (!verMC ? puPos.x &#43; puSize.width : puPos.y &#43; puSize.height);int fstStep &#61; (!verMC ? puHeight : puWidth);int secStep &#61; (!verMC ? puWidth : puHeight);bool scaled &#61; pu.cu->slice->getRefPic( REF_PIC_LIST_0, 0 )->isRefScaled( pu.cs->pps ) || ( pu.cs->slice->getSliceType() &#61;&#61; B_SLICE ? pu.cu->slice->getRefPic( REF_PIC_LIST_1, 0 )->isRefScaled( pu.cs->pps ) : false );m_subPuMC &#61; true;for (int fstDim &#61; fstStart; fstDim affine &#61; isAffine;
}