在VVC的扩展Merge模式当中,当前CU生成的Merge list中选择一个率失真代价值最小的候选项直接作为自己的运动信息。除了常规Merge模式,VVC还引入了带运动矢量差(Merge mode with MVD)的Merge模式。MMVD是对常规Merge列表的前两个MV进行细化,使预测的MV更加准确,实际上传输的是细化搜索的步长和搜索方向等信息,并没有MVD信息。(这里所谓的MVD应该指的是通过细化常规Merge的MV近似对MV加上MVD)

MMVD技术起源于之前提案中的ultimate motion vector expression(UMVE)技术,该技术是一种新的运动向量表示方法,在skip和merge模式当中使用起始点、运动步长、运动方向三个量来表示运动向量。编码端在发送了Skip Flag和Merge Flag之后,才发送MMVD Flag以指示当前CU是否使用MMVD预测模式。

MMVD技术应用于帧间预测的Skip和Merge模式当中,是一种运动矢量的特殊表达形式,在VTM中,MMVD技术流程大致如下所示:

(1)首先复用常规Merge模式候选列表,将Merge候选列表的前两个候选MV作为初始MV

(2)对于第1步得到初始的MV,分别以该初始MV在参考帧中所指向的位置作为起始点,在上下左右四个方向上,进行8种步长的搜索,如下图所示

关于步长和方向的定义如下:

Distance IDX

0

1

2

3

4

5

6

7

Offset (in unit of luma sample)

1/4

1/2

1

2

4

8

16

32

Direction IDX

00

01

10

11

x-axis

+

N/A

N/A

y-axis

N/A

N/A

+

这样每一个初始MV在每个方向上的每种步长都会形成一个新的MV,该MV包含三个信息,分别是起始点、搜索方向、搜索步长,因此一个初始的MV可以扩展出32个新的MV,所以两个初始MV共生成64个新的MV,分别对这些新的MV也会通过运动补偿得到当前CU的预测值。

(3)在Skip和Merge模式下,将所有得到的64个新的预测值之间进行率失真代价比较,选出最优的MV,在编码时需要编码该MV的三个信息:初始MV在Merge list中的索引值、搜索方向和搜索步长。在解码端的MMVD模式中,当前CU通过接收到从编码端传过来的三个语法元素,以与编码端相同的搜索方式搜索形成最优的MV,再进行运动补偿得到当前CU的预测值。

这里需要注意的是,MVD的符号因初始MV信息的不同而有所不同。

  1. 当初始MV为单向预测,或者为双向预测且前后双向的参考帧均在当前帧的同一侧时(即双向的参考图片的POC均大于或均小于当前图片的POC),就按照表5.2中指示的符号添加到该初始MV(前向和后向的符号都保持一致)的偏移值上去;
  2. 当初始MV为双向预测且前后双向的参考帧在当前帧的两侧时(即一向参考图片的POC大于当前图片的POC,另一向参考图像的POC小于当前图像的POC),则将表5.2中指示的符号添加到前向初始MV的偏移值上,而后向初始MV的偏移值取与前向相反的符号。

MVD根据每个方向上POC的差异进行缩放。 如果两个列表中POC的差异相同,则无需缩放。 否则,如果列表0中的POC差大于列表1中的POC,则通过将L0的POC差定义为td并将L1的POC差定义为tb来缩放列表1的MVD,如下图所示。 如果L1的POC差大于L0,则列表0的MVD以相同的方式缩放。 如果起始MV是单一预测的,则MVD将添加到可用MV。

VTM中代码及注释如下:(基于VTM10.0)

(1)从常规Merge候选列表中选取初始MV

//从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++) //遍历Merge候选列表{if (mrgCtx.mrgTypeNeighbours[k] == MRG_TYPE_DEFAULT_N){//只取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++;if (currBaseNum == MMVD_BASE_MV_NUM) //只取前两个MV{break;}}}
}

