一、前言

HEVC中在进行运动补偿时只考虑了平移运动,而在真实的世界存在各种运动,例如缩放、旋转等非平移运动。在H.266/VVC中提出了基于块的仿射变换运动补偿预测。如下图所示,一个块的仿射运动向量有两个控制点(4参数模型)或者三个控制点(6参数模型),如下图所示:

【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家

【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家

基于块的运动补偿算法如下:

1、首先将运动估计块划分为4*4的亮度子块

2、对每个亮度子块按四参数模型和六参数模式由参考点的仿射向量计算其中心像素的运动向量,然后四舍五入到1/16精度

  • 其中4参数仿射运动模型,中心像素为(x,y)的子块的运动向量计算方法如下

其中向量a,b在二维空间可以分别表示为 (ah,av)\left( a^h,a^v \right)(ah,av)(bh,bv)\left( b^h,b^v \right)(bh,bv) ,当前两块中心像素点的预测向量为 (MVh,MVv)\left( MV^h,MV^v \right)(MVh,MVv) ,可以用a、b向量表示为如下:

{MVh=bh−ahwx+bv−avwy+ahMVv=bv−avwx+bh−ahwy+av,其中w代表当前块的宽度\begin{cases} MV^h=\frac{b^h-a^h}{w}x+\frac{b^v-a^v}{w}y+a^h\\ MV^v=\frac{b^v-a^v}{w}x+\frac{b^h-a^h}{w}y+a^v\\ \end{cases},\text{其中}w\text{代表当前块的宽度} {MVh=wbhahx+wbvavy+ahMVv=wbvavx+wbhahy+av,其中w代表当前块的宽度

  • 其中6参数仿射运动模型,增加了一个参考点,它的运动向量为 (ch,cv)\left( c^h,c^v \right)(ch,cv),当前两块中心像素点的预测向量为 (MVh,MVv)\left( MV^h,MV^v \right)(MVh,MVv),中心像素为(x,y)的子块的运动向量可以用a、b、c向量表示为如下

{MVh=bh−ahwx+ch−ahhy+ahMVv=bv−avwx+cv−avhy+av,其中h代表当前块的高度\begin{cases} MV^h=\frac{b^h-a^h}{w}x+\frac{c^h-a^h}{h}y+a^h\\ MV^v=\frac{b^v-a^v}{w}x+\frac{c^v-a^v}{h}y+a^v\\ \end{cases},\text{其中}h\text{代表当前块的高度} {MVh=wbhahx+hchahy+ahMVv=wbvavx+hcvavy+av,其中h代表当前块的高度

3、每个子块的运动向量计算出来之后,根据运动向量进行运动补偿、插值滤波得到每个子块的预测值

4、 对于色度分量同样是划分为4 * 4的子块,其运动向量等于与其相关的4个4 * 4的亮度子块运动向量的平均值

注意:和传统的帧间运动向量预测方式一样,仿射运动向量也有两种预测方式:即仿射Merge模式、仿射AMVP模式


二、Affine merge模式

merge模式用于宽高均大于8的块,产生长度为5的候选列表,编码时仅传输CPMV在列表中的索引。

merge候选类表的构造顺序为:

1、继承自CU相邻位置的CPMVs

2、利用旁边CU的平移MV构造CPMVs

3、零向量

2.1 继承

首先,如果CU左侧和上侧都有affine模式,最多可以得到两个CPMV加入候选列表。左侧的搜索顺序为A0->A1,上侧的搜索顺序为B0->B1->B2,对于左侧和上方分别只继承扫描顺序中第一个有效的CU

【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家

当邻近的CU被选定之后,该邻近的CU的CPMV就用来生成当前CU的affine merge list 里的候选项,如下图所示,如果左下角的A块被选中,当A是4参数模型时,当前CU的两个CPMV根据v2和v3继承得到,当A时六参数仿射运动模型时,当前CU的三个CPMV根据v2、v3、v4计算得到

【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家

2.2 构造

用CPMVk表示第K个控制点,搜索CU周边的MV,搜索MV的顺序为:

CPMV1: B2->B3-A2

CPMV2: B1->B0

CPMV3: A1->A0

CPMV4: T ,使用TMVP(如果可用)

【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家

  • 6参数模型CPMV按以下顺序构造:{CPMV1,CPMV2,CPMV3},{CPMV1,CPMV2,CPMV4},{CPMV1,CPMV3,CPMV4},{CPMV2,CPMV3,CPMV4}
  • 4参数模型按以下顺序进行构造:{CPMV1,CPMV2},{CPMV1,CPMV3}
  • 为了避免缩放计算,如果控制点的参考图像不同则相关组合被丢弃

2.3 零向量填充

当使用继承和构造两种方法之后,候选列表仍然未满,使用0向量进行填充


三、Affine AMVP模式

Affine AMVP可以在长度和宽度都大于16的块上使用,在这种模式下,预测值CPMVPs在候选列表中的索引,以及它和运动搜索到的实际运动信息CPMVPs的差值会被编码传输,候选列表是长度为2

候选列表的构建顺序如下:

1、继承自相邻CU的CPMV候选项

2、由相邻CU的平移运动MV构建CPMV

3、直接使用相邻CU的平移MV

4、以上构建列表未满时,使用零向量进行填充

【几个提醒点】

  • 类型1的affine AMVP候选项构建方法和affine merge一样,区别在于相邻CU的参考图像必须和当前CU的参考图像一致
  • 类型2的affine AMVP候选项构建方法和affine merge一样,此外,相邻块的参考图像索引也要进行检查,要选择扫描顺序中第一个帧间编码且和当前CU具有相同参考图像的块。当当前CU是4参数仿射模型且mv0和mv1都有效时,将它们加入到affine list当中;当当前CU是6参数仿射模型且3个CPMV都有效时,将它们加入到affine AMVP list中,否则类型2的候选项无效
  • 如果类型1和类型2加入后affine AMVP中的候选项海事少于2,则按顺序加入mv0、mv1、mv2用平移运动MV预测当前CU所有控制点的MV,最后如果list还未满,加入零向量进行填充

四、相关代码分析:

1.affine AMVP list构建

在VTM8.0中,affine AMVP有3个候选项如下:

struct AffineAMVPInfo
{Mv       mvCandLT[ AMVP_MAX_NUM_CANDS_MEM ];  ///< array of affine motion vector predictor candidates for left-top cornerMv       mvCandRT[ AMVP_MAX_NUM_CANDS_MEM ];  ///< array of affine motion vector predictor candidates for right-top cornerMv       mvCandLB[ AMVP_MAX_NUM_CANDS_MEM ];  ///< array of affine motion vector predictor candidates for left-bottom cornerunsigned numCand;                       ///< number of motion vector predictor candidates
};
void PU::fillAffineMvpCand(PredictionUnit &pu, const RefPicList &eRefPicList, const int &refIdx, AffineAMVPInfo &affiAMVPInfo)
{affiAMVPInfo.numCand = 0;if (refIdx < 0){return;}// insert inherited affine candidates//继承其相邻CU的CPMV候选项Mv outputAffineMv[3];Position posLT = pu.Y().topLeft();Position posRT = pu.Y().topRight();Position posLB = pu.Y().bottomLeft();// check left neighborif ( !addAffineMVPCandUnscaled( pu, eRefPicList, refIdx, posLB, MD_BELOW_LEFT, affiAMVPInfo ) ){addAffineMVPCandUnscaled( pu, eRefPicList, refIdx, posLB, MD_LEFT, affiAMVPInfo );}// check above neighborif ( !addAffineMVPCandUnscaled( pu, eRefPicList, refIdx, posRT, MD_ABOVE_RIGHT, affiAMVPInfo ) ){if ( !addAffineMVPCandUnscaled( pu, eRefPicList, refIdx, posRT, MD_ABOVE, affiAMVPInfo ) ){addAffineMVPCandUnscaled( pu, eRefPicList, refIdx, posLT, MD_ABOVE_LEFT, affiAMVPInfo );}}if ( affiAMVPInfo.numCand >= AMVP_MAX_NUM_CANDS ){for (int i = 0; i < affiAMVPInfo.numCand; i++){affiAMVPInfo.mvCandLT[i].roundAffinePrecInternal2Amvr(pu.cu->imv);affiAMVPInfo.mvCandRT[i].roundAffinePrecInternal2Amvr(pu.cu->imv);affiAMVPInfo.mvCandLB[i].roundAffinePrecInternal2Amvr(pu.cu->imv);}return;}// insert constructed affine candidates//有相邻CU的平移运动MV构建CPMVint cornerMVPattern = 0;//-------------------  V0 (START) -------------------//AMVPInfo amvpInfo0;amvpInfo0.numCand = 0;// A->C: Above Left, Above, LeftaddMVPCandUnscaled( pu, eRefPicList, refIdx, posLT, MD_ABOVE_LEFT, amvpInfo0 );if ( amvpInfo0.numCand < 1 ){addMVPCandUnscaled( pu, eRefPicList, refIdx, posLT, MD_ABOVE, amvpInfo0 );}if ( amvpInfo0.numCand < 1 ){addMVPCandUnscaled( pu, eRefPicList, refIdx, posLT, MD_LEFT, amvpInfo0 );}cornerMVPattern = cornerMVPattern | amvpInfo0.numCand;//-------------------  V1 (START) -------------------//AMVPInfo amvpInfo1;amvpInfo1.numCand = 0;// D->E: Above, Above RightaddMVPCandUnscaled( pu, eRefPicList, refIdx, posRT, MD_ABOVE, amvpInfo1 );if ( amvpInfo1.numCand < 1 ){addMVPCandUnscaled( pu, eRefPicList, refIdx, posRT, MD_ABOVE_RIGHT, amvpInfo1 );}cornerMVPattern = cornerMVPattern | (amvpInfo1.numCand << 1);//-------------------  V2 (START) -------------------//AMVPInfo amvpInfo2;amvpInfo2.numCand = 0;// F->G: Left, Below LeftaddMVPCandUnscaled( pu, eRefPicList, refIdx, posLB, MD_LEFT, amvpInfo2 );if ( amvpInfo2.numCand < 1 ){addMVPCandUnscaled( pu, eRefPicList, refIdx, posLB, MD_BELOW_LEFT, amvpInfo2 );}cornerMVPattern = cornerMVPattern | (amvpInfo2.numCand << 2);outputAffineMv[0] = amvpInfo0.mvCand[0];outputAffineMv[1] = amvpInfo1.mvCand[0];outputAffineMv[2] = amvpInfo2.mvCand[0];outputAffineMv[0].roundAffinePrecInternal2Amvr(pu.cu->imv);outputAffineMv[1].roundAffinePrecInternal2Amvr(pu.cu->imv);outputAffineMv[2].roundAffinePrecInternal2Amvr(pu.cu->imv);if ( cornerMVPattern == 7 || (cornerMVPattern == 3 && pu.cu->affineType == AFFINEMODEL_4PARAM) ){affiAMVPInfo.mvCandLT[affiAMVPInfo.numCand] = outputAffineMv[0];affiAMVPInfo.mvCandRT[affiAMVPInfo.numCand] = outputAffineMv[1];affiAMVPInfo.mvCandLB[affiAMVPInfo.numCand] = outputAffineMv[2];affiAMVPInfo.numCand++;}if ( affiAMVPInfo.numCand < 2 ){// check corner MVs//直接使用旁边CU的平移MVfor ( int i = 2; i >= 0 && affiAMVPInfo.numCand < AMVP_MAX_NUM_CANDS; i-- ){if ( cornerMVPattern & (1 << i) ) // MV i exist{affiAMVPInfo.mvCandLT[affiAMVPInfo.numCand] = outputAffineMv[i];affiAMVPInfo.mvCandRT[affiAMVPInfo.numCand] = outputAffineMv[i];affiAMVPInfo.mvCandLB[affiAMVPInfo.numCand] = outputAffineMv[i];affiAMVPInfo.numCand++;}}// Get Temporal Motion Predictorif ( affiAMVPInfo.numCand < 2 && pu.cs->picHeader->getEnableTMVPFlag() ){const int refIdxCol = refIdx;Position posRB = pu.Y().bottomRight().offset( -3, -3 );const PreCalcValues& pcv = *pu.cs->pcv;Position posC0;bool C0Avail = false;Position posC1 = pu.Y().center();Mv cColMv;
#if JVET_O1143_MV_ACROSS_SUBPIC_BOUNDARYbool 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)
#elseif ( ((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;}}if ( ( C0Avail && getColocatedMVP( pu, eRefPicList, posC0, cColMv, refIdxCol, false ) ) || getColocatedMVP( pu, eRefPicList, posC1, cColMv, refIdxCol, false ) ){cColMv.roundAffinePrecInternal2Amvr(pu.cu->imv);affiAMVPInfo.mvCandLT[affiAMVPInfo.numCand] = cColMv;affiAMVPInfo.mvCandRT[affiAMVPInfo.numCand] = cColMv;affiAMVPInfo.mvCandLB[affiAMVPInfo.numCand] = cColMv;affiAMVPInfo.numCand++;}}if ( affiAMVPInfo.numCand < 2 ){// add zero MV//候选列表未满,用零向量进行填充for ( int i = affiAMVPInfo.numCand; i < AMVP_MAX_NUM_CANDS; i++ ){affiAMVPInfo.mvCandLT[affiAMVPInfo.numCand].setZero();affiAMVPInfo.mvCandRT[affiAMVPInfo.numCand].setZero();affiAMVPInfo.mvCandLB[affiAMVPInfo.numCand].setZero();affiAMVPInfo.numCand++;}}}for (int i = 0; i < affiAMVPInfo.numCand; i++){affiAMVPInfo.mvCandLT[i].roundAffinePrecInternal2Amvr(pu.cu->imv);affiAMVPInfo.mvCandRT[i].roundAffinePrecInternal2Amvr(pu.cu->imv);affiAMVPInfo.mvCandLB[i].roundAffinePrecInternal2Amvr(pu.cu->imv);}
}

2、affine merge list 构建:

void PU::getAffineMergeCand( const PredictionUnit &pu, AffineMergeCtx& affMrgCtx, const int mrgCandIdx )
{const CodingStructure &cs = *pu.cs;const Slice &slice = *pu.cs->slice;const uint32_t maxNumAffineMergeCand = slice.getPicHeader()->getMaxNumAffineMergeCand();for ( int i = 0; i < maxNumAffineMergeCand; i++ ){for ( int mvNum = 0; mvNum < 3; mvNum++ ){affMrgCtx.mvFieldNeighbours[(i << 1) + 0][mvNum].setMvField( Mv(), -1 );affMrgCtx.mvFieldNeighbours[(i << 1) + 1][mvNum].setMvField( Mv(), -1 );}affMrgCtx.interDirNeighbours[i] = 0;affMrgCtx.affineType[i] = AFFINEMODEL_4PARAM;affMrgCtx.mergeType[i] = MRG_TYPE_DEFAULT_N;affMrgCtx.BcwIdx[i] = BCW_DEFAULT;}affMrgCtx.numValidMergeCand = 0;affMrgCtx.maxNumMergeCand = maxNumAffineMergeCand;bool enableSubPuMvp = slice.getSPS()->getSBTMVPEnabledFlag() && !(slice.getPOC() == slice.getRefPic(REF_PIC_LIST_0, 0)->getPOC() && slice.isIRAP());bool isAvailableSubPu = false;if ( enableSubPuMvp && slice.getPicHeader()->getEnableTMVPFlag() ){MergeCtx mrgCtx = *affMrgCtx.mrgCtx;bool tmpLICFlag = false;CHECK( mrgCtx.subPuMvpMiBuf.area() == 0 || !mrgCtx.subPuMvpMiBuf.buf, "Buffer not initialized" );mrgCtx.subPuMvpMiBuf.fill( MotionInfo() );int pos = 0;// Get spatial MVconst Position posCurLB = pu.Y().bottomLeft();MotionInfo miLeft;//leftconst PredictionUnit* puLeft = cs.getPURestricted( posCurLB.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( posCurLB.offset( -1, 0 ) );// get Inter DirmrgCtx.interDirNeighbours[pos] = miLeft.interDir;// get Mv from LeftmrgCtx.mvFieldNeighbours[pos << 1].setMvField( miLeft.mv[0], miLeft.refIdx[0] );if ( slice.isInterB() ){mrgCtx.mvFieldNeighbours[(pos << 1) + 1].setMvField( miLeft.mv[1], miLeft.refIdx[1] );}pos++;}mrgCtx.numValidMergeCand = pos;isAvailableSubPu = getInterMergeSubPuMvpCand( pu, mrgCtx, tmpLICFlag, pos, 0);if ( isAvailableSubPu ){for ( int mvNum = 0; mvNum < 3; mvNum++ ){affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand << 1) + 0][mvNum].setMvField( mrgCtx.mvFieldNeighbours[(pos << 1) + 0].mv, mrgCtx.mvFieldNeighbours[(pos << 1) + 0].refIdx );affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand << 1) + 1][mvNum].setMvField( mrgCtx.mvFieldNeighbours[(pos << 1) + 1].mv, mrgCtx.mvFieldNeighbours[(pos << 1) + 1].refIdx );}affMrgCtx.interDirNeighbours[affMrgCtx.numValidMergeCand] = mrgCtx.interDirNeighbours[pos];affMrgCtx.affineType[affMrgCtx.numValidMergeCand] = AFFINE_MODEL_NUM;affMrgCtx.mergeType[affMrgCtx.numValidMergeCand] = MRG_TYPE_SUBPU_ATMVP;if ( affMrgCtx.numValidMergeCand == mrgCandIdx ){return;}affMrgCtx.numValidMergeCand++;// early terminationif ( affMrgCtx.numValidMergeCand == maxNumAffineMergeCand ){return;}}}if ( slice.getSPS()->getUseAffine() ){///> Start: inherited affine candidatesconst PredictionUnit* npu[5];int numAffNeighLeft = getAvailableAffineNeighboursForLeftPredictor( pu, npu );int numAffNeigh = getAvailableAffineNeighboursForAbovePredictor( pu, npu, numAffNeighLeft );for ( int idx = 0; idx < numAffNeigh; idx++ ){// derive Mv from Neigh affine PUMv cMv[2][3];const PredictionUnit* puNeigh = npu[idx];pu.cu->affineType = puNeigh->cu->affineType;if ( puNeigh->interDir != 2 ){xInheritedAffineMv( pu, puNeigh, REF_PIC_LIST_0, cMv[0] );}if ( slice.isInterB() ){if ( puNeigh->interDir != 1 ){xInheritedAffineMv( pu, puNeigh, REF_PIC_LIST_1, cMv[1] );}}for ( int mvNum = 0; mvNum < 3; mvNum++ ){affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand << 1) + 0][mvNum].setMvField( cMv[0][mvNum], puNeigh->refIdx[0] );affMrgCtx.mvFieldNeighbours[(affMrgCtx.numValidMergeCand << 1) + 1][mvNum].setMvField( cMv[1][mvNum], puNeigh->refIdx[1] );}affMrgCtx.interDirNeighbours[affMrgCtx.numValidMergeCand] = puNeigh->interDir;affMrgCtx.affineType[affMrgCtx.numValidMergeCand] = (EAffineModel)(puNeigh->cu->affineType);affMrgCtx.BcwIdx[affMrgCtx.numValidMergeCand] = puNeigh->cu->BcwIdx;if ( affMrgCtx.numValidMergeCand == mrgCandIdx ){return;}// early terminationaffMrgCtx.numValidMergeCand++;if ( affMrgCtx.numValidMergeCand == maxNumAffineMergeCand ){return;}}///> End: inherited affine candidates///> Start: Constructed affine candidates{MotionInfo mi[4];bool isAvailable[4] = { false };int8_t neighBcw[2] = { BCW_DEFAULT, BCW_DEFAULT };// control point: LT B2->B3->A2const Position posLT[3] = { pu.Y().topLeft().offset( -1, -1 ), pu.Y().topLeft().offset( 0, -1 ), pu.Y().topLeft().offset( -1, 0 ) };for ( int i = 0; i < 3; i++ ){const Position pos = posLT[i];const PredictionUnit* puNeigh = cs.getPURestricted( pos, pu, pu.chType );if ( puNeigh && CU::isInter( *puNeigh->cu )
#if JVET_Q0297_MER&& PU::isDiffMER(pu, *puNeigh)
#endif){isAvailable[0] = true;mi[0] = puNeigh->getMotionInfo( pos );neighBcw[0] = puNeigh->cu->BcwIdx;break;}}// control point: RT B1->B0const Position posRT[2] = { pu.Y().topRight().offset( 0, -1 ), pu.Y().topRight().offset( 1, -1 ) };for ( int i = 0; i < 2; i++ ){const Position pos = posRT[i];const PredictionUnit* puNeigh = cs.getPURestricted( pos, pu, pu.chType );if ( puNeigh && CU::isInter( *puNeigh->cu )
#if JVET_Q0297_MER&& PU::isDiffMER(pu, *puNeigh)
#endif){isAvailable[1] = true;mi[1] = puNeigh->getMotionInfo( pos );neighBcw[1] = puNeigh->cu->BcwIdx;break;}}// control point: LB A1->A0const Position posLB[2] = { pu.Y().bottomLeft().offset( -1, 0 ), pu.Y().bottomLeft().offset( -1, 1 ) };for ( int i = 0; i < 2; i++ ){const Position pos = posLB[i];const PredictionUnit* puNeigh = cs.getPURestricted( pos, pu, pu.chType );if ( puNeigh && CU::isInter( *puNeigh->cu )
#if JVET_Q0297_MER&& PU::isDiffMER(pu, *puNeigh)
#endif){isAvailable[2] = true;mi[2] = puNeigh->getMotionInfo( pos );break;}}// control point: RBif ( slice.getPicHeader()->getEnableTMVPFlag() ){//>> MTK colocated-RightBottom// offset the pos to be sure to "point" to the same position the uiAbsPartIdx would've pointed toPosition posRB = pu.Y().bottomRight().offset( -3, -3 );const PreCalcValues& pcv = *cs.pcv;Position posC0;bool C0Avail = false;#if JVET_O1143_MV_ACROSS_SUBPIC_BOUNDARYbool 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)
#elseif ( ((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       refIdx = 0;bool      bExistMV = C0Avail && getColocatedMVP( pu, REF_PIC_LIST_0, posC0, cColMv, refIdx, false );if ( bExistMV ){mi[3].mv[0] = cColMv;mi[3].refIdx[0] = refIdx;mi[3].interDir = 1;isAvailable[3] = true;}if ( slice.isInterB() ){bExistMV = C0Avail && getColocatedMVP( pu, REF_PIC_LIST_1, posC0, cColMv, refIdx, false );if ( bExistMV ){mi[3].mv[1] = cColMv;mi[3].refIdx[1] = refIdx;mi[3].interDir |= 2;isAvailable[3] = true;}}}//-------------------  insert model  -------------------//int order[6] = { 0, 1, 2, 3, 4, 5 };int modelNum = 6;int model[6][4] = {{ 0, 1, 2 },          // 0:  LT, RT, LB{ 0, 1, 3 },          // 1:  LT, RT, RB{ 0, 2, 3 },          // 2:  LT, LB, RB{ 1, 2, 3 },          // 3:  RT, LB, RB{ 0, 1 },             // 4:  LT, RT{ 0, 2 },             // 5:  LT, LB};int verNum[6] = { 3, 3, 3, 3, 2, 2 };int startIdx = pu.cs->sps->getUseAffineType() ? 0 : 4;for ( int idx = startIdx; idx < modelNum; idx++ ){int modelIdx = order[idx];getAffineControlPointCand(pu, mi, isAvailable, model[modelIdx], ((modelIdx == 3) ? neighBcw[1] : neighBcw[0]), modelIdx, verNum[modelIdx], affMrgCtx);if ( affMrgCtx.numValidMergeCand != 0 && affMrgCtx.numValidMergeCand - 1 == mrgCandIdx ){return;}// early terminationif ( affMrgCtx.numValidMergeCand == maxNumAffineMergeCand ){return;}}}///> End: Constructed affine candidates}///> zero paddingint cnt = affMrgCtx.numValidMergeCand;while ( cnt < maxNumAffineMergeCand ){for ( int mvNum = 0; mvNum < 3; mvNum++ ){affMrgCtx.mvFieldNeighbours[(cnt << 1) + 0][mvNum].setMvField( Mv( 0, 0 ), 0 );}affMrgCtx.interDirNeighbours[cnt] = 1;if ( slice.isInterB() ){for ( int mvNum = 0; mvNum < 3; mvNum++ ){affMrgCtx.mvFieldNeighbours[(cnt << 1) + 1][mvNum].setMvField( Mv( 0, 0 ), 0 );}affMrgCtx.interDirNeighbours[cnt] = 3;}affMrgCtx.affineType[cnt] = AFFINEMODEL_4PARAM;if ( cnt == mrgCandIdx ){return;}cnt++;affMrgCtx.numValidMergeCand++;}
}

参考文献:

[1] VVC标准草案8.0文档.JVET-Q2001

[2] CSDN大牛博主Dillon2015. VVC帧间预测(四)仿射运动补偿预测.2020.01.05

[3] CSDN大牛博主heweiqiran. H.266/VVC技术描述_3-帧间预测 _1-Affine模式:仿射运动补偿. 2019.10.23


更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!
【九】 H.266/VVC中帧间仿射运动补偿预测-编程之家