自学内容网 自学内容网

H266/VVC 帧间预测中 DMVR 技术

解码端运动矢量修正 DMVR

  1. 为了增强双向预测 merge 模式的 MV 的准确性,VVC 中应用了解码端运动矢量修正(Decoder side motion vector refinement, DMVR)。此技术主要计算参考帧列表 L0 和 L1 中两个候选块之间的失真。双向预测是在 L0 和 L1 中分别寻找一 个运动向量 MV0 和 MV1,然后将 MV0 和 MV1 所指向的预测块进行加权得到最终预测块,而 DMVR 技术不是直接使用 MV0 和 MV1,而是在 MV0 和 MV1 周围 1-2 个像素范围内搜索一个更加精确的 MV。 BM(双向匹配)方法计算参考图像列表L0和列表L1中两个候选块之间的失真。如图 36 所示,基于每个MV候选计算红色块之间的SAD。具有最低SAD的MV候选成为细化后的MV,并用于生成双预测信号。
    在这里插入图片描述
  2. 在 VVC 中,CU 满足以下条件时,可以应用 DMVR 技术:
    • 使用双预测MV的CU级 merge 模式
    • 相对于当前图像,一个参考图像在之前,另一个参考图像在之后
    • 两个参考图像到当前图像的距离(即POC差异)相同
    • 两个参考图像都是短期参考图像
    • CU具有超过64个亮度样本
    • CU的高度和宽度都大于或等于8个亮度样本
    • BCW权重索引表示等权重
    • 当前块未启用WP(Weighted Prediction,加权预测)
    • 当前块未使用CIIP(Combined Inter/Intra Prediction,组合帧间/帧内预测)模式
  3. DMVR过程生成的修正运动矢量 mv 用于生成帧间预测样本值,也用于未来图像编码的时域运动矢量预测(TMVP)。原始MV用于去块处理过程,也用于未来CU编码的空域运动矢量预测。
  4. 搜索方案: DMVR 技术第一步需要对初始点周边的位置进行搜索,以寻求更优的 MV 的偏移。计算前向、后向两个候选 MV 对的预测块之间的 SAD,使得每一个搜索点处 MV 的偏移都遵守镜像原则,即前、后向两个初始 MV 都必须同时使用同一个搜索点处的 MV 的偏移,按照镜像规则,前向初始点需要加上一个偏移, 后向的初始点减去对应的相同的偏移,如下公式所示:
    在这里插入图片描述
    • 其中 MVoffset 代表修正后 MV 和初始 MV 之间的偏移量。DMVR 的搜索范围是初始 MV 的 2 个整像素范围。
    • 搜索过程包括整像素偏移搜索阶段分像素修正阶段。整像素搜索偏移阶段以最多 2 个整像素为偏移,搜索初始 MV 和初始 MV 附近的 25 个点。首先计算初始 MV0 和 MV1 的 SAD(以 merge 模式 MV 作为初始 MV)。如果初始 MV0 和 MV1 的 SAD 小于阈值(阈值是 DMVR 搜索单元的像素数,对于 16x16 子 PU 阈值为 256),则终止 DMVR 的整数搜索过程。(为了减少 DMVR 改进不确定性的损失,进行阈值比较时使用的 SAD 为原始 SAD 减去其 SAD 值的 1/4,阈值为 DMVR 搜索单元的像素数)。否则,按照光栅扫描顺序计算并检查其余 24 点的 SAD,并选择 SAD 最小的点作为整像素搜索的结果。第二步就是对每个子块进行 MV 细化,利用子块的参考区域生成子块的最终运动补偿预测值。
    • 为了减小计算复杂度,分像素搜索使用参数误差曲面方程(parametric error surface equation)来代替 SAD 计算。在整像素搜索中,如果初始 MV0 和 MV1 的 SAD 小于阈值,会提前终止整像素搜索过程,也不再进行分像素搜索过程。
    • 在基于参数误差曲面的子像素偏移估计中,以整像素搜索得到的结果为中心, 其相邻的四个位置的 Cost 用于拟合以下形式的二维抛物线误差曲面方程:
      在这里插入图片描述
      • 其中 xmin , ymin 对应于 Cost 最小的分数位置,C 对应于最小 Cost 值。通过使用五个搜索点的 Cost 求解上述等式,其中 xmin , ymin 计算公式为:
        在这里插入图片描述

      • 由于所有 Cost 值均为正且最小值为 E(0,0),因此 xmin 和 ymin 的值会自动限制在-8 到 8 之间。将计算出的分像素(xmin, ymin)加到整像素细化的 MV 以获得分像素精确细化 MV。

  5. 在 DMVR 中为了降低计算复杂度,使用双线性插值滤波器生成分数像素, 用于 DMVR 中的搜索过程。通过使用双线性插值滤波器,在 2 个像素的搜索范围内,与正常的运动补偿过程相比,DVMR 不会访问更多的参考像素。在通过 DMVR 搜索过程获得修正 MV 之后,将使用常规的 8 抽头插值滤波器来生成最终预测值。
  6. 当CU(编码单元)的宽度和/高度大于16个亮度样本时,它将进一步被分割成宽度和/高度等于16个亮度样本的子块。DMVR搜索过程的最大单元尺寸限制为16x16
  7. 在 2019 年 6 月的JVET-O0297[CE9-related: Simplification for DMVR padding process] 提案中对 DMVR 技术的填充过程进行了简化。
    在这里插入图片描述在这里插入图片描述
  8. 在 2020 年 1 月的 JVET-Q2002[Algorithm description for Versatile Video Coding and Test Model 8 (VTM 8)] 提案中对 DMVR 技术进行了总结描述。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
  9. 在 VVenC 编码器中在 UnitTools.cpp 文件中 checkDMVRCondition 函数用来检测 DMVR 的应用条件。
