VVC帧间预测(七)BDOF

双向光流bi-directional optical flow (BDOF)

BDOF是由JEM中的BIO发展而来,相较于BIO,BDOF计算复杂度更低,尤其是乘法运算数量和乘数大小更低。

BDOF是用来修正CU的4x4子块的双向预测信号。当CU满足下面几个条件时可以使用BDOF技术:1)CU的高不等于4,且CU尺寸不为4x8;2)CU没有使用仿射模式或ATMVP merge模式编码;3)CU使用的是“真”双向预测,即它的两个参考帧一个播放顺序在其前另一个播放顺序在其后。BDOF仅用于亮度分量。

正如其名,BDOF基于光流的概念它假设物体的运动是平滑的。对于每个4x4的子块,通过使L0和L1的预测值的差值最小来计算运动修正量(Vx,Vy),然后用计算出来的运动修正值来调整4x4子块的双向预测值。BDOF的处理过程如下:

1、计算梯度

对前向和后向的预测值分别计算水平和垂直梯度。梯度值直接通过相邻值相减得到。

相关代码如下:

//!<计算水平和垂直梯度
void gradFilterCore(Pel* pSrc, int srcStride, int width, int height, int gradStride, Pel* gradX, Pel* gradY, const int bitDepth)
{
  Pel* srcTmp = pSrc + srcStride + 1;
  Pel* gradXTmp = gradX + gradStride + 1;
  Pel* gradYTmp = gradY + gradStride + 1;
#if JVET_N0325_BDOF	//!<shift1 = max( 6, bitDepth-6)
  int  shift1 = std::max<int>(6, (bitDepth - 6));
#else
  int  shift1 = std::max<int>(2, (IF_INTERNAL_PREC - bitDepth));
#endif

  for (int y = 0; y < (height - 2 * BIO_EXTEND_SIZE); y++)
  {
    for (int x = 0; x < (width - 2 * BIO_EXTEND_SIZE); x++)
    {//!<计算梯度
      gradYTmp[x] = (srcTmp[x + srcStride] - srcTmp[x - srcStride]) >> shift1;
      gradXTmp[x] = (srcTmp[x + 1] - srcTmp[x - 1]) >> shift1;
    }
    gradXTmp += gradStride;
    gradYTmp += gradStride;
    srcTmp += srcStride;
  }

  gradXTmp = gradX + gradStride + 1;
  gradYTmp = gradY + gradStride + 1;
  for (int y = 0; y < (height - 2 * BIO_EXTEND_SIZE); y++)
  {//!<边界梯度
    gradXTmp[-1] = gradXTmp[0];
    gradXTmp[width - 2 * BIO_EXTEND_SIZE] = gradXTmp[width - 2 * BIO_EXTEND_SIZE - 1];
    gradXTmp += gradStride;

    gradYTmp[-1] = gradYTmp[0];
    gradYTmp[width - 2 * BIO_EXTEND_SIZE] = gradYTmp[width - 2 * BIO_EXTEND_SIZE - 1];
    gradYTmp += gradStride;
  }

  gradXTmp = gradX + gradStride;
  gradYTmp = gradY + gradStride;
  ::memcpy(gradXTmp - gradStride, gradXTmp, sizeof(Pel)*(width));
  ::memcpy(gradXTmp + (height - 2 * BIO_EXTEND_SIZE)*gradStride, gradXTmp + (height - 2 * BIO_EXTEND_SIZE - 1)*gradStride, sizeof(Pel)*(width));
  ::memcpy(gradYTmp - gradStride, gradYTmp, sizeof(Pel)*(width));
  ::memcpy(gradYTmp + (height - 2 * BIO_EXTEND_SIZE)*gradStride, gradYTmp + (height - 2 * BIO_EXTEND_SIZE - 1)*gradStride, sizeof(Pel)*(width));
}

2、计算梯度的自相关和互相关

计算梯度的自相关和互相关S1,S2,S3,S5,S6

相关代码如下:

void calcBIOParCore(const Pel* srcY0Temp, const Pel* srcY1Temp, const Pel* gradX0, const Pel* gradX1, const Pel* gradY0, const Pel* gradY1, int* dotProductTemp1, int* dotProductTemp2, int* dotProductTemp3, int* dotProductTemp5, int* dotProductTemp6, const int src0Stride, const int src1Stride, const int gradStride, const int widthG, const int heightG, const int bitDepth)
{
#if JVET_N0325_BDOF
  int shift4 = std::max<int>(4, (bitDepth - 8));	//!<nb
  int shift5 = std::max<int>(1, (bitDepth - 11));	//!<na
#else
  int shift4 = std::min<int>(8, (bitDepth - 4));
  int shift5 = std::min<int>(5, (bitDepth - 7));
#endif
  //!<6x6 windows
  for (int y = 0; y < heightG; y++)
  {
    for (int x = 0; x < widthG; x++)
    {
      int temp = (srcY0Temp[x] >> shift4) - (srcY1Temp[x] >> shift4);   //!<theta(i,j)
      int tempX = (gradX0[x] + gradX1[x]) >> shift5;					//!<psix(i,j)
      int tempY = (gradY0[x] + gradY1[x]) >> shift5;					//!<psiy(i,j)
      dotProductTemp1[x] = tempX * tempX;								//!<S1
      dotProductTemp2[x] = tempX * tempY;								//!<S2
      dotProductTemp3[x] = -tempX * temp;								//!<S3
      dotProductTemp5[x] = tempY * tempY;								//!<S5
      dotProductTemp6[x] = -tempY * temp;								//!<S6
    }
    srcY0Temp += src0Stride;
    srcY1Temp += src1Stride;
    gradX0 += gradStride;
    gradX1 += gradStride;
    gradY0 += gradStride;
    gradY1 += gradStride;
    dotProductTemp1 += widthG;
    dotProductTemp2 += widthG;
    dotProductTemp3 += widthG;
    dotProductTemp5 += widthG;
    dotProductTemp6 += widthG;
  }
}

