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

一、前言

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

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

说明:BDOF基于光流的概念,它假设物体的运动是平滑的。对于每个4 * 4的子块,通过使前向预测L0和后向预测L1的预测值的差值最小来计算运动修正量 ( V x , V y ) (Vx,V_y) (Vx,Vy),然后用计算出来的修正值来调整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 ) )    > >    s h i f t 1 −    ( I ( k ) ( i − 1 , j ) )    > >    s h i f t 1 ) \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) xI(k)(i,j)=((I(k)(i+1,j))>>shift1(I(k)(i1,j))>>shift1)
∂ I ( k ) ∂ y ( i , j )    =    ( ( I ( k ) ( i , j + 1 ) )    > >    s h i f t 1 −    ( I ( k ) ( i , j − 1 ) )    > >    s h i f t 1 ) \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) yI(k)(i,j)=((I(k)(i,j+1))>>shift1(I(k)(i,j1))>>shift1)
I ( k ) ( i , j ) 是 L k ( 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{处的预测值} I(k)(i,j)Lk(k=0,1)中坐标(i,j)处的预测值
s h i f t 1 = max ⁡ ( 6 , b i t D e p t h − 6 ) , b i t D e p t h 是 亮度分量的比特深度 shift1=\max \left( 6,bitDepth-6 \right) ,bitDepth是\text{亮度分量的比特深度} shift1=max(6,bitDepth6),bitDepth亮度分量的比特深度

相关代码如下:

//计算水平和垂直梯度
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 计算梯度的自相关和互相关

计算梯度的自相关和互相关 S 1 、 S 2 、 S 3 、 S 4 、 S 5 、 S 6 S_1、S_2、S_3、S_4、S_5、S_6 S1S2S3S4S5S6

S 1 = ∑ ( 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)} S1=(i,j)Ωψx(i,j)Ψx(i,j)

S 2 = ∑ ( 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)} S2=(i,j)Ωψx(i,j)Ψy(i,j)

S 3 = ∑ ( 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)} S3=(i,j)Ωθ(i,j)Ψx(i,j)

S 5 = ∑ ( 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)} S5=(i,j)ΩΨy(i,j)Ψy(i,j)

S 6 = ∑ ( 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)} S6=(i,j)Ωθ(i,j)Ψy(i,j)

其中:

θ ( i , j ) = ( I ( 1 ) ( i , j ) > > n b ) − ( I ( 0 ) ( i , j ) > > n b ) \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) θ(i,j)=(I(1)(i,j)>>nb)(I(0)(i,j)>>nb)

n a = min ⁡ ( 1 , b i t D e p t h − 11 ) n_a=\min \left( 1,bitDepth-11 \right) na=min(1,bitDepth11)

n b = min ⁡ ( 4 , b i t D e p t h − 8 ) n_b=\min \left( 4,bitDepth-8 \right) nb=min(4,bitDepth8)

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

相关的的代码如下:

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 使用互相关和自相关的结果计算运动修正值 ( V x , V y ) (V_x,V_y) (Vx,Vy)

v x = S 1 > 0 ? c l i p 3 ( − t h B I O ′ , − t h B I O ′ , − ( ( S 3 ⋅ 2 n b − n a ) > > ⌊ log ⁡ 2 S 1 ⌋ ) ) : 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=S1>0?clip3(thBIO,thBIO,((S32nbna)>>log2S1)):0

v x = S 5 > 0 ? c l i p 3 ( − t h B I O ′ , − t h B I O ′ , − ( ( S 6 ⋅ 2 n b − n a − ( ( v x S 2 , m ) < < n S 2 + v x S 2 , s ) / 2 ) > > ⌊ log ⁡ 2 S 5 ⌋ ) ) : 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 vx=S5>0?clip3(thBIO,thBIO,((S62nbna((vxS2,m)<<nS2+vxS2,s)/2)>>log2S5)):0

其中:

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

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

t h B I O ′ = 2 max ⁡ ( 5 , B D − 7 ) th'_{BIO}=2^{\max \left( 5,BD-7 \right)} thBIO=2max(5,BD7)

n S 2 = 12 n_{S_2}=12 nS2=12

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

3.4 计算修正之后的预测值

b ( x , y ) = r n d ( ( v x ( ∂ I ( 1 ) ( x , y ) ∂ x − ∂ I ( 0 ) ( x , y ) ∂ x ) ) / 2 ) + r n d ( ( v y ( ∂ I ( 1 ) ( x , y ) ∂ y − ∂ I ( 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) b(x,y)=rnd((vx(xI(1)(x,y)xI(0)(x,y)))/2)+rnd((vy(yI(1)(x,y)yI(0)(x,y)))/2)

p r e d B D O F ( x , y ) = ( I ( 0 ) ( x , y ) + I ( 1 ) ( x , y ) + b ( x , y ) + o f f s e t ) > > s h i f t 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 predBDOF(x,y)=(I(0)(x,y)+I(1)(x,y)+b(x,y)+offset)>>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边界之外的任何样本和梯度值,则从其最近的像素中进行填充(即重复)

在这里插入图片描述

注意

  • 当亮度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


更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值