欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-07 13:31:12
...

目的:为了提高merge模式下双向预测MV的准确性

基本思路:双向预测是在list0和list1中分别寻找一个运动向量,然后将MV0和MV1所指向的预测块进行加权得到最终预测块,而DMVR技术不是直接使用MV0和MV1,而是在MV0和MV1周围1-2个像素范围内搜索一个更加精确的MV,具体做法是直接计算前后两个经过偏移后的预测块(下图中红色的部分)之间的SAD值,选取SAD值较小的那个偏移后的MV的组合,即为要选取的修正后的MV,并最终利用修正后的MV生成当前块的双向加权预测值。

注意:DMVR生成的修正MV用于生成帧间预测值和后续连续图像编码的时域运动向量预测值(TMVR);原始的MV用于去方块效应和后续CU编码的空域运动向量预测值(SMVP)

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

当满足下列所有条件时,可以使用该技术,即dmvrFlag被置为1
  • CU使用merge模式,且是双向预测
  • 两个参考帧分别位于当当前帧之前和当前帧之后
  • 当前图片与前向参考图片的POC差和当前图片与后向参考图片的POC差相等
  • CU至少需要有128个像素点
  • CU的宽和高都大于或等于8
  • BCW使用相等的权值
  • 当前块使不使用WP模式

所以条件在具体代码中实现如下:

bool PU::checkDMVRCondition(const PredictionUnit& pu)   //传入的参数为当前编码的PU
{
  WPScalingParam *wp0;//前向WP加权权重
  WPScalingParam *wp1;//后向WP加权权重
  int refIdx0 = pu.refIdx[REF_PIC_LIST_0];   //前向参考帧在L0中的索引值
  int refIdx1 = pu.refIdx[REF_PIC_LIST_1];   //后向参考帧在L1中的索引值
  pu.cu->slice->getWpScaling(REF_PIC_LIST_0, refIdx0, wp0);  //获取前向WP的权重
  pu.cu->slice->getWpScaling(REF_PIC_LIST_1, refIdx1, wp1);  //获取后向WP的权重
  if (pu.cs->sps->getUseDMVR() && (!pu.cs->picHeader->getDisDmvrFlag()))
  {
    //满足下列所有条件函数的返回值为真
    return pu.mergeFlag   //为merge模式
      && pu.mergeType == MRG_TYPE_DEFAULT_N  //为常规的merge模式
      && !pu.ciipFlag   //非CIIP模式
      && !pu.cu->affine //非Affine模式
      && !pu.mmvdMergeFlag  //非MMVD_Merge模式
      && !pu.cu->mmvdSkip   //非mmvd_Skip模式
      && PU::isBiPredFromDifferentDirEqDistPoc(pu)  //当前图片与前向参考图片的POC差和当前图片与后向参考图片的POC差相等
      && (pu.lheight() >= 8)  //PU的高大于或等于8
      && (pu.lwidth() >= 8)   //PU的宽大于或等于8
      && ((pu.lheight() * pu.lwidth()) >= 128)   //PU的尺寸大于或等于128
      && (pu.cu->BcwIdx == BCW_DEFAULT)
#if JVET_Q0128_DMVR_BDOF_ENABLING_CONDITION
      //亮度分量和相应的色度分量的前向MV权重和后向MV权重存在flag都等于0(即都不可以用)
      && ((!wp0[COMPONENT_Y].bPresentFlag) && (!wp0[COMPONENT_Cb].bPresentFlag) && (!wp0[COMPONENT_Cr].bPresentFlag) && (!wp1[COMPONENT_Y].bPresentFlag) && (!wp1[COMPONENT_Cb].bPresentFlag) && (!wp1[COMPONENT_Cr].bPresentFlag))
#else
      && ((!wp0[COMPONENT_Y].bPresentFlag) && (!wp1[COMPONENT_Y].bPresentFlag))
#endif
#if JVET_Q0487_SCALING_WINDOW_ISSUES  //缩放窗口问题
      && ( refIdx0 < 0 ? true : (pu.cu->slice->getRefPic( REF_PIC_LIST_0, refIdx0 )->isRefScaled( pu.cs->pps ) == false) )
      && ( refIdx1 < 0 ? true : (pu.cu->slice->getRefPic( REF_PIC_LIST_1, refIdx1 )->isRefScaled( pu.cs->pps ) == false) )
#else
      && ( refIdx0 < 0 ? true : pu.cu->slice->getScalingRatio( REF_PIC_LIST_0, refIdx0 ) == SCALE_1X ) && ( refIdx1 < 0 ? true : pu.cu->slice->getScalingRatio( REF_PIC_LIST_1, refIdx1 ) == SCALE_1X )
#endif
      ;
  }
  else
  {
    return false;
  }
}
DMVR技术在VTM8.0中的具体实现
  • 第一步:对初始点的周边位置进行搜索,以寻求更优的MV的偏移位置