(2)基于初始MV,对每种初始MV进行细化搜索,总共得到64种细化MV,遍历这64种细化的MV,通过率失真准则选出最优MV

      if ( pu.cs->sps->getUseMMVD() ) //使用MMVD模式,这里选出最优的MMVD的Merge候选(初始MV+搜索方向+搜索步长){cu.mmvdSkip = true;pu.regularMergeFlag = true; //普通Merge列表依旧可用,因为初始的MV还是要从普通Merge中获取// 一个MV产生32种,两个初始MV产生64种MVconst int tempNum = (mergeCtx.numValidMergeCand > 1) ? MMVD_ADD_NUM : MMVD_ADD_NUM >> 1;//对MMVD候选循环遍历,由于每个初始MV共有4种搜索方向,8种搜索步长,即每个初始MV产生32种细化MVfor (int mmvdMergeCand = 0; mmvdMergeCand < tempNum; mmvdMergeCand++){int baseIdx = mmvdMergeCand / MMVD_MAX_REFINE_NUM;//初始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;//计算RD CostinsertPos = -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]);}}} // MMVD

其中,在setMmvdMergeCandiInfo函数中实现对每种MV进行各个方向各步长的搜索

// 设置MMVD的候选信息,candIdx就是32种组合中的一种模式的索引, candIdx值为0~64
void MergeCtx::setMmvdMergeCandiInfo(PredictionUnit& pu, int candIdx)
{const Slice &slice = *pu.cs->slice;const int mvShift = MV_FRACTIONAL_BITS_DIFF; // 2//参考MVD的候选,2,8,16,32,64,128,256const 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];//里面存储前向和后向MVtempIdx = candIdx;//组合新的MV索引fPosGroup = tempIdx / (MMVD_BASE_MV_NUM * MMVD_MAX_REFINE_NUM);//整个候选列表的初始位置为0tempIdx = tempIdx - fPosGroup * (MMVD_BASE_MV_NUM * MMVD_MAX_REFINE_NUM);//传进来扩展MV的索引fPosBaseIdx = tempIdx / MMVD_MAX_REFINE_NUM;//初始MV的索引(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); // 前向参考帧的POCconst int poc1 = slice.getRefPOC(REF_PIC_LIST_1, refList1); // 后向参考帧的POCconst int currPoc = slice.getPOC();// 当前帧的POC//前向MV的四种搜索方向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) //双向的参考图片的POC均大于或均小于当前图片的POC{tempMv[0] = tempMv[1];}else //一向参考图片的POC大于当前图片的POC,另一向参考图像的POC小于当前图像的POC{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());}}elsetempMv[1] = tempMv[0].scaleMv(scale);}pu.interDir = 3;//这里为每个选中的MV选择起始MV然后加上然后加上步长和方向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] = mmvdBaseMv[fPosBaseIdx][1].mv + tempMv[1];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;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;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);
}