注:代码中na,nb的定义和草案中的不一致。

3、使用上面互相关和自相关的结果计算运动修正值(Vx,Vy)

相关代码如下:

       if (sGx2 > 0)
      {//!< vx = S1>0 ? (S3 << 3) >> |logS1| : 0
        tmpx = rightShiftMSB(sGxdI << 3, sGx2);
        tmpx = Clip3(-limit, limit, tmpx);
      }
      if (sGy2 > 0) //!< S5>0
      {
        int     mainsGxGy = sGxGy >> 12; //!< S~2m = S2 >> 12
        int     secsGxGy = sGxGy & ((1 << 12) - 1); //!< S~2s
        int     tmpData = tmpx * mainsGxGy; //!< vx*S~2m
        tmpData = ((tmpData << 12) + tmpx*secsGxGy) >> 1; //!< ((vx*S~2m)<<12 + vx*S~2s)/2
        tmpy = rightShiftMSB(((sGydI << 3) - tmpData), sGy2); //!< (S6<<3 - tmpData) >> |logS5|
        tmpy = Clip3(-limit, limit, tmpy);
      }

4、计算修正后的预测值

shift和offset在代码中定义如下:
  const int   shiftNum = IF_INTERNAL_PREC + 1 - bitDepth;
  const int   offset = (1 << (shiftNum - 1)) + 2 * IF_INTERNAL_OFFS;

#define IF_INTERNAL_PREC 14 ///< Number of bits for internal precision
#define IF_INTERNAL_OFFS (1<<(IF_INTERNAL_PREC-1)) ///< Offset used internally

上式在计算中乘数不超过15比特,且在计算BDOF过程中中间参数最多不超过32bit。

相关代码如下:

void addBIOAvgCore(const Pel* src0, int src0Stride, const Pel* src1, int src1Stride, Pel *dst, int dstStride, const Pel *gradX0, const Pel *gradX1, const Pel *gradY0, const Pel*gradY1, int gradStride, int width, int height, int tmpx, int tmpy, int shift, int offset, const ClpRng& clpRng)
{
  int b = 0;

  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x += 4)
    {//!<b(x,y)计算
      b = tmpx * (gradX0[x] - gradX1[x]) + tmpy * (gradY0[x] - gradY1[x]);
      b = ((b + 1) >> 1);
      dst[x] = ClipPel((int16_t)rightShift((src0[x] + src1[x] + b + offset), shift), clpRng); //!<计算pred_BDOF

      b = tmpx * (gradX0[x + 1] - gradX1[x + 1]) + tmpy * (gradY0[x + 1] - gradY1[x + 1]);
      b = ((b + 1) >> 1);
      dst[x + 1] = ClipPel((int16_t)rightShift((src0[x + 1] + src1[x + 1] + b + offset), shift), clpRng);

      b = tmpx * (gradX0[x + 2] - gradX1[x + 2]) + tmpy * (gradY0[x + 2] - gradY1[x + 2]);
      b = ((b + 1) >> 1);
      dst[x + 2] = ClipPel((int16_t)rightShift((src0[x + 2] + src1[x + 2] + b + offset), shift), clpRng);

      b = tmpx * (gradX0[x + 3] - gradX1[x + 3]) + tmpy * (gradY0[x + 3] - gradY1[x + 3]);
      b = ((b + 1) >> 1);
      dst[x + 3] = ClipPel((int16_t)rightShift((src0[x + 3] + src1[x + 3] + b + offset), shift), clpRng);
    }
    dst += dstStride;       src0 += src0Stride;     src1 += src1Stride;
    gradX0 += gradStride; gradX1 += gradStride; gradY0 += gradStride; gradY1 += gradStride;
  }
}

在第一步计算梯度的过程中,在计算边缘处的梯度时会超出当前CU的边界。为了解决这个问题VTM5在使用BDOF时会在CU边界扩展一行/列,如下图所示。为了控制生成扩展预测值的复杂度,扩展区域(白色位置)的值直接使用最近的整数像素位置的参考值,不需要进行插值计算。对于CU内部区域(灰色位置)用8抽头插值滤波器进行插值。这些扩展值仅用于梯度计算。对于BDOF后续步骤,如果需要使用CU边界之外的任何样本和梯度值,则从其最近的像素中填充(即重复)它们。

当亮度CU的宽和/或高大于16时,需要将其划分为宽和/或高等于16的子块,在BDOF处理过程中子块边界被当作CU边界。BDOF能处理的最大块为16x16。

如果当前块启用了BCW,即BCW权重索引指示权重不相等,则将禁用双向光流。同样,如果对于当前块启用了WP,即,对于两个参考图片中的任一个,luma_weight_lx_flag 标志为1,则也禁用了BDOF。当CU用对称MVD模式或CIIP模式编码时,BDOF也被禁用。

参考

JVET-N1002

JVET-N0147

JVET-N0152

JVET-N0325

感兴趣的请关注微信公众号Video Coding

发布了87 篇原创文章 · 获赞 108 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/Dillon2015/article/details/103895663
今日推荐