由于在VTM中需要计算前后两向的两个候选MV的预测块之间的SAD值,因此使得每一个搜索点处MV的偏移都要遵循一个镜像原则,意思就是前后两个初始MV都必须同时使用同一个搜索点处的MV的偏移,然后按照镜像规则,前向初始点加一个偏移,后向的初始点减去对应的相同的偏移,具体公式如下所示:

MVL0=MVL0+MVoffsetMVL1=MVL1MVoffset MV_{L_0}'=MV_{L_0}+MV_{offset}\\ MV_{L_1}'=MV_{L_1}-MV_{offset}
void InterPrediction::xProcessDMVR相关代码:

//初始的MV计算出的子CU的前向及后向预测值(这里前后向的搜索点遵循净吸纳过方式,即对于同一个MV偏移,前向MV是加上偏移量,后向MV是减去偏移量)
Pel *addrL0 = biLinearPredL0 + totalDeltaMV[0] + (totalDeltaMV[1] * m_biLinearBufStride);
Pel *addrL1 = biLinearPredL1 - totalDeltaMV[0] - (totalDeltaMV[1] * m_biLinearBufStride);
//子pu的最终修正后的MV是原来的Merge的初始MV加上子pu的修正MVD(即MV的偏置,前向加偏置,后向减去偏置,呈现镜像的方式)
subPu.mv[0] = mergeMv[REF_PIC_LIST_0] + pu.mvdL0SubPu[num];
subPu.mv[1] = mergeMv[REF_PIC_LIST_1] - pu.mvdL0SubPu[num];

其中MV偏移量代表的是修正后的MV和初始MV之间的偏移量,在VTM中,搜索的最大偏移量为2个整像素,因此总共有25个搜索点(一个初始点和24个周围的点),在VTM8.0代码中的缓存如下(可以在commmonLib库中的InterPrediction.h头文件中找到):

//25个搜索偏置,其中一个是零偏置,代表初始MV本身,其余24个搜素点都是在初始MV基础上在水平和垂直方向上进行一定整像素的偏置,最多偏移2个整像素位置
Mv m_pSearchOffset[25] = { Mv(-2,-2), Mv(-1,-2), Mv(0,-2), Mv(1,-2), Mv(2,-2),
                             Mv(-2,-1), Mv(-1,-1), Mv(0,-1), Mv(1,-1), Mv(2,-1),
                             Mv(-2, 0), Mv(-1, 0), Mv(0, 0), Mv(1, 0), Mv(2, 0),
                             Mv(-2, 1), Mv(-1, 1), Mv(0, 1), Mv(1, 1), Mv(2, 1),
                             Mv(-2, 2), Mv(-1, 2), Mv(0, 2), Mv(1, 2), Mv(2, 2) };

在VTM8.0中的DMVR的搜索过程,分为整像素搜索阶段和分像素搜索阶段,具体流程如下图所示:

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

a.整像素搜索阶段

