双向光流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