bool CU::checkDMVRCondition(const CodingUnit& cu)
{
  if (!cu.cs->sps->DMVR || cu.cs->slice->picHeader->disDmvrFlag)
  {
    return false;
  }

  return cu.mergeFlag
    && cu.mergeType == MRG_TYPE_DEFAULT_N
    && !cu.ciip
    && !cu.affine
    && !cu.mmvdMergeFlag
    && !cu.mmvdSkip
    && CU::isBiPredFromDifferentDirEqDistPoc(cu)
    && (cu.lheight() >= 8)
    && (cu.lwidth() >= 8)
    && ((cu.lheight() * cu.lwidth()) >= 128)
    && (cu.BcwIdx == BCW_DEFAULT);
}
  1. 在 VVenC 编码器中 InterPrediction.h 文件中 DMVR 技术的 25 个 搜索点如下【但实际并没有应用到该数组?应该是从 VTM 移植算法直接拷贝该数组定义的】。
class DMVR : public InterPredInterpolation
{
  RdCost*                 m_pcRdCost;

  PelStorage              m_yuvPred[NUM_REF_PIC_LIST_01];
  PelStorage              m_yuvTmp[NUM_REF_PIC_LIST_01];
  PelStorage              m_yuvPad[NUM_REF_PIC_LIST_01];
  const 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) };
private:
  void     xCopyAndPad          ( const CodingUnit& cu, PelUnitBuf& pcPad, RefPicList refId, bool forLuma);
  void     xFinalPaddedMCForDMVR( const CodingUnit& cu, PelUnitBuf* dstBuf, const PelUnitBuf *refBuf, const bool bioApplied, const Mv startMV[NUM_REF_PIC_LIST_01], const Mv& refMV );

protected:
  DMVR();
  virtual ~DMVR();
  void destroy();
  void init                    ( RdCost* pcRdCost, const ChromaFormat chFormat );
  void xProcessDMVR            ( const CodingUnit& cu, PelUnitBuf& pcYuvDst, const ClpRngs &clpRngs, const bool bioApplied );
};
  1. 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的处理过程如xProcessDMVR函数。
