《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!
“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!
链接地址:H.264/AVC视频编解码技术详解
GitHub代码地址:点击这里
一、去块滤波的基本概念
1.1 去块滤波的作用
去块滤波器(Deblocking Filter)是视频编解码器中的重要组成部分,其核心作用在于消除编码过程中产生的图像块效应。图像中的块效应主要因为以宏块为基本单元的编码结构而产生。在编码中,每个宏块的子块都会按照既定分割方式进行预测、变换和量化编码,在这个过程中可能导致块效应的因素主要有以下几种:
- 由于变换和量化编码的运算精度误差导致边界出现不连续;
- 由于码率设置较低,量化强度较大,或者相邻宏块的量化参数不一致导致重建图像的细节部分产生差异;
- 由于运动补偿时的参考块位置与当前块位置关系不一致导致重建像素的内容实际上缺乏相关性。
为了解决该问题,在H.264中定义了名为"Deblocking Filter"的像素域的图像滤波算法,以降低块与宏块边界的像素不一致性,提升整体视觉主观体验。
1.2 去块滤波器的定义
在H.264的标准文档中,去块滤波器定义在8.7节中。在H.264的以下profile中,去块滤波是必要的组成部分:
- Baseline, Constrained Baseline, Main, Extended, High, High 10, High 4:2:2, High 4:4:4 Predictive;
在以下profile中,去块滤波器推荐而不强制使用:
- High 10 Intra, High 4:2:2 Intra, High 4:4:4 Intra, CAVLC 4:4:4 iNTRA;
在H.264帧解码的过程中,去块滤波器在该帧所有宏块的解码像素数据重建完成之后进行。在执行中,该过程按照宏块地址的顺序,对所有的NxN分割模式的宏块的像素块分割边界分别进行,其中不包括整个图像的边界以及被标志值disable_deblocking_filter_idc所禁用的边界。
二、 去块滤波的执行过程
去块滤波对于每一个宏块的亮度和色度分量分别进行。对于宏块的每一个分量,首先进行垂直方向的滤波,然后进行水平方向的滤波。垂直方向的滤波从左向右进行,水平方向的滤波从上向下进行。在水平和垂直两方向上,待滤波的块边沿分为两类:半宏块和1/4宏块的边沿。标准文档中的插图表示如下:
在上图中,实线表示半宏块的边沿,虚线表示1/4宏块的边沿。对于亮度/色度以及不同的参数设置,去块滤波操作的边沿不同。对于亮度分量,根据transform_size_8x8_flag的值判断:
- 如果transform_size_8x8_flag为0,即采用4×4尺寸变换,对实线和虚线的边沿进行滤波;
- 如果transform_size_8x8_flag为1,即采用8×8尺寸变换,只对实线的块边沿进行滤波;
对于色度分量,只考虑4:2:0格式,只对半宏块边沿即实线部分进行滤波。
宏块去块滤波的过程主要分为如下几个步骤:
- 确定当前宏块的邻域有效性;
获取几个关键的标识参数:
- fieldMbInFrameFlag:只考虑帧编码,该值应为0;
- filterInternalEdgesFlag:如果当前slice中的disable_deblocking_filter_idc的值设为0,则该flag为1;反之为0;
- filterLeftMbEdgeFlag:如果当前slice中的disable_deblocking_filter_idc设为1,或disable_deblocking_filter_idc设为2而且该宏块不可得,或宏块为图像的左侧边沿宏块,则该flag设为0;反之设为1;
- filterTopMbEdgeFlag:如果当前slice中的disable_deblocking_filter_idc设为1,或disable_deblocking_filter_idc设为2而且该宏块不可得,或宏块为图像的上方边沿宏块,则该flag设为0;反之设为1;
- 根据上述关键标识参数执行滤波过程。
2.1 左宏块边沿滤波
若标识位filterLeftMbEdgeFlag设为1,根据协议文档中8.7.1节的方法执行宏块亮度分量左侧边沿的滤波。其中chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (0, k)的k取[0,15]。
2.2 上宏块边沿滤波
若标识位filterTopMbEdgeFlag的值为1,宏块亮度分量的上水平边沿进行滤波过程。由于当前设置下MbaffFrameFlag始终为0,因此根据协议文档中8.7.1节的方法执行滤波。相关参数为chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (k,0)的k取[0,15]。
2.3 内部块边沿滤波
当标志位filterInternalEdgesFlag为1时,执行宏块内部块边沿的滤波。内部块边沿的滤波分为水平和垂直两种。
2.3.1 内部垂直块边沿滤波
内部垂直边沿滤波的步骤为:
- 如果transform_size_8x8_flag为0,根据协议文档中8.7.1节的方法执行滤波。相关参数为chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (4,k)的k取[0,15]。该步骤即为第一和第二列块相邻边沿的滤波。
- 根据协议文档中8.7.1节的方法执行滤波,相关参数为chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (8,k)的k取[0,15]。该步骤即为第二和第三列块相邻边沿的滤波。
- 如果transform_size_8x8_flag为0,根据协议文档中8.7.1节的方法执行滤波。相关参数为chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (12,k)的k取[0,15]。该步骤即为第三和第四列块相邻边沿的滤波。
2.3.2 内部水平块边沿滤波
内部水平边沿滤波的步骤为:
- 如果transform_size_8x8_flag为0,根据协议文档中8.7.1节的方法执行滤波。相关参数为chromaEdgeFlag为0,verticalEdgeFlag为0,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (k,4)的k取[0,15]。该步骤即为第一和第二行块相邻边沿的滤波。
- 根据协议文档中8.7.1节的方法执行滤波,相关参数为chromaEdgeFlag为0,verticalEdgeFlag为0,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (k,8)的k取[0,15]。该步骤即为第二和第三行块相邻边沿的滤波。
- 如果transform_size_8x8_flag为0,根据协议文档中8.7.1节的方法执行滤波。相关参数为chromaEdgeFlag为0,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (k,12)的k取[0,15]。该步骤即为第三和第四行块相邻边沿的滤波。
2.4 色度分量的滤波
在图像为彩色图像的情况下,色度部分同样需要进行滤波操作。对于色度分量的滤波,其思路类似于亮度分量。
2.4.1 色度左宏块边沿滤波
如果filterLeftMbEdgeFlag为1时,根据协议文档中8.7.1节的方法执行宏块色度分量左侧边沿的滤波。chromaEdgeFlag为1,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (0, k)的k取[0,7]。
2.4.2 色度上宏块边沿滤波
如果filterTopMbEdgeFlag为1,根据协议文档中8.7.1节的方法执行宏块色度分量上方边沿的滤波。。chromaEdgeFlag为1,verticalEdgeFlag为0,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = ( k,0)的k取[0,7]。
2.4.3 色度分量内部块边沿滤波
色度分量的内部块滤波也分为水平和垂直两个方向,且只有在transform_size_8x8_flag为0时执行。
对于水平方向,根据协议文档中8.7.1节的方法执行滤波,相关参数为chromaEdgeFlag为1,verticalEdgeFlag为0,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (k,4)的k取[0,8]。
对于垂直方向,根据协议文档中8.7.1节的方法执行滤波,相关参数为chromaEdgeFlag为1,verticalEdgeFlag为1,fieldModeInFrameFilteringFlag等于fieldMbInFrameFlag(帧编码模式中始终0),(xEk, yEk) = (4,k)的k取[0,8]。
三、块/宏块边沿滤波方法
滤波每一个块边沿的方法定义于标准协议文档的8.7.1节。所需要的步骤包括:
确定相关参数:
- chromaEdgeFlag:表明当前数据属于亮度或色度;
- iCbCr:色度分量索引;
- verticalEdgeFlag:水平、垂直方向标志位;
- fieldModeInFrameFilteringFlag:隔行模式标志位;
- nE:一个边沿包含的像素数量,亮度为16,色度为8;
- 获取原重建块的待滤波像素;
- 根据原像素进行滤波;
- 修改重建像素值。
## 3.1 获取原重建块的待滤波像素
对一个块边沿上的某一个像素位置进行滤波时,需要获取该像素位置相邻的8个原像素值作为参考。标准文档中的插图如下:
上图中的p0和q0代表块边界某个位置左右的相邻像素,根据滤波方向的不同,可能是左右或上下相邻的像素。以水平块边沿滤波为例,采用边界垂直方向的8个相邻像素,如下图所示:
上图中,红色像素P0和Q0表示边界两侧的相邻像素,其余蓝色像素为滤波所需要的其他相邻像素。
## 3.2 边界像素滤波
在获取到边界的参考像素后,下一步执行的操作是参考8个像素值进行滤波。滤波过程的具体方法定义在标准的8.7.2节。实现一个边界的滤波需要三大步骤:
- 计算边界滤波强度;
- 判断滤波条件;
- 滤波边界像素;
3.2.1 计算边界滤波强度
边界滤波强度是边界像素滤波时的一个重要的参数,决定了像素点的滤波采取怎样的算法。实现方法定义在标准的8.7.2.1节。边界滤波强度可能的取值有0、1、2、3、4共5个值。其中:
- 0:表示该边界不需要滤波;
- 1、2:主要用于帧间编码图像的滤波;
- 3、4:主要用于帧内编码图像的滤波;
本节中主要以帧内编码为例说明边界滤波强度的计算方法,即滤波强度取3、4的条件。
- 当滤波边界为宏块边沿,并且边界相邻像素p0和q0都属于帧编码宏块,并且任意一个像素的所属宏块为帧内编码或所属的帧类型为SI或SP时,边界滤波强度为4;
- 当滤波边界为子块边界,并且边界相邻像素p0和q0都属于帧编码宏块,并且任意一个像素的所属宏块为帧内编码或所属的帧类型为SI或SP时,边界滤波强度为3;
- 在帧内编码模式中,同时作为宏块边沿和图像边沿的像素,滤波强度为0,不进行滤波。
3.2.2 判断滤波条件
判断滤波条件的方法定义在8.7.2.2节,所需数据包括边界两侧的4个像素值p0/p1/q0/q1,颜色空间分量标志位chromaEdgeFlag,边界滤波强度bS,所属宏块的量化参数QPp/QPq,以及在视频头部读取到的两个语法元素filterOffsetA/filterOffsetB。
首先计算边界相邻像素所属宏块量化参数QP的平均值:
qPav =(qPp +qPq +1)>>1;
计算查表所需要的两个索引值:
indexA = Clip3(0, 51, qPav + filterOffsetA);
indexB = Clip3(0, 51, qPav + filterOffsetB);
根据计算得到的indexA和indexB,通过在表8-16中查找对应的alpha和beta值:
根据上述计算得到的信息,可以获得滤波判决条件:
filterSamplesFlag=(bS!=0 && Abs(p0 −q0 )<α && Abs(p1 −p0 )<β && Abs(q1 −q0 )<β);
3.2.3 滤波边界像素
针对边界滤波强度不同的像素点,采用的方法也相应不同。滤波过程修改的像素值包括边界两边的相邻像素以及分别向两个方向扩展两个的像素值,即根据边界左右对称的6个相邻像素。
对于滤波强度为4的像素点(即非作为图像边缘的宏块边界),采用如下方法进行滤波:
如果滤波亮度分量,且满足ap <β && Abs(p0 −q0 )<((α>>2)+2)时,边界左侧或上方的三个滤波后像素的计算方法为:
- p′0 =(p2 +2p1 +2p0 +2*q0 +q1 +4)>>3
- p′1 =(p2 +p1 +p0 +q0 +2)>>2
- p′2 =(2p3 +3p2 +p1 +p0 +q0 +4)>>3
右侧或下方的三个滤波后像素的计算方法为:
- q′0 =(p1 +2p0 +2q0 +2*q1 +q2 +4)>>3
- q′1 =(p0 +q0 +q1 +q2 +2)>>2
- q′2 =(2q3 +3q2 +q1 +q0 +p0 +4)>>3
如果滤波色度分量,或者不满足ap <β && Abs(p0 −q0 )<((α>>2)+2)时,边界左侧或上方的三个滤波后像素的计算方法为:
- p′0 =(2*p1 +p0 +q1 +2)>>2
- p′1 = p1
- p′2 = p2
右侧或下方的三个滤波后像素的计算方法为:
- q′0 =(2*q1 +q0 +p1 +2)>>2
- q′1 = q1
- q′2 = q2
对于滤波强度为3的像素点(即内部块边沿),采用的滤波方法略复杂:
首先依据上面获得的indexA和边界滤波强度查表获得C0值,该值由标准文档中的表8-17获得。
获取参考像素的二级差分值:
ap =Abs(p2 −p0 )
aq =Abs(q2 −q0 )
计算阈值变量tc:
亮度:tC =tC0 +((ap <β)?1:0)+((aq <β)?1:0)
色度:tC =tC0 +1
计算边界相邻像素:
Δ =Clip3(−tC,tC,((((q0 −p0 )<<2)+(p1 −q1 )+4)>>3)) p′0 = Clip1( p0 + Δ )
q′0 = Clip1( q0 − Δ )
对亮度分量,p1/q1的推导方式为:
p′1 =p1 +Clip3(−tC0,tC0,(p2 +((p0 +q0 +1)>>1)−(p1 <<1)) >> 1)
q′1 =q1 +Clip3(−tC0,tC0,(q2 +((p0 +q0 +1)>>1)−(q1 <<1)) >> 1)
其它,p2/q2以及色度分量的p1/q1/p2/q2像素维持原值不变。