在VTM8.0中,为了减少算法复杂度,在整像素搜索阶段中使用了提前终止算法,用来代替25个位置的全搜索,具体算法为:在DMVR的正像素偏移搜索阶段,首先计算初始MV对得到的前后向预测块的SAD,并将SAD减少其原来的1/4.如果减少1/4倍的初始MV对的SAD值仍然小于一个阈值(256),则终止DMVR的整像素搜索阶段,否则,将按照光栅扫描的顺序计算并检查其余24个搜索点的SAD值,知道选出最优的整像素偏移值。在xProcessDMVR中的具体代码如下:

          if (i == 0)
          {
            //计算初始MV的SADCost,并赋值到minCost中,作为一个初始的SADCost(该Cost就是计算两个预测块addrL0以及addrL1之间的差值)
            minCost = xDMVRCost(clpRngs.comp[COMPONENT_Y].bd, addrL0, m_biLinearBufStride, addrL1, m_biLinearBufStride, dx, dy);
            //在初始的minCost之上减去其值的1/4倍,目的是为了优先选出初始的MV
            minCost -= (minCost >>2);
            //如果减去了1/4的初始MV的Cost值后,minCost的值小于当前子PU的尺寸,即小于一定阈值,则提前终止整个的搜索阶段
            if (minCost < (dx * dy))
            {
              //满足阈值条件的话notZeroCost = false,就说明后面不进行分像素阶段的搜索了
              notZeroCost = false;
              break;
            }
            pSADsArray[0] = minCost;
          }

b.分像素搜素阶段

前提:如果整像素搜索阶段由于初始MV的(SAD-1/4SAD)小于一定阈值而被提前终止,则后面既不对剩余的24点进行整像素搜索,也不进行分像素搜索

1.如果在整像素上搜索阶段结束了之后,得到的偏移值仍然是(0,0),说明初始MV就是最有的MV,因而可以直接进入分像素搜索阶段,通过初始MV点及其周围的4个点的SAD值,通过二维抛物线误差面方程解出最有的分像素的MV偏移值,稍微用分像素去小小的修正一下即可,不需要整像素级别的大修正

2.如果在整像素搜索阶段结束了之后,得到的偏移值不为(0,0),说明初始MV并不是最好的,最有的MV是其整像素范围内的某一个点,因此在遍历完25个搜素点后得到SAD值最小的那个整像素点偏移,对初始MV进行修正;然后再进行分像素搜索,同样用二维抛物线误差面方程解出最优的分像素MV偏移值。最终的修正MV是在初始MV的基础上偏移一个整像素偏移值加上一个分像素偏移值。这里需要注意的一点是,如果在进行完整像素的搜索阶段之后,搜出的最优偏移位置,水平偏移分量或者垂直偏移分量中有一个为正负2个像素的单位的话,则不能进行分像素阶段的搜索,因为处于最外层的搜索点如果作为中心点的话,其周围四分方向的某些点可能是不存在的,导致无法进行分像素阶段的搜索工作。

具体分像素搜索阶段考虑到复杂度的问题,在该阶段采用解二维抛物线误差面方程的方法代替传统的比较SAD值的方法。在正像素阶段我们可以得到中心点及其周围4个点的SADCost(中心、上、下、左、右),该过程需要求解一个二维的抛物线误差面方程,将5个整像素搜索点的SAD值及其相应的整像素偏移坐标代入,然后求解方程,最终接出Cost最小的分像素的位置(Xmin,Ymin),即(dMvX,dMvY),具体公式如下所示:

E(x,y)=A(xXmin)2+B(yYmin)2+C其中:(xmin,ymin)对应cost最小的分像素位置,C对应最小的costx,y对应相对于中心位置的偏移量 E\left( x,y \right) =A\left( x-X_{\min} \right) ^2+B\left( y-Y_{\min} \right) ^2+C\\ \text{其中:}\left( x_{\min},y_{\min} \right) \text{对应}\cos t\text{最小的分像素位置,}C\text{对应最小的}\cos t\text{值}\\ x,y\text{对应相对于中心位置的偏移量}

通过使用5个点的cost值可以求解出上面的方程,如下图所示,得到:

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

xmin=(E(1,0)E(1,0))  /(2(E(1,0)+E(1,0)2E(0,0)))  ymin=(E(0,1)E(0,1))  /(2(E(0,1)+E(0,1)2E(0,0))) x_{\min}=\left( E\left( -1,0 \right) -E\left( 1,0 \right) \right) \,\, / \left( 2\left( E\left( -1,0 \right) +E\left( 1,0 \right) -2E\left( 0,0 \right) \right) \right) \,\,\\y_{\min}=\left( E\left( 0,-1 \right) -E\left( 0,1 \right) \right) \,\,/ \left( 2\left( E\left( 0,-1 \right) +E\left( 0,1 \right) -2E\left( 0,0 \right) \right) \right)
求解x_min时,只会用到中心点横轴上的三个蓝色点;求解y_min时,只会用到中心点纵轴上的三个蓝色点,最终求得的分像素点如图的红色位置。