H.266/VVC帧间预测技术学习:带有运动矢量差的Merge技术(Merge mode with MVD)相关推荐

  1. H.266/VVC帧间预测总结

    一.帧间预测基本原理 帧间预测是利用视频帧与帧之间的相关性,去除视频帧间的时间冗余信息.统计表明,帧间差绝对值超过3的像素平均不到一帧像素的4%,因此,采用高效的帧间编码方式,可以很大程度上提高视频压 ...

  2. H.266/VVC帧间预测技术学习:高级运动矢量预测(Advanced Motion Vector Prediction, AMVP)

    高级运动矢量预测模式(Advanced Motion Vector Prediction,AMVP) AMVP模式是H.265/HEVC中提出的新的MV预测技术,H.266/VVC仍采用了该技术,并在 ...

  3. H.266/VVC帧间预测技术学习:帧间和帧内联合预测(Combined inter and intra prediction, CIIP)

    在HEVC中一个CU在预测时要么使用帧内预测要么使用帧间预测,二者只能取其一.而VVC中提出的CIIP技术,是将帧间预测信号与帧内预测信号相结合. 在VVC中,当CU以Merge模式编码时,且CU包含 ...

  4. H.266/VVC帧间预测技术学习:几何划分模式(Geometric partitioning mode, GPM)

    几何划分模式 (Geometric partitioning mode ,GPM)原理 针对图像中运动物体的边界部分,VVC采用了几何划分模式进行帧间预测.如下图所示,GPM模式在运动物体的边界处进行 ...

  5. H.266/VVC帧间预测技术学习:CU级双向加权预测(Bi-prediction with CU-level weight)

    CU级双向加权预测(Bi-prediction with CU-level weight ,BCW) 在HEVC中,通过对从两个不同参考图片获得的两个预测信号求平均和/或使用两个不同运动矢量来生成双向 ...

  6. H.266/VVC帧间预测技术学习:解码端运动矢量细化(Decoder side motion vector refinement, DMVR)

    解码端运动矢量细化(Decoder side motion vector refinement, DMVR) 为了提高Merge模式的MV的准确性,在VVC中使用了基于双边匹配(BM)的解码端运动矢量 ...

  7. 【十三】 H.266/VVC | 帧间预测技术 | 解码端运动向量修正技术(DMVR)

    目的:为了提高merge模式下双向预测MV的准确性 基本思路:双向预测是在list0和list1中分别寻找一个运动向量,然后将MV0和MV1所指向的预测块进行加权得到最终预测块,而DMVR技术不是直接 ...

  8. H.266/VVC帧间预测技术学习:双向光流技术(Bi-directional optical flow, BDOF)

    双向光流技术(Bi-directional optical flow,BDOF ) VVC中采用了双向光流技术来修正双向预测的像素值.BDOF以前被称为BIO,包含在JEM参考软件中.与JEM中的版本 ...

  9. H.266/VVC帧内预测总结

    一.帧内预测基本原理 帧内预测技术是利用同一帧中相邻像素的相关性,利用当前块相邻区域的重建像素预测当前块中像素的技术,如下图所示,当前CU可以利用相邻A.B.C.D和E位置处的重建像素来预测当前CU中 ...

最新文章

  1. 三层交换不同VLAN间通信
  2. php编写猜拳游戏,Python中猜拳游戏与猜筛子游戏的实现方法
  3. 修改mysql编码方式centos_CentOS下修改mysql数据库编码为UTF-8(附mysql开启远程链接和开放3306端口)...
  4. 【车牌识别】+【模板匹配】基于智能交通的车牌识别系统
  5. 微软发布 OpenJDK 预览版!
  6. DWR第四篇之对象传参
  7. 20191003每日一句
  8. ViBe算法source code
  9. 2021微信透明头像!真实有用
  10. 多少天能学会php,如何在十天内学会php之第八天_php
  11. Python Matplotlib数据可视化绘图之(一)————柱状图
  12. ccc-sklearn-9-SVM(3)
  13. TpyeScript基础
  14. 攻防世界 pwn forgot
  15. python 股票量化盘后分析系统V0.42
  16. 巴菲特与搭档查理芒格手把手教你如何读财报,唯一一篇百看不厌炒股最实用文章
  17. 使用Mbrfix卸载Ubuntu系统
  18. 【零 zedboard】从SD卡中启动
  19. php 系统平均负载,理解 Linux 的平均负载和性能监控
  20. 一个空格引发的销售订单中“物料未对*销售组织语言定义”问题

热门文章

  1. 一起学 WebGL:感受三维世界之视图矩阵
  2. 创新型中药制药企业苏轩堂将在纳斯达克挂牌交易
  3. JDK自带工具查看内存
  4. 设置电子围栏 高德地图_高德地图韦东:厘米级定位电子围栏,可规范共享单车“乱停放”...
  5. 免费版网管软件,轻松解决六大网络问题
  6. PDF转HTML格式怎么转?这些方法值得收藏
  7. mysql与ofbiz_ofbiz连接mysql并创建独立数据库
  8. 概率论与数量统计(二)_第2章随机变量及其概率分布__贝努利试验
  9. unity网络实战开发(丛林战争)-前期知识准备(008-粘包和分包及解决方案)
  10. 计算机教育在线投稿,《计算机教育》杂志投稿的一些成功技巧