void DMVR::xProcessDMVR( const CodingUnit& cu, PelUnitBuf& pcYuvDst, const ClpRngs &clpRngs, const bool bioApplied )
{
  PROFILER_SCOPE_AND_STAGE_EXT( 1, _TPROF, P_INTER_MRG_DMVR, cu.cs, CH_L );
  /*Always High Precision*/
  const int csx      = getChannelTypeScaleX( CH_C, cu.chromaFormat );
  const int csy      = getChannelTypeScaleY( CH_C, cu.chromaFormat );
  const int mvShift  = MV_FRACTIONAL_BITS_INTERNAL;
  const int mvShiftC = mvShift + csx;

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


  const int dy = std::min<int>(cu.lumaSize().height, DMVR_SUBCU_SIZE);
  const int dx = std::min<int>(cu.lumaSize().width,  DMVR_SUBCU_SIZE);

  const Position& puPos = cu.lumaPos();

  bool bioAppliedType[MAX_NUM_SUBCU_DMVR];

  // Do refinement search
  {
    const int bilinearBufStride = (cu.Y().width + (2 * DMVR_NUM_ITERATION));
    const int padSize = DMVR_NUM_ITERATION << 1;
    const int dstOffset = -( DMVR_NUM_ITERATION * bilinearBufStride + DMVR_NUM_ITERATION );

    /*use merge MV as starting MV*/
    Mv mergeMVL0 = cu.mv[L0][0];
    Mv mergeMVL1 = cu.mv[L1][0];

    /*Clip the starting MVs*/
    clipMv(mergeMVL0, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);
    clipMv(mergeMVL1, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);

    /*L0 MC for refinement*/
    {
      const Picture* refPic = cu.slice->getRefPic(L0, cu.refIdx[L0]);

      PelUnitBuf yuvTmp = PelUnitBuf(cu.chromaFormat, PelBuf(m_yuvTmp[L0].getBuf(COMP_Y).buf + dstOffset, bilinearBufStride, cu.lwidth() + padSize, cu.lheight() + padSize));

      mergeMVL0.hor -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
      mergeMVL0.ver -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);

      xPredInterBlk(COMP_Y, cu, refPic, mergeMVL0, yuvTmp, true, clpRngs[COMP_Y], false, false, L0, cu.lwidth() + padSize, cu.lheight() + padSize, true);
    }

    /*L1 MC for refinement*/
    {
      const Picture* refPic = cu.slice->getRefPic(L1, cu.refIdx[L1]);

      PelUnitBuf yuvTmp = PelUnitBuf(cu.chromaFormat, PelBuf(m_yuvTmp[L1].getBuf(COMP_Y).buf + dstOffset, bilinearBufStride, cu.lwidth() + padSize, cu.lheight() + padSize));

      mergeMVL1.hor -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);
      mergeMVL1.ver -= (DMVR_NUM_ITERATION << MV_FRACTIONAL_BITS_INTERNAL);

      xPredInterBlk(COMP_Y, cu, refPic, mergeMVL1, yuvTmp, true, clpRngs[COMP_Y], false, false, L1, cu.lwidth() + padSize, cu.lheight() + padSize, true);
    }

    // point mc buffer to center point to avoid multiplication to reach each iteration to the beginning
    const Pel* biLinearPredL0 = m_yuvTmp[0].getBuf( COMP_Y ).buf;
    const Pel* biLinearPredL1 = m_yuvTmp[1].getBuf( COMP_Y ).buf;
    const int bioEnabledThres = 2 * dy * dx;
    const int bd = cu.cs->slice->clpRngs[COMP_Y].bd;

    DistParam distParam = m_pcRdCost->setDistParam( nullptr, nullptr, bilinearBufStride, bilinearBufStride, bd, COMP_Y, dx, dy, 1, true );

    int num = 0;
    int yStart = 0;
    uint64_t sadArray[((2 * DMVR_NUM_ITERATION) + 1) * ((2 * DMVR_NUM_ITERATION) + 1)];

    for( int y = puPos.y; y < ( puPos.y + cu.lumaSize().height ); y = y + dy, yStart = yStart + dy )
    {
      for( int x = puPos.x, xStart = 0; x < ( puPos.x + cu.lumaSize().width ); x = x + dx, xStart = xStart + dx )
      {
        uint64_t minCost        = MAX_UINT64;

        // set all entries to MAX_UNIT64
        uint64_t *pSADsArray = &sadArray[( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) * ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) >> 1];

        const Pel* addrL0Centre = biLinearPredL0 + yStart * bilinearBufStride + xStart;
        const Pel* addrL1Centre = biLinearPredL1 + yStart * bilinearBufStride + xStart;

        const Pel* addrL0 = addrL0Centre;
        const Pel* addrL1 = addrL1Centre;

        distParam.org.buf = addrL0;
        distParam.cur.buf = addrL1;
        minCost  = distParam.distFunc( distParam ) >> 1;
        minCost -= ( minCost >> 2 );

        if( minCost < ( dx * dy ) )
        {
          cu.mvdL0SubPu[num] = Mv( 0, 0 );
        }
        else
        {
          int16_t totalDeltaMV[2] = { 0, 0 };
          int16_t deltaMV[2]      = { 0, 0 };

          pSADsArray[0] = minCost;
          pSADsArray    = sadArray;

          for( int ver = -2; ver <= 2; ver++ )
          {
            const int initHor = -2;
            const ptrdiff_t offset = initHor + ver * bilinearBufStride;
              
            distParam.org.buf = addrL0 + offset;
            distParam.cur.buf = addrL1 - offset;
              
            distParam.dmvrSadX5( distParam, pSADsArray, ver != 0 );

            for( int hor = -2; hor <= 2; hor++, pSADsArray++ )
            {
              Distortion cost = *pSADsArray;

              if( cost < minCost )
              {
                minCost    = cost;
                deltaMV[0] = hor;
                deltaMV[1] = ver;
              }
            }
          }

          pSADsArray = &sadArray[( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) * ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) >> 1];

          totalDeltaMV[0] += deltaMV[0];
          totalDeltaMV[1] += deltaMV[1];
          pSADsArray      += ( ( deltaMV[1] * ( ( ( 2 * DMVR_NUM_ITERATION ) + 1 ) ) ) + deltaMV[0] );
          totalDeltaMV[0]  = totalDeltaMV[0] * ( 1 << mvShift );
          totalDeltaMV[1]  = totalDeltaMV[1] * ( 1 << mvShift );

          xDMVRSubPixelErrorSurface( totalDeltaMV, deltaMV, pSADsArray );

          cu.mvdL0SubPu[num] = Mv( totalDeltaMV[0], totalDeltaMV[1] );
        }

        bioAppliedType[num] = ( minCost < bioEnabledThres ) ? false : bioApplied;

        num++;
      }
    }
  }

  // Final MC
  CodingUnit subCu = cu;
  subCu.UnitArea::operator=(UnitArea(cu.chromaFormat, Area(puPos.x, puPos.y, dx, dy)));
  PelUnitBuf subPredBuf = pcYuvDst.subBuf(UnitAreaRelative(cu, subCu));

  PelUnitBuf predBuf[NUM_REF_PIC_LIST_01];
  predBuf[L0] = m_yuvPred[L0].getCompactBuf( subCu );
  predBuf[L1] = m_yuvPred[L1].getCompactBuf( subCu );
  /* For padding */
  PelUnitBuf padBuf[NUM_REF_PIC_LIST_01];
  padBuf[L0] = m_yuvPad[L0].getBufPart(subCu);
  padBuf[L1] = m_yuvPad[L1].getBufPart(subCu);

  int x = 0, y = 0;
  int xStart = 0, yStart = 0;
  int num = 0;
  const int scaleX = getComponentScaleX(COMP_Cb, cu.chromaFormat);
  const int scaleY = getComponentScaleY(COMP_Cb, cu.chromaFormat);

  const ptrdiff_t dstStride[MAX_NUM_COMP] = { pcYuvDst.bufs[COMP_Y].stride, cu.chromaFormat != CHROMA_400 ? pcYuvDst.bufs[COMP_Cb].stride : 0, cu.chromaFormat != CHROMA_400 ? pcYuvDst.bufs[COMP_Cr].stride : 0 };
  for( y = puPos.y; y < ( puPos.y + cu.lumaSize().height ); y = y + dy, yStart = yStart + dy )
  {
    for( x = puPos.x, xStart = 0; x < ( puPos.x + cu.lumaSize().width ); x = x + dx, xStart = xStart + dx )
    {
      subCu.Y().x = x;
      subCu.Y().y = y;

      if( cu.chromaFormat != CHROMA_400 )
      {
        subCu.Cb().x = subCu.Cr().x = x >> csx;
        subCu.Cb().y = subCu.Cr().y = y >> csy;
      }

      Mv mv0 = mergeMv[REF_PIC_LIST_0] + cu.mvdL0SubPu[num]; mv0.clipToStorageBitDepth();
      Mv mv1 = mergeMv[REF_PIC_LIST_1] - cu.mvdL0SubPu[num]; mv1.clipToStorageBitDepth();

      bool padBufL0  = (mv0.hor >> mvShift)  != (mergeMv[0].hor >> mvShift)  || (mv0.ver >> mvShift)  != (mergeMv[0].ver >> mvShift);
      bool padBufL0C = (mv0.hor >> mvShiftC) != (mergeMv[0].hor >> mvShiftC) || (mv0.ver >> mvShiftC) != (mergeMv[0].ver >> mvShiftC);
        
      bool padBufL1  = (mv1.hor >> mvShift)  != (mergeMv[1].hor >> mvShift)  || (mv1.ver >> mvShift)  != (mergeMv[1].ver >> mvShift);
      bool padBufL1C = (mv1.hor >> mvShiftC) != (mergeMv[1].hor >> mvShiftC) || (mv1.ver >> mvShiftC) != (mergeMv[1].ver >> mvShiftC);

      padBufL0C &= cu.chromaFormat != CHROMA_400;
      padBufL1C &= cu.chromaFormat != CHROMA_400;

      if (padBufL0)  xCopyAndPad(subCu, padBuf[L0], L0, true);
      if (padBufL0C) xCopyAndPad(subCu, padBuf[L0], L0, false);
      if (padBufL1)  xCopyAndPad(subCu, padBuf[L1], L1, true);
      if (padBufL1C) xCopyAndPad(subCu, padBuf[L1], L1, false);

      xFinalPaddedMCForDMVR( subCu, predBuf, padBuf, bioAppliedType[num], mergeMv, cu.mvdL0SubPu[num] );

      subPredBuf.bufs[COMP_Y].buf  = pcYuvDst.bufs[COMP_Y].buf + xStart + yStart * dstStride[COMP_Y];
      if( cu.chromaFormat != CHROMA_400 )
      {
        subPredBuf.bufs[COMP_Cb].buf = pcYuvDst.bufs[COMP_Cb].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMP_Cb]);
        subPredBuf.bufs[COMP_Cr].buf = pcYuvDst.bufs[COMP_Cr].buf + (xStart >> scaleX) + ((yStart >> scaleY) * dstStride[COMP_Cr]);
      }

      xWeightedAverage( subCu, predBuf[L0], predBuf[L1], subPredBuf, bioAppliedType[num] );

      num++;
    }
  }
}
  1. 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的分像素搜索过程在 xDMVRSubPixelErrorSurface函数中完成。