在具体代码中,五个点的SAD表示为:sadBuffer[0] (中心点位置)、sadBuffer[1] (左)、sadBuffer[2] (上)、sadBuffer[3] (右) 、sadBuffer[4] (下)

 	sadbuffer[0] = pSADsArray[0];
    sadbuffer[1] = pSADsArray[-1];
    sadbuffer[2] = pSADsArray[-sadStride];
    sadbuffer[3] = pSADsArray[1];
    sadbuffer[4] = pSADsArray[sadStride];

DMVR的分像素MV的搜索过程中除法的输出值会被限制到-8~8之间,也即意味着分像素的精度的偏移量最多不能超过1/2像素的距离,而且因0以实现分像素级别的细化。最终初始向量加上整像素偏移量再加上一个分像素的偏移量即可得到最终的修正MV

  • 第二步:得到的细化MV后,利用子块参考区域生成最终的运动补偿预测块

在VTM8.0中,为了减少子块之间填充的不均匀性和减少DMVR需要的内存带宽,建议在子块的参考区域边界进行填充。具体来说,就是在MV细化的过程中,每个自愧啊MV细化后的运动补偿过程仅使用子块参考区域内的23 * 23 个参考样本点,这样设计的目的可以使得DMVR的硬件设计更加规范、简单。

代码中的具体实现细节:

//滤波器的尺寸,亮度8抽头,色度4抽头
int filtersize = (compID == (COMPONENT_Y)) ? NTAPS_LUMA : NTAPS_CHROMA; 
      cMv += Mv(-(((filtersize >> 1) - 1) << mvshiftTemp), -(((filtersize >> 1) - 1) << mvshiftTemp));
      bool wrapRef = false;
      if (pu.cs->sps->getWrapAroundEnabledFlag())
      {
        wrapRef = wrapClipMv(cMv, pu.blocks[0].pos(), pu.blocks[0].size(), pu.cs->sps, pu.cs->pps);
      }
      else
      {
        clipMv(cMv, pu.lumaPos(), pu.lumaSize(), *pu.cs->sps, *pu.cs->pps);
      }
//参考块的宽度和高度增加7,使得其器尺寸变为23*23
      int width = pcYuvDst.bufs[compID].width + (filtersize - 1);
      int height = pcYuvDst.bufs[compID].height + (filtersize - 1);
问题归纳
  • 为什么实用SAD作为代价比较的基准,而不是实用RDCost?

答:因为该技术只是用于解码端的MV的修正,对于解码端而言,它已经接受到了确知的码流信息,因此码率已经无法作为率失真的衡量基准,只有失真可以衡量,所以只需要通过SAD值计算代价值就可以了

  • DMVR技术在编码端和解码端需要同时执行,虽然该技术是针对解码端的,但是在编码端执行同样的操作,这是为了保证编解码的一致性
  • 初始的MV在Merge候选列表中的索引值需要保存下来,并传输到解码端,然后在解码端执行相同的DMVR操作
  • 由于最大的DMVR的搜索范围是16*16,因此如果当前PU的宽或者高大于16,就必须将其划分为子块进行搜索,每个子块的参考区域大小为23 *23
  • VVC中MV是1/16的像素精度,这些分像素是通过8抽头的插值滤波器生成的。在DMVR中,为了减少计算复杂性在分像素搜索阶段使用双线性插值滤波器生成分像素。另外,使用双线性插值滤波器只需要访问周围2个像素范围,而不用想普通运动补偿一样需要参考更多的像素,当获得最终修正MV之后,在生成预测值是需要使用到8抽头插值滤波器。

DMVR技术整体代码实现细节:

