在VVC的扩展merge模式当中,当前CU生成的merge list中选择一个率失真代价值最小的候选项直接作为自己的运动信息,而MMVD是将候选项的运动信息作为自己MV的预测值,最终编码对象为实际获得的MV与该预测值之间的差值(MVD)
MMVD技术起源于之前提案中的ultimate motion vector expression(UMVE)技术,该技术是一种新的运动向量表示方法,在skip和merge模式当中使用起始点、运动步长、运动方向三个量来表示运动向量
MMVD技术应用与帧间预测的Skip和Merge模式当中,是一种运动矢量的特殊表达形式,在VTM中,MMVD技术流程大致如下所示:
首先复用普通merge模式的merge候选列表,在这些候选中选择列表的前两个候选作为初始的运动矢量对该初始MV进行扩展,主要在运动幅度和运动方向上进行扩展,来得到最终的扩展后的运动矢量,以形成新的运动矢量。具体实现步骤如下:
(1)、首先利用VVC中普通merge候选列表构建的过程,得到当前CU的merge list,检查邻块的顺序如下图所示
(2)、对于第一步得到的merge list中的前两个候选,把它们作为初始的MV,以该候选MV在参考图像中所指向的位置作为起始点,在上下左右四个方向上,进行8中步长的搜索,如下图所示
步长索引表:
方向索引表:
这样每一个初始MV在每个方向上的每种步长都会形成一个新的MV,即细化过的MV,该MV包含三个信息,分别是起始点、搜索方向、搜索步长,因此一个初始的MV可以扩展出32个新的MV,这些新的MV也会通过一次运动补偿得到当前CU的预测值。
(3)、在Skip和merge模式下,将所有得到的64个新的预测值之间进行率失真代价比较,最终选择出最优的一种组合作为MMVD的最优的merge候选,我们需要存储该MV的三个信息:分别是其初始MV在merge list中的索引值、移动的方向和搜索步长三个语法元素
1.在merge list中选择前两个运动向量作为MMVD的初始向量代码示例
static const int MMVD_BASE_MV_NUM = 2; //一个全局静态常量,基于MMVD初始向量的个数 //从merge list当中选取两个初始MV void PU::getInterMMVDMergeCandidates(const PredictionUnit &pu, MergeCtx& mrgCtx, const int& mrgCandIdx) { int refIdxList0, refIdxList1; int k; int currBaseNum = 0; const uint16_t maxNumMergeCand = mrgCtx.numValidMergeCand; for (k = 0; k < maxNumMergeCand; k++) { //只取MRG_TYPE_DEFAULT_N类型候选项 if (mrgCtx.mrgTypeNeighbours[k] == MRG_TYPE_DEFAULT_N) { refIdxList0 = mrgCtx.mvFieldNeighbours[(k << 1)].refIdx; refIdxList1 = mrgCtx.mvFieldNeighbours[(k << 1) + 1].refIdx; if ((refIdxList0 >= 0) && (refIdxList1 >= 0)) { mrgCtx.mmvdBaseMv[currBaseNum][0] = mrgCtx.mvFieldNeighbours[(k << 1)]; mrgCtx.mmvdBaseMv[currBaseNum][1] = mrgCtx.mvFieldNeighbours[(k << 1) + 1]; } else if (refIdxList0 >= 0) { mrgCtx.mmvdBaseMv[currBaseNum][0] = mrgCtx.mvFieldNeighbours[(k << 1)]; mrgCtx.mmvdBaseMv[currBaseNum][1] = MvField(Mv(0, 0), -1); } else if (refIdxList1 >= 0) { mrgCtx.mmvdBaseMv[currBaseNum][0] = MvField(Mv(0, 0), -1); mrgCtx.mmvdBaseMv[currBaseNum][1] = mrgCtx.mvFieldNeighbours[(k << 1) + 1]; } mrgCtx.mmvdUseAltHpelIf[currBaseNum] = mrgCtx.useAltHpelIf[k]; currBaseNum++; //只取两个base MV if (currBaseNum == MMVD_BASE_MV_NUM) break; } } }2.对选择的初始运动矢量进行步长和方向的扩展
//设置MMVD的候选信息,candIdx就是32中组合中的一种模式的索引 void MergeCtx::setMmvdMergeCandiInfo(PredictionUnit& pu, int candIdx) { const Slice &slice = *pu.cs->slice; const int mvShift = MV_FRACTIONAL_BITS_DIFF; //参考MVD的候选,2,8,16,32,64,128,256 //这里实际就是8中补偿的偏置量 const int refMvdCands[8] = { 1 << mvShift , 2 << mvShift , 4 << mvShift , 8 << mvShift , 16 << mvShift , 32 << mvShift, 64 << mvShift , 128 << mvShift }; int fPosGroup = 0; int fPosBaseIdx = 0; //起始MV的索引 int fPosStep = 0; //搜索步长 int tempIdx = 0; int fPosPosition = 0; //搜索的方向 Mv tempMv[2]; //里面存储前向和后向MV tempIdx = candIdx; //组合新的MV索引 fPosGroup = tempIdx / (MMVD_BASE_MV_NUM * MMVD_MAX_REFINE_NUM); //整个候选列表的初始位置为0 tempIdx = tempIdx - fPosGroup * (MMVD_BASE_MV_NUM * MMVD_MAX_REFINE_NUM); //传进来扩展MV的索引 fPosBaseIdx = tempIdx / MMVD_MAX_REFINE_NUM;//第一个初始向量的索引0或者第二个初始向量的索引1 tempIdx = tempIdx - fPosBaseIdx * (MMVD_MAX_REFINE_NUM);//0-31(各自初始向量的32中扩展MV) fPosStep = tempIdx / 4; //每个初始MV对应对应的8种搜索步长的索引 fPosPosition = tempIdx - fPosStep * (4); //该值总为0-3,对应四个搜索方向 int offset = refMvdCands[fPosStep]; //8中步长对应的偏置 if ( pu.cu->slice->getPicHeader()->getDisFracMMVD() ) { offset <<= 2; } const int refList0 = mmvdBaseMv[fPosBaseIdx][0].refIdx; //每个初始MV的前向参考列表 const int refList1 = mmvdBaseMv[fPosBaseIdx][1].refIdx; //每个初始MV的候选项参考列表 if ((refList0 != -1) && (refList1 != -1)) //双向参考列表存在 { const int poc0 = slice.getRefPOC(REF_PIC_LIST_0, refList0); //前向参考帧的POC const int poc1 = slice.getRefPOC(REF_PIC_LIST_1, refList1); //后向参考帧的POC const int currPoc = slice.getPOC(); //当前参考帧的POC //每个MV的结构体里包含器搜索方向、以及搜索步长、然后赋值给tempMv数组 if (fPosPosition == 0) { tempMv[0] = Mv(offset, 0); //右 } else if (fPosPosition == 1) { tempMv[0] = Mv(-offset, 0); //左 } else if (fPosPosition == 2) { tempMv[0] = Mv(0, offset); //上 } else { tempMv[0] = Mv(0, -offset); //下 } //前后参考帧属于同一帧 if ((poc0 - currPoc) == (poc1 - currPoc)) { tempMv[1] = tempMv[0]; //双向的MVD是一样的 } else if (abs(poc1 - currPoc) > abs(poc0 - currPoc)) //后向参考帧的POC大于前向参考帧的POC { const int scale = PU::getDistScaleFactor(currPoc, poc0, currPoc, poc1); tempMv[1] = tempMv[0]; const bool isL0RefLongTerm = slice.getRefPic(REF_PIC_LIST_0, refList0)->longTerm; const bool isL1RefLongTerm = slice.getRefPic(REF_PIC_LIST_1, refList1)->longTerm; if (isL0RefLongTerm || isL1RefLongTerm)//有其中一个是长期参考帧 { if ((poc1 - currPoc)*(poc0 - currPoc) > 0) { tempMv[0] = tempMv[1]; } else { tempMv[0].set(-1 * tempMv[1].getHor(), -1 * tempMv[1].getVer()); } } else //都不是长期参考帧 tempMv[0] = tempMv[1].scaleMv(scale); } else { const int scale = PU::getDistScaleFactor(currPoc, poc1, currPoc, poc0); const bool isL0RefLongTerm = slice.getRefPic(REF_PIC_LIST_0, refList0)->longTerm; const bool isL1RefLongTerm = slice.getRefPic(REF_PIC_LIST_1, refList1)->longTerm; if (isL0RefLongTerm || isL1RefLongTerm) { if ((poc1 - currPoc)*(poc0 - currPoc) > 0)//若前后参考帧都来自当前帧的时域的同一侧,后向MMVDoffset与前向相等 { tempMv[1] = tempMv[0]; } else //若前后参考帧来自不同侧,前向和后向的MMVDoffset相对称 { tempMv[1].set(-1 * tempMv[0].getHor(), -1 * tempMv[0].getVer()); } } else tempMv[1] = tempMv[0].scaleMv(scale); } pu.interDir = 3; //双向列表的DIr设置为3 //这里为每个选中的MV选择起始MV然后加上然后加上步长和方向 pu.mv[REF_PIC_LIST_0] = mmvdBaseMv[fPosBaseIdx][0].mv + tempMv[0]; //MMVD的前向MV pu.refIdx[REF_PIC_LIST_0] = refList0; pu.mv[REF_PIC_LIST_1] = mmvdBaseMv[fPosBaseIdx][1].mv + tempMv[1]; //MMVD的后向MV pu.refIdx[REF_PIC_LIST_1] = refList1; } else if (refList0 != -1) //如果只有前向列表存在 { if (fPosPosition == 0) //右 { tempMv[0] = Mv(offset, 0); } else if (fPosPosition == 1) //左 { tempMv[0] = Mv(-offset, 0); } else if (fPosPosition == 2) //上 { tempMv[0] = Mv(0, offset); } else //下 { tempMv[0] = Mv(0, -offset); } pu.interDir = 1; //前向列表的DIr设置为1 pu.mv[REF_PIC_LIST_0] = mmvdBaseMv[fPosBaseIdx][0].mv + tempMv[0]; pu.refIdx[REF_PIC_LIST_0] = refList0; pu.mv[REF_PIC_LIST_1] = Mv(0, 0); pu.refIdx[REF_PIC_LIST_1] = -1; } else if (refList1 != -1) { if (fPosPosition == 0) { tempMv[1] = Mv(offset, 0); } else if (fPosPosition == 1) { tempMv[1] = Mv(-offset, 0); } else if (fPosPosition == 2) { tempMv[1] = Mv(0, offset); } else { tempMv[1] = Mv(0, -offset); } pu.interDir = 2; //后向类表的DIr设置为2 pu.mv[REF_PIC_LIST_0] = Mv(0, 0); pu.refIdx[REF_PIC_LIST_0] = -1; pu.mv[REF_PIC_LIST_1] = mmvdBaseMv[fPosBaseIdx][1].mv + tempMv[1]; pu.refIdx[REF_PIC_LIST_1] = refList1; } pu.mmvdMergeFlag = true; pu.mmvdMergeIdx = candIdx; pu.mergeFlag = true; pu.regularMergeFlag = true; pu.mergeIdx = candIdx; pu.mergeType = MRG_TYPE_DEFAULT_N; pu.mvd[REF_PIC_LIST_0] = Mv(); pu.mvd[REF_PIC_LIST_1] = Mv(); pu.mvpIdx[REF_PIC_LIST_0] = NOT_VALID; pu.mvpIdx[REF_PIC_LIST_1] = NOT_VALID; pu.mvpNum[REF_PIC_LIST_0] = NOT_VALID; pu.mvpNum[REF_PIC_LIST_1] = NOT_VALID; pu.cu->imv = mmvdUseAltHpelIf[fPosBaseIdx] ? IMV_HPEL : 0; //MV使用半像素精度 pu.cu->BcwIdx = (interDirNeighbours[fPosBaseIdx] == 3) ? BcwIdx[fPosBaseIdx] : BCW_DEFAULT; //双向参考索引 for (int refList = 0; refList < 2; refList++) { if (pu.refIdx[refList] >= 0) { pu.mv[refList].clipToStorageBitDepth(); } } PU::restrictBiPredMergeCandsOne(pu); }3.MMVD技术的实现代码
if ( pu.cs->sps->getUseMMVD() )//使用MMVD模式,这里选出最优的MMVD的Merge候选(初始MV+搜索方向+搜索步长) { cu.mmvdSkip = true; #if JVET_O0249_MERGE_SYNTAX pu.regularMergeFlag = true;//普通Merge列表依旧可用,因为初始的MV还是要从普通Merge中获取 #endif const int tempNum = (mergeCtx.numValidMergeCand > 1) ? MMVD_ADD_NUM : MMVD_ADD_NUM >> 1; //对MMVD候选循环遍历 for (int mmvdMergeCand = 0; mmvdMergeCand < tempNum; mmvdMergeCand++)//循环64遍,前32种得到起始点1以及对应的8个步长 //因为对于每种MV扩展都可以得到4*8种组合,因此两个起始点总共是64种组合 { int baseIdx = mmvdMergeCand / MMVD_MAX_REFINE_NUM;//候选基MV(即初始MV)索引,要么0 要么1,(这里就是两个初始MV的起点,每个初始MV有32种4*8的步长+方向的组合) int refineStep = (mmvdMergeCand - (baseIdx * MMVD_MAX_REFINE_NUM)) / 4;//表示8种步长 if (refineStep >= m_pcEncCfg->getMmvdDisNum()) continue; //设置MMVD候选的信息,得到每个扩展MV具体的搜索初始点以及每个方向以及对应的步长 mergeCtx.setMmvdMergeCandiInfo(pu, mmvdMergeCand); PU::spanMotionInfo(pu, mergeCtx); pu.mvRefine = true; distParam.cur = singleMergeTempBuffer->Y(); pu.mmvdEncOptMode = (refineStep > 2 ? 2 : 1); CHECK(!pu.mmvdMergeFlag, "MMVD merge should be set"); // Don't do chroma MC here //MMVD的Merge候选运动补偿计算预测值 m_pcInterSearch->motionCompensation(pu, *singleMergeTempBuffer, REF_PIC_LIST_X, true, false); pu.mmvdEncOptMode = 0; pu.mvRefine = false; Distortion uiSad = distParam.distFunc(distParam);//失真函数 m_CABACEstimator->getCtx() = ctxStart; uint64_t fracBits = m_pcInterSearch->xCalcPuMeBits(pu);//计算码率 double cost = (double)uiSad + (double)fracBits * sqrtLambdaForFirstPassIntra; //计算RDcost insertPos = -1; //更新候选列表 updateCandList(ModeInfo(mmvdMergeCand, false, true, false), cost, RdModeList, candCostList, uiNumMrgSATDCand, &insertPos); if (insertPos != -1) { for (int i = int(RdModeList.size()) - 1; i > insertPos; i--) { swap(acMergeTempBuffer[i - 1], acMergeTempBuffer[i]); } swap(singleMergeTempBuffer, acMergeTempBuffer[insertPos]);//将代价更小的那个MV插入到Merge列表中 } } }更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!