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

【十二】 H.266/VVC | 帧间预测技术 | 双向光流技术BDOF

程序员文章站 2022-07-07 14:18:22
...

一、前言

双向光流技术是由JEM参考模型中的BIO技术发展而来,相较于BIO,BDOF计算的复杂度更低,尤其是乘法运算数量和乘数大小更小。

作用:用来修正CU的4 * 4子块CU的双向预测信号。

说明:BDOF基于光流的概念,它假设物体的运动是平滑的。对于每个4 * 4的子块,通过使前向预测L0和后向预测L1的预测值的差值最小来计算运动修正量(Vx,Vy)(Vx,V_y),然后用计算出来的修正值来调整4 * 4子块的双向预测值。同时该技术仅应用与亮度分量。

二、使用条件

BDOF用于在4 * 4子块级别上优化CU双向预测信号,如果BDOF满足一下所有条件,则将BDOF应用于CU:

  • 使用“真正的“双向预测模式,即两个参考帧的一帧在当前帧之前,另一帧在当前帧之后
  • 两个参考帧到当前帧的距离(即POC差)是相同的
  • 两个参考帧均为短期参考帧
  • CU未使用Affine模式或ATMVP模式进行编码
  • CU需要超过64个亮度像素值(4*16这种形式不可取)
  • CU高度和宽度均大于或者等于8
  • 对于BCW权重,要求等权重
  • 当前CU未启用加权预测
  • 当前CU不适用CIIP模式或TPM模式

三、具体实现

3.1 计算水平和垂直梯度

对前向和后向预测值分别计算水平和垂直梯度,梯度值直接通过相邻值相减得到,具体公式实现如下:


I(k)x(i,j)  =  ((I(k)(i+1,j))  >>  shift1  (I(k)(i1,j))  >>  shift1) \frac{\partial I^{\left( k \right)}}{\partial x}\left( i,j \right) \,\,=\,\,\left( \left( I^{\left( k \right)}\left( i+1,j \right) \right) \,\,>>\,\,shift1 -\,\,\left( I^{\left( k \right)}\left( i-1,j \right) \right) \,\,>>\,\,shift1 \right)


I(k)y(i,j)  =  ((I(k)(i,j+1))  >>  shift1  (I(k)(i,j1))  >>  shift1) \frac{\partial I^{\left( k \right)}}{\partial y}\left( i,j \right) \,\,=\,\,\left( \left( I^{\left( k \right)}\left( i,j+1 \right) \right) \,\,>>\,\,shift1 -\,\,\left( I^{\left( k \right)}\left( i,j-1 \right) \right) \,\,>>\,\,shift1 \right)


I(k)(i,j)Lk(k=0,1)中坐标(i,j)处的预测值 I^{\left( k \right)}\left( i,j \right) 是Lk\left( k=0,1 \right) \text{中坐标}为\left( i,j \right) \text{处的预测值}


shift1=max(6,bitDepth6),bitDepth亮度分量的比特深度 shift1=\max \left( 6,bitDepth-6 \right) ,bitDepth是\text{亮度分量的比特深度}

相关代码如下:

//计算水平和垂直梯度
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;
  int  shift1 = 6;   

  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] >> shift1 ) - ( srcTmp[x - srcStride] >> shift1 );
      gradXTmp[x] = ( srcTmp[x + 1] >> shift1 ) - ( srcTmp[x - 1] >> shift1 );
    }
    gradXTmp += gradStride;
    gradYTmp += gradStride;
    srcTmp += srcStride;
  }

  if (PAD)
  {
  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));
  }
}


3.2 计算梯度的自相关和互相关

计算梯度的自相关和互相关S1S2S3S4S5S6S_1、S_2、S_3、S_4、S_5、S_6

S1=(i,j)Ωψx(i,j)Ψx(i,j) S_1=\sum_{\left( i,j \right) \in \varOmega}{\psi _x\left( i,j \right) \cdot \varPsi _x\left( i,j \right)}


S2=(i,j)Ωψx(i,j)Ψy(i,j) S_2=\sum_{\left( i,j \right) \in \varOmega}{\psi _x\left( i,j \right) \cdot \varPsi _y\left( i,j \right)}


S3=(i,j)Ωθ(i,j)Ψx(i,j) S_3=\sum_{\left( i,j \right) \in \varOmega}{\theta \left( i,j \right) \cdot \varPsi _x\left( i,j \right)}


S5=(i,j)ΩΨy(i,j)Ψy(i,j) S_5=\sum_{\left( i,j \right) \in \varOmega}{\varPsi _y\left( i,j \right) \cdot \varPsi _y\left( i,j \right)}


S6=(i,j)Ωθ(i,j)Ψy(i,j) S_6=\sum_{\left( i,j \right) \in \varOmega}{\theta \left( i,j \right) \cdot \varPsi _y\left( i,j \right)}

其中:

θ(i,j)=(I(1)(i,j)>>nb)(I(0)(i,j)>>nb) \theta \left( i,j \right) =\left( I^{\left( 1 \right)}\left( i,j \right) >>n_b \right) -\left( I^{\left( 0 \right)}\left( i,j \right) >>n_b \right)


na=min(1,bitDepth11) n_a=\min \left( 1,bitDepth-11 \right)


nb=min(4,bitDepth8) n_b=\min \left( 4,bitDepth-8 \right)


Ω环绕44子块的66的窗口 \varOmega 是\text{环绕}4 * 4\text{子块的}6 * 6\text{的窗口}

相关的的代码如下:

void InterPrediction::xCalcBIOPar(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, int bitDepth)
{
  g_pelBufOP.calcBIOPar(srcY0Temp, srcY1Temp, gradX0, gradX1, gradY0, gradY1, dotProductTemp1, dotProductTemp2, dotProductTemp3, dotProductTemp5, dotProductTemp6, src0Stride, src1Stride, gradStride, widthG, heightG, bitDepth);
}

