环路滤波
一、环路滤波基础
定义:在视频编码过程中进行滤波,滤波后的图像用于后续编码。
目的:
- 提升编码图像的质量。
- 为后续编码图像提供高质量参考,获得更好的预测效果。
VVC中主要采取的技术有:
- 色度与亮度缩放(Luma Mapping Chroma Scaling, LMCS)、
- 去方块滤波(De-Blocking Filter, DBF)、
- 样点自适应补偿(Sample Adaptive Offset, SAO)、
- 自适应滤波(Adaptive Loop Filter, ALF)。
DBF用于降低方块效应、SAO用于改善振铃效应、ALF可以减少解码误差。
在编码环路中,一般是先去方块滤波、样点自适应补偿和自适应环路滤波的顺序。LMCS一般对编码前的图像进行预处理。
二、环路滤波方法
2.1 色度与亮度缩放(LMCS)
2.2.1 背景
包括两个部分:
- 基于自适应分段线性模型的亮度映射;应用在像素级,利用亮度值范围及广电转换特性来提高视频的编码效率。
- 基于亮度的色度残差缩放:应用在色度块级,通过补偿亮度信号映射对色度信号的影响。
解码端LMCS框架结构如下图所示:
2.2.2 VVC 中的亮度/色度缩放
基本思想
在指定的位深下更好地使用允许的亮度值范围。10bit视频亮度值范围为[64,940]。在编解码过程中,前向映射将范围[64,940]映射到[0,1023]中,再进行变换、量化等模块的处理。或者一个亮度范围较小的视频,没有充分利用允许的亮度值,因此LMCS就是将原始域亮度值映射到允许的亮度值范围。
基于分段线性模型的亮度映射
VVC中,前向映射函数FwdMap使用一个分段线性模型,反向映射函数InvMap为逆函数。
根据视频的位深将原始域的码值范围划分为16个相等的片段,每个片段的码字数量由OrgCW表示,
变量InputPivot[i]表示原始域内各片段的边界点。
有InputPivot[i] = i * OrgCW;
映射域内各片段的边界点表示MappedPivot[i],
MappedPivot[i+1]-MappedPivot[i]的值就是映射域中第i个片段的亮度值个数,表示为SignalledCW[i]。
色度缩放
前向缩放将原始域色度值转换到映射域,有色度缩放以TU为单位,同一个TU使用相同的缩放因子,也使用分段线性模型,同一个片段色度缩放因子相同。色度缩放偏移量deltaCRS由自适应参数集LMCS_APS标识。
LMCS的实现
介绍的是VTM中使用的LMCS模型参数的构建方法,对于SDR\HDR-PQ\HDR-HLG视频的特性不同,模型参数不同。对于SDR和HDR-HLG视频,基于局部亮度方差,针对PSNR指标进行优化。对于HDR-PQ视频,针对加权PSNR进行优化,基本思想为空间平滑区域分配相比复杂区域更多的码字。
SDR\HDR-HLG视频
1、统计分析视频内容,在原始域划分片段,分配初始码字
2、根据图像内容调整片段的码字数量
3、如果分配的码字总数大于允许的最大码字数,从第一个片段开始,每个片段减少相同的数量,知道妈祖条件,得到最终每个片段分配的码字数量SignalledCW[i].
4、根据亮度样本映射前后的相对值及平均局部方差,确定LMCS片类型、高码率自适应和色度调整自适应参数。
HDR-PQ视频
1、计算映射模型曲线的斜率
2、积分映射模型曲线的斜率
3、归一化
4、计算每个片段的码字数量。
LMCS的相关语法元素
SPS层
变量名 | 含义 |
---|---|
sps_lmcs_enabled_flag | 为1的时候标识使用LMCS |
PH层
变量名 | 含义 |
---|---|
ph_lmcs_enabled_flag | 当前图像是否使用LMCS |
ph_lmcs_aps_id | 当前按图片中的slice所应用的LMCS APS的aps_adaptation_parameter_set_id |
ph_chroma_residual_scale_flag | 当前图片是否使用色度残差缩放 |
SH层
变量名 | 含义 |
---|---|
sh_lmcs_used_flag | 当前slice是否使用LMCS |
APS层
变量名 | 含义 |
---|---|
lmcs_min_bin_idx | LMCS过程中使用的最小bin索引 |
lmcs_delta_max_bin_idx | 最大bin索引与15之间的差值 |
lmcs_delta_cw_prec_minus1 | 加1用于kmcs_delta_abs_cw[i]的比特数 |
lmcs_delta_abs_cw[i] | 第i个区间的码字绝对增量值 |
lmcs_delta_sign_cw_flag[i] | 标识变量LmcsDeltaCW[i]的符号 |
lmcs_delta_abs_crs | 表示变量LmcsDeltaCrs的绝对码字值 |
lmcs_delta_sign_crs_flag | 表示变量LmcsDeltaCrs的符号 |
2.2 去方块滤波(DBF)—减轻块效应
2.2.1 背景
目前主流的视频编码标准都是基于分块的混合编码机制,其处理过程是针对每个块单独进行处理的,因此由于编码模式的差异以及量化误差的原因,会导致相邻块重建像素不连续的现象。对于一个两侧强相关性的块边界,当图像较平滑时,就会在重建图像中出现方块效应,如下图所示。
方块效应产生的原因主要包括以下两个方面:
- 由变换、量化的误差引起的。在视频编码中,量化是一个有损压缩过程,因此在反量化、反变换之后的重建图像会出现误差,由于不同块之间的处理不一致,而且边界处的误差格外大一些,会造成图像在边界上的视觉不连续。
- 来自帧间的运动补偿过程。对于相邻块,其运动补偿的预测数据可能来自同一帧的不同位置、不同帧不同的位置,因此会产生误差,引起图像在复制的边界上的不连续现象。
为了消除或者减轻块效应,通常使用去块滤波(DBF)修正图像边界处的像素值。
2.2.2 VVC 中的去块滤波
VVC中与HEVC的去块滤波过程类似。在VVC中,对CU边界、变换子块边界和预测子块边界进行去块滤波处理。预测子块边界包括由SbTMVP和仿射模式引入的预测单元PU边界,变换子块边界包括由SBT和ISP模式引入的变换单元边界,以及由于大CUs的隐式划分而引起的变换。对CU边界和变换子块边界采用4x4滤波处理网格,对预测子块边界采用8x8滤波处理网格。对于SBT和ISP子块,类似于HEVC去块滤波器中TU的逻辑,当任一变换子块的边缘有非零系数时,在TU边界上应用去块滤波器。对于SbTMVP和仿射预测子块,类似于HEVC中PU中的逻辑,在8x8网格上应用去块滤波器,同时考虑相邻预测子块的运动矢量和参考图片之间的差异。变换块边界最多可以用变换边界一侧的5个样本去块,变换边界也是编码块的一部分,其中SbTMVP或affine用于实现并行友好去块。内部预测子块边界来自变换块边界的4个样本在每侧最多滤波1个样本,内部预测子块边界远离变换块边界的8个样本在边界的每侧最多滤波2个样本,并且其他内部预测子块边界在边界的每侧最多滤波3个样本。
针对亮度滤波,VVC对“大块”引入了双线性滤波器(更强滤波),“大块”即边界长度大于等于32的块。亮度分量的滤波器分类如下:
- 更强滤波,最多可达每侧边界的7个像素
- 强滤波,对边界像素每边修改3个像素
- 弱滤波,最多对边界两边修改2个像素
由于VVC的二叉树、三叉树划分引入矩形块,所以VVC中亮度使用4x4大小的滤波处理单元。
针对色度滤波,主要分为以下两种滤波器:
- 强滤波,对边界每边修改3个像素
- 弱滤波,对边界每侧修改1个像素
- 色度滤波仅使用8x8的滤波处理单元。
以8x8滤波处理单元为例,每个处理块横跨4个8x8的呈“+”字形的边界, 如下图所示,
边界两侧块分别用P(垂直边界左侧块或者水平边界上侧块)和Q(垂直边界右侧块和水平边界下侧块)表示。
对于需要滤波的边界,按照先亮度分量后色度分量的顺序;
对于同一分量的块,按照先垂直边界后水平边界的顺序。
VVC的去块滤波过程和HEVC的类似,主要包含三个步骤
- 根据边界两侧的编码模式和编码参数确定边界强度
- 根据边界两侧像素值,确定滤波强度(包括滤波开关决策和滤波强度决策)
- 进行滤波处理过程
2.3 样点自适应补偿(SAO)—改善振铃效应
2.3.1 背景
2.3.2 VVC 中的样点自适应补偿
2.4 自适应滤波(ALF)—减少解码误差
2.4.1 背景
自适应环路滤波(ALF)并不是在 H.266/VVC 标准制定过程中才被提出来的技术,实际上其早在 H.265/HEVC 标准制定时就基本确定了现有形式的雏形,只是由于当时硬件算力的限制未能加入到 HEVC 标准中。详细可阅读以下的论文。随着硬件算力的提升与一些优化方法的提出,以及人们对更高效率的编解码算法的期望,ALF 自然就成了 VVC 标准中不可或缺的部分。由于 ALF 是一个十分有效的手段,也就吸引力不少人去研究,很多率失真改进方法不断被提出,所以基本每一次会议之后都能看到一些不同的地方。这里我主要就其滤波的原理进行介绍,这些目前在网上的资料是比较少的,也可以让大家能够更好地理解 VTM 代码里的相关实现。
ALF 模块处于去块效应滤波(Deblocking Filtering, DF)和样点自适应补偿(Sample Adaptive Offset, SAO)之后。相比于 DF 和 SAO,ALF 更加贴近于我们对图像滤波的一般理解,也就是说其可以表示为图像与卷积核之间的卷积运算。而至于所使用卷积核,当然不是什么高斯模糊、边缘检测之类的,视频编解码算法里降低码率和误差才是王道。靠滤波来直接降低码率自然是不行的,那是熵编码干的活,那什么滤波可以降低误差呢?没错,就是维纳滤波(Wiener Filtering, WF)。
维纳滤波是一种基于最小均方误差准则、对平稳过程的最优估计器,其输出与期望输出之间的均方误差为最小。然而,要注意的是,维纳滤波并不是一种现成的滤波器,其只是确立了一种准则,具体还是要自己设计的,在信号与系统里也就是找到相应的系统冲激响应,在离散图像里也就是要找到相应的卷积核。因此,下面的内容将就该卷积核的求导进行详细的介绍。
2.4.2 VVC 中的自适应滤波
1. 滤波器的形状
虽然维纳滤波并没有限制所用的滤波器形式,但我们还是要综合考虑其实现的难度以及所能带来的增益。
在图像处理中,最方便、直观的滤波方式就是采用一个矩形的卷积核,ALF 也遵循这种习惯。不过,很显然,卷积核的尺寸越大,所获得的增益一般来说会越大,但其所带来的计算复杂度也就越高,而且这种增益和复杂度之间的关系并不是线性的。
另外,维纳滤波是和原始数据相关的,而解码端是不可能接触到原始数据的,那就意味着我们必须要把解码端所求得的滤波系数编到码流中进行传输,所以我们也不能把卷积核弄得太大。
考虑以上因素,H.266 定义了两种形状的滤波器,其中对亮度分量使用 7x7 的滤波器,而对色度分量采用 5x5 的滤波器。
为了减少传输滤波器系数的码字以及降低滤波的计算复杂度,ALF 使用了菱形且中心对称的滤波系数矩阵,其形状如图 1 所示,也就是只有菱形内的像素会参与滤波,而中心对称的像素使用相同的滤波系数,其中滤波器中心的位置即是当前滤波的像素位置。
对于亮度分量,H.266 定义了最多 25 组滤波系数,像素所属分组基于重建后的图像以 4x4 小块为单位根据该块以及周围像素的梯度信息进行推导,该块内所有像素均属于同一组。
对于色度分量,Cb,Cr 各自只定义一组系数。三个分量独立进行率失真优化与系数推导以及最终的滤波。
具体亮度分量像素的分类在标准文档中有详细介绍,这里不多说。
enum AlfFilterType
{
ALF_FILTER_5,
ALF_FILTER_7,
ALF_NUM_OF_FILTER_TYPES
};
2. 维纳滤波
将上式写成矩阵形式,可得
为了方便,我们令
那么上面的线性方程组可写为
其中
3. 块分类
对于亮度分量需要为每个4x4的子块分类,共25个类别C。
类别C由4x4块的方向D和活动性A决定:
C = 5D + A
其中D和A分别表示当前块的Direction和Activity;
计算之前需要先用1-D拉普拉斯算子计算当前块的水平、垂直和两个对角方向的gradient如下:
为了计算D和A,需要计算子块的水平、垂直、两个对角线方向的梯度。
梯度计算使用一维拉普拉斯方法实现:
为了减少计算复杂度,在计算梯度前,进行一维拉普拉斯计算下采样,如下图:
因此,计算4x4块类别C的步骤为:
- 先进行下采样,在计算水平gh和垂直gv方向的梯度
- 得到梯度后D和A的计算方法如下:
- 代入类别C=5D + A,计算出亮度4x4块的类别
- 对于色度分量不需要对其子块进行分类操作,它只有一个滤波器。
4. 滤波器系数和门限值几何变换
由上一步可以得到滤波器,但是在滤波操作前需要对滤波器系数和相应门限值进行几何变换,包括旋转、对角和垂直翻转。变换类型由上面计算的块的梯度决定。
对滤波器进行几何变换效果等价于对滤波区域进行相应几何变换,这么做的目的是使不同块方向对齐。
VTM 代码分析如图所示:
if( filtType == ALF_FILTER_7 )
{
if( transposeIdx == 1 )
{//!<对角线变换
filterCoeff = { coef[9], coef[4], coef[10], coef[8], coef[1], coef[5], coef[11],
coef[7], coef[3], coef[0], coef[2], coef[6], coef[12] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[9], clip[4], clip[10], clip[8], clip[1], clip[5], clip[11],
clip[7], clip[3], clip[0], clip[2], clip[6], clip[12] };
#endif
}
else if( transposeIdx == 2 )
{//!<垂直翻转
filterCoeff = { coef[0], coef[3], coef[2], coef[1], coef[8], coef[7], coef[6],
coef[5], coef[4], coef[9], coef[10], coef[11], coef[12] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[0], clip[3], clip[2], clip[1], clip[8], clip[7], clip[6],
clip[5], clip[4], clip[9], clip[10], clip[11], clip[12] };
#endif
}
else if( transposeIdx == 3 )
{//!<旋转变换
filterCoeff = { coef[9], coef[8], coef[10], coef[4], coef[3], coef[7], coef[11],
coef[5], coef[1], coef[0], coef[2], coef[6], coef[12] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[9], clip[8], clip[10], clip[4], clip[3], clip[7], clip[11],
clip[5], clip[1], clip[0], clip[2], clip[6], clip[12] };
#endif
}
else
{//!<不变换
filterCoeff = { coef[0], coef[1], coef[2], coef[3], coef[4], coef[5], coef[6],
coef[7], coef[8], coef[9], coef[10], coef[11], coef[12] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[0], clip[1], clip[2], clip[3], clip[4], clip[5], clip[6],
clip[7], clip[8], clip[9], clip[10], clip[11], clip[12] };
#endif
}
}
else
{
if( transposeIdx == 1 )
{//!<对角线变换
filterCoeff = { coef[4], coef[1], coef[5], coef[3], coef[0], coef[2], coef[6] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[4], clip[1], clip[5], clip[3], clip[0], clip[2], clip[6] };
#endif
}
else if( transposeIdx == 2 )
{//!<垂直翻转
filterCoeff = { coef[0], coef[3], coef[2], coef[1], coef[4], coef[5], coef[6] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[0], clip[3], clip[2], clip[1], clip[4], clip[5], clip[6] };
#endif
}
else if( transposeIdx == 3 )
{//!<旋转变换
filterCoeff = { coef[4], coef[3], coef[5], coef[1], coef[0], coef[2], coef[6] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[4], clip[3], clip[5], clip[1], clip[0], clip[2], clip[6] };
#endif
}
else
{//!<不变换
filterCoeff = { coef[0], coef[1], coef[2], coef[3], coef[4], coef[5], coef[6] };
#if JVET_N0242_NON_LINEAR_ALF
filterClipp = { clip[0], clip[1], clip[2], clip[3], clip[4], clip[5], clip[6] };
#endif
}
采用的几何变换方式由上面计算的四个方向的梯度决定:
int hv1, hv0, d1, d0, hvd1, hvd0;
if( sumV > sumH )
{
hv1 = sumV;
hv0 = sumH;
dirTempHV = 1;
}
else
{
hv1 = sumH;
hv0 = sumV;
dirTempHV = 3;
}
if( sumD0 > sumD1 )
{
d1 = sumD0;
d0 = sumD1;
dirTempD = 0;
}
else
{
d1 = sumD1;
d0 = sumD0;
dirTempD = 2;
}
if( d1*hv0 > hv1*d0 )
{
hvd1 = d1;
hvd0 = d0;
mainDirection = dirTempD;
secondaryDirection = dirTempHV;
}
else
{
hvd1 = hv1;
hvd0 = hv0;
mainDirection = dirTempHV;
secondaryDirection = dirTempD;
}
int directionStrength = 0;
if( hvd1 > 2 * hvd0 )
{
directionStrength = 1;
}
if( hvd1 * 2 > 9 * hvd0 )
{
directionStrength = 2;
}
if( directionStrength )
{
classIdx += ( ( ( mainDirection & 0x1 ) << 1 ) + directionStrength ) * 5;
}
//!<几何变换索引计算
static const int transposeTable[8] = { 0, 1, 0, 2, 2, 3, 1, 3 };
int transposeIdx = transposeTable[mainDirection * 2 + ( secondaryDirection >> 1 )];
5. 滤波器参数传输
在VTM5中,ALF滤波器参数在Adaptation Parameter Set (APS)中。
在一个APS中,有至多25组亮度滤波器参数和门限值,及至多1组色度滤波器参数和门限值。为了减少比特开销,不同类别的滤波器参数可以merge。
当前slice使用的APS索引在slice header中。
APS中可以解码出门限值索引,通过门限值索引可以从亮度门限值列表和色度门限值列表中指定所需的门限值。门限值列表由位深决定:
滤波过程可以在CTB级进行控制,可以传输一个标志位表示是否需要对一个亮度CTB进行ALF滤波。一个亮度CTB使用的滤波器可以从16个固定滤波器和APS提供的滤波器中选择。有一个滤波器索引表示最终使用的滤波器。16个固定滤波器是预定义好的,硬编码进编码器和解码器了。
滤波器参数被量化成均值为128。为了减少乘法的复杂性,需要对码流进行一致性处理以使非中心位置的参数在[-128,127]间。中心位置的参数不需要传输,默认等于128。