static void xDMVRSubPixelErrorSurface( int16_t *totalDeltaMV, int16_t *deltaMV, uint64_t *pSADsArray )
{
  int sadStride = (((2 * DMVR_NUM_ITERATION) + 1));
  uint64_t sadbuffer[5];
  if( ( abs( totalDeltaMV[ 0 ] ) != ( 2 << MV_FRACTIONAL_BITS_INTERNAL ) )
   && ( abs( totalDeltaMV[ 1 ] ) != ( 2 << MV_FRACTIONAL_BITS_INTERNAL ) ) )
  {
    int32_t tempDeltaMv[2] = { 0,0 };
    sadbuffer[0] = pSADsArray[0];
    sadbuffer[1] = pSADsArray[-1];
    sadbuffer[2] = pSADsArray[-sadStride];
    sadbuffer[3] = pSADsArray[1];
    sadbuffer[4] = pSADsArray[sadStride];
    xSubPelErrorSrfc(sadbuffer, tempDeltaMv);
    totalDeltaMV[0] += tempDeltaMv[0];
    totalDeltaMV[1] += tempDeltaMv[1];
  }
}
  1. 在 VVenC 编码器中 InterPrediction.cpp 文件中 DMVR 技术的运动补偿如xFinalPaddedMCForDMVR函数。