void calcBIOSumsCore(const Pel* srcY0Tmp, const Pel* srcY1Tmp, Pel* gradX0, Pel* gradX1, Pel* gradY0, Pel* gradY1, int xu, int yu, const int src0Stride, const int src1Stride, const int widthG, const int bitDepth, int* sumAbsGX, int* sumAbsGY, int* sumDIX, int* sumDIY, int* sumSignGY_GX)
{
  int shift4 = 4;
  int shift5 = 1;

  for (int y = 0; y < 6; y++)
  {
    for (int x = 0; x < 6; x++)
    {
      int tmpGX = (gradX0[x] + gradX1[x]) >> shift5;
      int tmpGY = (gradY0[x] + gradY1[x]) >> shift5;
      int tmpDI = (int)((srcY1Tmp[x] >> shift4) - (srcY0Tmp[x] >> shift4));
      *sumAbsGX += (tmpGX < 0 ? -tmpGX : tmpGX);
      *sumAbsGY += (tmpGY < 0 ? -tmpGY : tmpGY);
      *sumDIX += (tmpGX < 0 ? -tmpDI : (tmpGX == 0 ? 0 : tmpDI));
      *sumDIY += (tmpGY < 0 ? -tmpDI : (tmpGY == 0 ? 0 : tmpDI));
      *sumSignGY_GX += (tmpGY < 0 ? -tmpGX : (tmpGY == 0 ? 0 : tmpGX));

    }
    srcY1Tmp += src1Stride;
    srcY0Tmp += src0Stride;
    gradX0 += widthG;
    gradX1 += widthG;
    gradY0 += widthG;
    gradY1 += widthG;
  }
}


3.3 使用互相关和自相关的结果计算运动修正值(Vx,Vy)(V_x,V_y)

vx=S1>0?clip3(thBIO,thBIO,((S32nbna)>>log2S1)):0 v_x=S_1>0?clip3\left( -th'_{BIO},-th'_{BIO},-\left( \left( S_3\cdot 2^{n_b-n_a} \right) >>\lfloor \log _2S_1 \rfloor \right) \right) :0


vx=S5>0?clip3(thBIO,thBIO,((S62nbna((vxS2,m)<<nS2+vxS2,s)/2)>>log2S5)):0 v_x=S_5>0?clip3\left( -th'_{BIO},-th'_{BIO},-\left( \left( S_6\cdot 2^{n_b-n_a}-\left( \left( v_xS_{2,m} \right) <<n_{S_2}+v_xS_{2,s} \right) /2 \right) >>\lfloor \log _2S_5 \rfloor \right) \right) :0

其中:

S2,m=S2>>nS2 S_{2,m}=S_2>>n_{S_2}


S2,s=S2&(2nS21) S_{2,s}=S_2\&\left( 2^{n_{S_2}}-1 \right)


thBIO=2max(5,BD7) th'_{BIO}=2^{\max \left( 5,BD-7 \right)}


nS2=12 n_{S_2}=12


向下取整操 \lfloor \cdot \rfloor 是\text{向下取整操}作

3.4 计算修正之后的预测值

b(x,y)=rnd((vx(I(1)(x,y)xI(0)(x,y)x))/2)+rnd((vy(I(1)(x,y)yI(0)(x,y)y))/2) b\left( x,y \right) =rnd\left( \left( v_x\left( \frac{\partial I^{\left( 1 \right)}\left( x,y \right)}{\partial x}-\frac{\partial I^{\left( 0 \right)}\left( x,y \right)}{\partial x} \right) \right) /2 \right) +rnd\left( \left( v_y\left( \frac{\partial I^{\left( 1 \right)}\left( x,y \right)}{\partial y}-\frac{\partial I^{\left( 0 \right)}\left( x,y \right)}{\partial y} \right) \right) /2 \right)


predBDOF(x,y)=(I(0)(x,y)+I(1)(x,y)+b(x,y)+offset)>>shift pred_{BDOF}\left( x,y \right) =\left( I^{\left( 0 \right)}\left( x,y \right) +I^{\left( 1 \right)}\left( x,y \right) +b\left( x,y \right) +offset \right) >>shift

注意:上式在计算中乘数不超过15比特,且在计算BDOF的过程中中间参数最多不超过32比特


相关代码如下:

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++)
  { //计算b(x,y)
    for (int x = 0; x < width; x += 4)
    {
      b = tmpx * (gradX0[x] - gradX1[x]) + tmpy * (gradY0[x] - gradY1[x]);
      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]);
      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]);
      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]);
      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;
  }
}


3.5 边缘梯度计算方法

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

【十二】 H.266/VVC | 帧间预测技术 | 双向光流技术BDOF

注意

  • 当亮度CU的宽、高大于16时,需要将其划分为宽、高等于16的子块,在BDOF处理的过程中子块的边界被当做CU边界。BDOF能处理的最大块为16 * 16
  • 如果当前块启用了BCW,即BCW权重索引指示权重不相等,则将禁用双向光流。同样,如果对于当前块启用WP,即,对于两个参考图片中的任意一个,luma_weight_lx_flag标志为1,则也禁止使用双向光流。当CU用对称MVD模式和CIIP模式编码时,禁止使用双向光流

参考列表

【1】 JVET-N1002

【2】JVET-N0147

【3】JVET-N0152

【4】JVET-N0325

【5】博主Dillion2015.“VVC帧间预测(七)BDOF”.2020.01.12


更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!
【十二】 H.266/VVC | 帧间预测技术 | 双向光流技术BDOF

相关标签: VVC/H.266