void InterPrediction::xProcessDMVR(PredictionUnit& pu, PelUnitBuf &pcYuvDst, const ClpRngs &clpRngs, const bool bioApplied)
{
  int iterationCount = 1;
  /*Always High Precision*/
  int mvShift = MV_FRACTIONAL_BITS_INTERNAL;

  /*use merge MV as starting MV*/
  Mv mergeMv[] = { pu.mv[REF_PIC_LIST_0] , pu.mv[REF_PIC_LIST_1] };

  m_biLinearBufStride = (MAX_CU_SIZE + (2 * DMVR_NUM_ITERATION));

  int dy = std::min<int>(pu.lumaSize().height, DMVR_SUBCU_HEIGHT);
  int dx = std::min<int>(pu.lumaSize().width,  DMVR_SUBCU_WIDTH);
  Position puPos = pu.lumaPos();

  int bd = pu.cs->slice->getClpRngs().comp[COMPONENT_Y].bd;

  int            bioEnabledThres = 2 * dy * dx;
  bool           bioAppliedType[MAX_NUM_SUBCU_DMVR];

#if JVET_J0090_MEMORY_BANDWITH_MEASURE
  JVET_J0090_SET_CACHE_ENABLE(true);
  for (int k = 0; k < NUM_REF_PIC_LIST_01; k++)
  {
    RefPicList refId = (RefPicList)k;
    const Picture* refPic = pu.cu->slice->getRefPic(refId, pu.refIdx[refId]);
    for (int compID = 0; compID < MAX_NUM_COMPONENT; compID++)
    {
      Mv cMv = pu.mv[refId];
      int mvshiftTemp = mvShift + getComponentScaleX((ComponentID)compID, pu.chromaFormat);
      int filtersize = (compID == (COMPONENT_Y)) ? NTAPS_LUMA : NTAPS_CHROMA;
      cMv += Mv(-(((filtersize >> 1) - 1) << mvshiftTemp), -(((filtersize >> 1) - 1) << mvshiftTemp));
      bool wrapRef = false;
      if (pu.cs->sps->getWrapAroundEnabledFlag())
      {
        wrapRef = wrapClipMv(cMv, pu.blocks[0].pos(), pu.blocks[0].size(), pu.cs->sps, pu.cs->pps);
      }
      else
      {
        clipMv(cMv, pu.lumaPos(), pu.lumaSize(), *pu.cs->sps, *pu.cs->pps);
      }

      int width = pcYuvDst.bufs[compID].width + (filtersize - 1);
      int height = pcYuvDst.bufs[compID].height + (filtersize - 1);

      CPelBuf refBuf;
      Position recOffset = pu.blocks[compID].pos().offset(cMv.getHor() >> mvshiftTemp, cMv.getVer() >> mvshiftTemp);
      refBuf = refPic->getRecoBuf(CompArea((ComponentID)compID, pu.chromaFormat, recOffset, pu.blocks[compID].size()), wrapRef);

      JVET_J0090_SET_REF_PICTURE(refPic, (ComponentID)compID);
      for (int row = 0; row < height; row++)
      {
        for (int col = 0; col < width; col++)
        {
          JVET_J0090_CACHE_ACCESS(((Pel *)refBuf.buf) + row * refBuf.stride + col, __FILE__, __LINE__);
        }
      }
    }
  }
  JVET_J0090_SET_CACHE_ENABLE(false);
#endif

  {
    int num = 0;

    int scaleX = getComponentScaleX(COMPONENT_Cb, pu.chromaFormat);
    int scaleY = getComponentScaleY(COMPONENT_Cb, pu.chromaFormat);
    m_biLinearBufStride = (dx + (2 * DMVR_NUM_ITERATION));
    // point mc buffer to cetre point to avoid multiplication to reach each iteration to the begining
    Pel *biLinearPredL0 = m_cYuvPredTempDMVRL0 + (DMVR_NUM_ITERATION * m_biLinearBufStride) + DMVR_NUM_ITERATION;
    Pel *biLinearPredL1 = m_cYuvPredTempDMVRL1 + (DMVR_NUM_ITERATION * m_biLinearBufStride) + DMVR_NUM_ITERATION;

    PredictionUnit subPu = pu;
    subPu.UnitArea::operator=(UnitArea(pu.chromaFormat, Area(puPos.x, puPos.y, dx, dy)));
    m_cYuvRefBuffDMVRL0 = (pu.chromaFormat == CHROMA_400 ?
      PelUnitBuf(pu.chromaFormat, PelBuf(m_cRefSamplesDMVRL0[0], pcYuvDst.Y())) :
      PelUnitBuf(pu.chromaFormat, PelBuf(m_cRefSamplesDMVRL0[0], pcYuvDst.Y()),
        PelBuf(m_cRefSamplesDMVRL0[1], pcYuvDst.Cb()), PelBuf(m_cRefSamplesDMVRL0[2], pcYuvDst.Cr())));
    m_cYuvRefBuffDMVRL0 = m_cYuvRefBuffDMVRL0.subBuf(UnitAreaRelative(pu, subPu));

    m_cYuvRefBuffDMVRL1 = (pu.chromaFormat == CHROMA_400 ?
      PelUnitBuf(pu.chromaFormat, PelBuf(m_cRefSamplesDMVRL1[0], pcYuvDst.Y())) :
      PelUnitBuf(pu.chromaFormat, PelBuf(m_cRefSamplesDMVRL1[0], pcYuvDst.Y()), PelBuf(m_cRefSamplesDMVRL1[1], pcYuvDst.Cb()),
        PelBuf(m_cRefSamplesDMVRL1[2], pcYuvDst.Cr())));
    m_cYuvRefBuffDMVRL1 = m_cYuvRefBuffDMVRL1.subBuf(UnitAreaRelative(pu, subPu));

    PelUnitBuf srcPred0 = (pu.chromaFormat == CHROMA_400 ?
      PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[0][0], pcYuvDst.Y())) :
      PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[0][0], pcYuvDst.Y()), PelBuf(m_acYuvPred[0][1], pcYuvDst.Cb()), PelBuf(m_acYuvPred[0][2], pcYuvDst.Cr())));
    PelUnitBuf srcPred1 = (pu.chromaFormat == CHROMA_400 ?
      PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[1][0], pcYuvDst.Y())) :
      PelUnitBuf(pu.chromaFormat, PelBuf(m_acYuvPred[1][0], pcYuvDst.Y()), PelBuf(m_acYuvPred[1][1], pcYuvDst.Cb()), PelBuf(m_acYuvPred[1][2], pcYuvDst.Cr())));

    srcPred0 = srcPred0.subBuf(UnitAreaRelative(pu, subPu));
    srcPred1 = srcPred1.subBuf(UnitAreaRelative(pu, subPu));

    int yStart = 0;
    for (int y = puPos.y; y < (puPos.y + pu.lumaSize().height); y = y + dy, yStart = yStart + dy)
    {
      for (int x = puPos.x, xStart = 0; x < (puPos.x + pu.lumaSize().width); x = x + dx, xStart = xStart + dx)
      {
        PredictionUnit subPu = pu;
        subPu.UnitArea::operator=(UnitArea(pu.chromaFormat, Area(x, y, dx, dy)));
        xPrefetch(subPu, m_cYuvRefBuffDMVRL0, REF_PIC_LIST_0, 1);
        xPrefetch(subPu, m_cYuvRefBuffDMVRL1, REF_PIC_LIST_1, 1);

        xinitMC(subPu, clpRngs);

        uint64_t minCost = MAX_UINT64;
        bool notZeroCost = true;
        int16_t totalDeltaMV[2] = { 0,0 };
        int16_t deltaMV[2] = { 0, 0 };
        uint64_t  *pSADsArray;
        for (int i = 0; i < (((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)); i++)
        {
         //25个整像素点SAD值的初始化操作
          m_SADsArray[i] = MAX_UINT64;
        }
        pSADsArray = &m_SADsArray[(((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)) >> 1];
        for (int i = 0; i < iterationCount; i++)
        {
          deltaMV[0] = 0;
          deltaMV[1] = 0;
          Pel *addrL0 = biLinearPredL0 + totalDeltaMV[0] + (totalDeltaMV[1] * m_biLinearBufStride);
          Pel *addrL1 = biLinearPredL1 - totalDeltaMV[0] - (totalDeltaMV[1] * m_biLinearBufStride);
          if (i == 0)
          { //计算初始MV对应的Cost
            minCost = xDMVRCost(clpRngs.comp[COMPONENT_Y].bd, addrL0, m_biLinearBufStride, addrL1, m_biLinearBufStride, dx, dy);
            minCost -= (minCost >>2);
            if (minCost < (dx * dy))
            {  //判断初始MV的Cost值是否小于阈值,提前终止整像素搜索
              notZeroCost = false;
              break;
            }
            pSADsArray[0] = minCost;
          }
          if (!minCost)
          {
            notZeroCost = false;
            break;
          }
//计算25个整像素点的对应的Cost
          xBIPMVRefine(bd, addrL0, addrL1, minCost, deltaMV, pSADsArray, dx, dy);

          if (deltaMV[0] == 0 && deltaMV[1] == 0)   //整像素是搜索阶段是否结束,这两个变量为整像素偏移量
          {
            break;
          }
          totalDeltaMV[0] += deltaMV[0];//记录整像素搜索阶段,水平偏移量
          totalDeltaMV[1] += deltaMV[1];//记录整像素搜索阶段,水平平移量
          pSADsArray += ((deltaMV[1] * (((2 * DMVR_NUM_ITERATION) + 1))) + deltaMV[0]);
        }

        bioAppliedType[num] = (minCost < bioEnabledThres) ? false : bioApplied;
        totalDeltaMV[0] = (totalDeltaMV[0] << mvShift);
        totalDeltaMV[1] = (totalDeltaMV[1] << mvShift);
        //计算分像素代价
        xDMVRSubPixelErrorSurface(notZeroCost, totalDeltaMV, deltaMV, pSADsArray);

        pu.mvdL0SubPu[num] = Mv(totalDeltaMV[0], totalDeltaMV[1]);
        PelUnitBuf subPredBuf = pcYuvDst.subBuf(UnitAreaRelative(pu, subPu));

        bool blockMoved = false;
        if (pu.mvdL0SubPu[num] != Mv(0, 0))
        {
          blockMoved = true;
#if JVET_Q0438_MONOCHROME_BUGFIXES
          if (isChromaEnabled(pu.chromaFormat))
          {
#endif
          xPrefetch(subPu, m_cYuvRefBuffDMVRL0, REF_PIC_LIST_0, 0);
          xPrefetch(subPu, m_cYuvRefBuffDMVRL1, REF_PIC_LIST_1, 0);
#if JVET_Q0438_MONOCHROME_BUGFIXES
          }
#endif
          xPad(subPu, m_cYuvRefBuffDMVRL0, REF_PIC_LIST_0);
          xPad(subPu, m_cYuvRefBuffDMVRL1, REF_PIC_LIST_1);
        }

#if JVET_Q0438_MONOCHROME_BUGFIXES
        int dstStride[MAX_NUM_COMPONENT] = { pcYuvDst.bufs[COMPONENT_Y].stride,
                                             isChromaEnabled(pu.chromaFormat) ? pcYuvDst.bufs[COMPONENT_Cb].stride : 0,
                                             isChromaEnabled(pu.chromaFormat) ? pcYuvDst.bufs[COMPONENT_Cr].stride : 0};
#else
        int dstStride[MAX_NUM_COMPONENT] = { pcYuvDst.bufs[COMPONENT_Y].stride, pcYuvDst.bufs[COMPONENT_Cb].stride, pcYuvDst.bufs[COMPONENT_Cr].stride };
#endif
        subPu.mv[0] = mergeMv[REF_PIC_LIST_0] + pu.mvdL0SubPu[num];
        subPu.mv[1] = mergeMv[REF_PIC_LIST_1] - pu.mvdL0SubPu[num];

        subPu.mv[0].clipToStorageBitDepth();
        subPu.mv[1].clipToStorageBitDepth();

        xFinalPaddedMCForDMVR(subPu, srcPred0, srcPred1, m_cYuvRefBuffDMVRL0, m_cYuvRefBuffDMVRL1, bioAppliedType[num], mergeMv
          , blockMoved
        );

        subPredBuf.bufs[COMPONENT_Y].buf = pcYuvDst.bufs[COMPONENT_Y].buf + xStart + yStart * dstStride[COMPONENT_Y];

#if JVET_Q0438_MONOCHROME_BUGFIXES
        if (isChromaEnabled(pu.chromaFormat))
        {
#endif
        subPredBuf.bufs[COMPONENT_Cb].buf = pcYuvDst.bufs[COMPONENT_Cb].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMPONENT_Cb]);

        subPredBuf.bufs[COMPONENT_Cr].buf = pcYuvDst.bufs[COMPONENT_Cr].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMPONENT_Cr]);
#if JVET_Q0438_MONOCHROME_BUGFIXES
        }