void DMVR::xFinalPaddedMCForDMVR( const CodingUnit& cu, PelUnitBuf* dstBuf, const PelUnitBuf *refBuf, const bool bioApplied, const Mv mergeMv[NUM_REF_PIC_LIST_01], const Mv& refMv )
{
  int mvShift = MV_FRACTIONAL_BITS_INTERNAL;
  Mv mv[2];
  mv[L0] = mergeMv[L0] + refMv; mv[L0].clipToStorageBitDepth();
  mv[L1] = mergeMv[L1] - refMv; mv[L1].clipToStorageBitDepth();

  for (int k = 0; k < NUM_REF_PIC_LIST_01; k++)
  {
    RefPicList refId = (RefPicList)k;
    const Mv& cMv = mv[refId];
    Mv cMvClipped( cMv );
    clipMv(cMvClipped, cu.lumaPos(), cu.lumaSize(), *cu.cs->pcv);
    const Picture* refPic = cu.slice->getRefPic(refId, cu.refIdx[refId]);
    const Mv& startMv = mergeMv[refId];
    for (int compID = 0; compID < getNumberValidComponents(cu.chromaFormat); compID++)
    {
      int mvshiftTemp = mvShift + getComponentScaleX((ComponentID)compID, cu.chromaFormat);
      int deltaIntMvX = (cMv.hor >> mvshiftTemp) - (startMv.hor >> mvshiftTemp);
      int deltaIntMvY = (cMv.ver >> mvshiftTemp) - (startMv.ver >> mvshiftTemp);

      CHECK((abs(deltaIntMvX) > DMVR_NUM_ITERATION) || (abs(deltaIntMvY) > DMVR_NUM_ITERATION), "not expected DMVR movement");

      if (deltaIntMvX || deltaIntMvY)
      {
        const PelBuf& srcBuf = refBuf[refId].bufs[compID];
        int offset = (deltaIntMvY)*srcBuf.stride + (deltaIntMvX);

        xPredInterBlk( ( ComponentID ) compID, cu, nullptr, cMvClipped, dstBuf[refId], true, cu.cs->slice->clpRngs[compID], bioApplied, false, refId, 0, 0, 0, srcBuf.buf + offset, srcBuf.stride );
      }
      else
      {
        xPredInterBlk( ( ComponentID ) compID, cu, refPic,  cMvClipped, dstBuf[refId], true, cu.cs->slice->clpRngs[compID], bioApplied, false, refId, 0, 0, 0 );
      }
    }
  }
}

原文地址:https://blog.csdn.net/yanceyxin/article/details/145261874

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!