【十二】 H.266/VVC | 帧间预测技术 | 双向光流技术BDOF
一、前言
双向光流技术是由JEM参考模型中的BIO技术发展而来,相较于BIO,BDOF计算的复杂度更低,尤其是乘法运算数量和乘数大小更小。
作用:用来修正CU的4 * 4子块CU的双向预测信号。
说明:BDOF基于光流的概念,它假设物体的运动是平滑的。对于每个4 * 4的子块,通过使前向预测L0和后向预测L1的预测值的差值最小来计算运动修正量,然后用计算出来的修正值来调整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 计算水平和垂直梯度
对前向和后向预测值分别计算水平和垂直梯度,梯度值直接通过相邻值相减得到,具体公式实现如下:
相关代码如下:
//计算水平和垂直梯度
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 计算梯度的自相关和互相关
计算梯度的自相关和互相关
其中:
相关的的代码如下:
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 使用互相关和自相关的结果计算运动修正值
其中:
3.4 计算修正之后的预测值
注意:上式在计算中乘数不超过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
更多关于视频编码知识和资源的分享,更精致的文章排版,欢迎关注博主微信公众号,一起交流、学习、进步!!!
上一篇: C#像素鸟(独自一鸟闯天下)
下一篇: OpenvSwitch key模块详解