#endif
        xWeightedAverage(subPu, srcPred0, srcPred1, subPredBuf, subPu.cu->slice->getSPS()->getBitDepths(), subPu.cu->slice->clpRngs(), bioAppliedType[num]);
        num++;
      }
    }
  }
  JVET_J0090_SET_CACHE_ENABLE(true);
}

所调用计算整像素点Cost的函数实现:

void InterPrediction::xBIPMVRefine(int bd, Pel *pRefL0, Pel *pRefL1, uint64_t& minCost, int16_t *deltaMV, uint64_t *pSADsArray, int width, int height)
{
  const int32_t refStrideL0 = m_biLinearBufStride;
  const int32_t refStrideL1 = m_biLinearBufStride;
  Pel *pRefL0Orig = pRefL0;
  Pel *pRefL1Orig = pRefL1;
  for (int nIdx = 0; (nIdx < 25); ++nIdx)
  {//计算25个整像素点对应的Cost,并找出Cost值小的那一个
    int32_t sadOffset = ((m_pSearchOffset[nIdx].getVer() * ((2 * DMVR_NUM_ITERATION) + 1)) + m_pSearchOffset[nIdx].getHor());
    pRefL0 = pRefL0Orig + m_pSearchOffset[nIdx].hor + (m_pSearchOffset[nIdx].ver * refStrideL0);
    pRefL1 = pRefL1Orig - m_pSearchOffset[nIdx].hor - (m_pSearchOffset[nIdx].ver * refStrideL1);
    if (*(pSADsArray + sadOffset) == MAX_UINT64)
    {
      const uint64_t cost = xDMVRCost(bd, pRefL0, refStrideL0, pRefL1, refStrideL1, width, height);
      *(pSADsArray + sadOffset) = cost;
    }
    if (*(pSADsArray + sadOffset) < minCost)
    {
      minCost = *(pSADsArray + sadOffset);
      deltaMV[0] = m_pSearchOffset[nIdx].getHor();
      deltaMV[1] = m_pSearchOffset[nIdx].getVer();
    }
  }
}

所调用计算分像素点Cost的函数实现(具体实先调用xDMVRSubPixelErrorSurface函数,该函数再调用xSubPelErrorSrfc函数):

void xSubPelErrorSrfc(uint64_t *sadBuffer, int32_t *deltaMv)
{
  int64_t numerator, denominator;
  int32_t mvDeltaSubPel;
  int32_t mvSubPelLvl = 4;/*1: half pel, 2: Qpel, 3:1/8, 4: 1/16*/
                                                        /*horizontal*/
    numerator = (int64_t)((sadBuffer[1] - sadBuffer[3]) << mvSubPelLvl);
    denominator = (int64_t)((sadBuffer[1] + sadBuffer[3] - (sadBuffer[0] << 1)));

    if (0 != denominator)
    {
      if ((sadBuffer[1] != sadBuffer[0]) && (sadBuffer[3] != sadBuffer[0]))
      {
        mvDeltaSubPel = div_for_maxq7(numerator, denominator);
        deltaMv[0] = (mvDeltaSubPel);
      }
      else
      {
        if (sadBuffer[1] == sadBuffer[0])
        {
          deltaMv[0] = -8;// half pel
        }
        else
        {
          deltaMv[0] = 8;// half pel
        }
      }
    }

    /*vertical*/
    numerator = (int64_t)((sadBuffer[2] - sadBuffer[4]) << mvSubPelLvl);
    denominator = (int64_t)((sadBuffer[2] + sadBuffer[4] - (sadBuffer[0] << 1)));
    if (0 != denominator)
    {
      if ((sadBuffer[2] != sadBuffer[0]) && (sadBuffer[4] != sadBuffer[0]))
      {
        mvDeltaSubPel = div_for_maxq7(numerator, denominator);
        deltaMv[1] = (mvDeltaSubPel);
      }
      else
      {
        if (sadBuffer[2] == sadBuffer[0])
        {
          deltaMv[1] = -8;// half pel
        }
        else
        {
          deltaMv[1] = 8;// half pel
        }
      }
    }
  return;
}

更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!
【十三】 H.266/VVC | 帧间预测技术 | 解码端运动向量修正技术(DMVR)

相关标签: VVC/H.266