方块效应是指图像由于失真而造成的编码边界不连续而在视觉上产生“一块一块”的效果。方块效应的产生是由于现在的编码技术都是基于块的编码,不同块的预测变换量化等过程相互独立从而引入的量化误差大小及分布也相互独立。对块边界进行平滑滤波可以有效降低或去除方块效应,这就是去方块滤波(Deblocking Filter)。
去方块滤波处理流程如下:
H.265/HEVC中去方块滤波
H.265中去方块滤波有如下特点:
-
无论是亮度还是色度分量,只按照8x8的块边界进行处理,且必须是TU或PU的边界,图像边界不处理。对于色度分量仅当边界两侧至少有一个块采用帧内预测时该边界才需要被滤波,这使滤波次数大大降低。
-
滤波时,待处理边界两边最多各修正3个像素值,这使得8x8块边界空间独立可以并行处理。
-
可以先处理整幅图像的垂直边界再处理水平边界,而非H.264中垂直边界和水平边界穿插处理。
滤波决策
虽然去方块滤波按照8x8块边界处理,而实际上是将8x8的块分成两部分独立处理,垂直边界以8x4为基本单位,水平边界以4x8为基本单位。如下图所示:
滤波决策是对所有PU和TU边界中8x8的块边界确定其滤波强度和滤波参数。只有平坦区域的不连续边界才需要滤波。下图是滤波决策过程:
对于亮度分量的块边界需要上面3个步骤确定滤波强度和滤波参数,而对于色度分量的块边界滤波决策环节只需要为其确定边界强度,且其边界强度值直接取自对应的亮度边界。
(1)获取边界强度
获取边界强度(Boundary Strength,BS)就是根据边界块的编码参数初步判断块边界是否需要滤波及滤波参数。BS的取值为0、1或2。对于亮度分量当BS为0时表示该边界不需要滤波,不再进行后续处理。当亮度分量的BS为1或2时才会进行后续处理。对于色度分量当BS为0或1时表示该边界不需要滤波,不再进行后续处理,只有BS为2时才需要滤波(也不需要进行后续的滤波开关决策和滤波强度选择)。
BS的获取过程如上图所示。P和Q分别为边界两侧的块,如前面图中所示。
边界强度计算在下面函数中实现。
Void xGetBoundaryStrengthSingle ( TComDataCU* pCtu, DeblockEdgeDir edgeDir, UInt uiPartIdx );
(2)滤波开关决策
由于人眼的空间掩蔽效应,图像平坦区域的不连续边界更容易被观察到。当边界两侧变化剧烈时边界处的不连续可能是由视频内容自身导致。另外,滤波会减弱强纹理区域应有的纹理信息。滤波开关决策就是根据边界块内像素值的变化程度判断该边界的内容特性,然后根据内容特性确定是否需要进行滤波操作。
下图是一个垂直块边界区域示意图。p(x,y),q(x,y)分别是边界两侧的像素值。
现定义首行和末行的像素变化率。
该垂直块边界区域的纹理度定义为:
纹理度值越大表明该区域越不平坦,当大到一定程度时该边界不需要滤波。H.265/HEVC规定满足下面条件时该边界滤波开关打开,否则关闭。
阈值beta为判决门限,其与边界两侧块的量化参数QP有关,由下面方法得出。
const UChar TComLoopFilter::sm_tcTable[MAX_QP + 1 + DEFAULT_INTRA_TC_OFFSET] =
{
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,5,5,6,6,7,8,9,10,11,13,14,16,18,20,22,24
};
const UChar TComLoopFilter::sm_betaTable[MAX_QP + 1] =
{
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64
};
(3)滤波强弱选择
在上一步滤波开关打开的前提下,需要对视频内容进行更细致的判断以进一步确定滤波强度。
上面3种边界情况,(a)与(b)相比,边界两侧像素平坦而边界处变化剧烈,在视觉上会形成更强的块效应,需要对边界周围像素进行大范围、大幅度修正。(c)边界处像素变化特别大,由于失真总会处于一定范围,当差值超出一定范围后,这种块边界则是由视频内容本身所致。
当上面6个式子都满足时采用强滤波,否则采用弱滤波。第1、2式用于判断边界两侧像素值变化率,第3、4式用于判断边界两侧像素是否平坦,第5,6式用于判断边界处像素的跨度是否控制在一定范围。tc是判决门限,其与边界两侧块的量化参数QP有关,由下面方法得出。
__inline Bool TComLoopFilter::xUseStrongFiltering( Int offset, Int d, Int beta, Int tc, Pel* piSrc)
{
Pel m4 = piSrc[0];
Pel m3 = piSrc[-offset];
Pel m7 = piSrc[ offset*3];
Pel m0 = piSrc[-offset*4];
Int d_strong = abs(m0-m3) + abs(m7-m4);
return ( (d_strong < (beta>>3)) && (d<(beta>>2)) && ( abs(m3-m4) < ((tc*5+1)>>1)) );
}
滤波操作
当上面滤波决策判定为需要滤波时,则需要进行滤波操作。包括3种情况:亮度边界的强滤波、亮度边界的弱滤波、色度边界的滤波。
亮度边界的强滤波:
强滤波会对边界两侧像素进行大范围、大幅度修正。需要修正的像素为边界两侧各3个像素。
亮度边界的弱滤波:
弱滤波修正的像素范围和幅度较小。最多需要修正的像素为边界两侧各2个像素。需要根据每行像素具体情况进行滤波操作。
下面以第1行像素为例说明:
接着判断p(1,0)及q(1,0)是否需要修正。
下面代码是亮度强滤波和弱滤波的实现:
/**
- Deblocking for the luminance component with strong or weak filter
.
\param piSrc pointer to picture data
\param iOffset offset value for picture data
\param tc tc value
\param sw decision strong/weak filter
\param bPartPNoFilter indicator to disable filtering on partP
\param bPartQNoFilter indicator to disable filtering on partQ
\param iThrCut threshold value for weak filter decision
\param bFilterSecondP decision weak filter/no filter for partP
\param bFilterSecondQ decision weak filter/no filter for partQ
\param bitDepthLuma luma bit depth
*/
__inline Void TComLoopFilter::xPelFilterLuma( Pel* piSrc, Int iOffset, Int tc, Bool sw, Bool bPartPNoFilter, Bool bPartQNoFilter, Int iThrCut, Bool bFilterSecondP, Bool bFilterSecondQ, const Int bitDepthLuma)
{
Int delta;
Pel m4 = piSrc[0];
Pel m3 = piSrc[-iOffset];
Pel m5 = piSrc[ iOffset];
Pel m2 = piSrc[-iOffset*2];
Pel m6 = piSrc[ iOffset*2];
Pel m1 = piSrc[-iOffset*3];
Pel m7 = piSrc[ iOffset*3];
Pel m0 = piSrc[-iOffset*4];
if (sw)
{//!<强滤波
piSrc[-iOffset] = Clip3(m3-2*tc, m3+2*tc, ((m1 + 2*m2 + 2*m3 + 2*m4 + m5 + 4) >> 3));
piSrc[0] = Clip3(m4-2*tc, m4+2*tc, ((m2 + 2*m3 + 2*m4 + 2*m5 + m6 + 4) >> 3));
piSrc[-iOffset*2] = Clip3(m2-2*tc, m2+2*tc, ((m1 + m2 + m3 + m4 + 2)>>2));
piSrc[ iOffset] = Clip3(m5-2*tc, m5+2*tc, ((m3 + m4 + m5 + m6 + 2)>>2));
piSrc[-iOffset*3] = Clip3(m1-2*tc, m1+2*tc, ((2*m0 + 3*m1 + m2 + m3 + m4 + 4 )>>3));
piSrc[ iOffset*2] = Clip3(m6-2*tc, m6+2*tc, ((m3 + m4 + m5 + 3*m6 + 2*m7 +4 )>>3));
}
else
{//!<弱滤波
/* Weak filter */
delta = (9*(m4-m3) -3*(m5-m2) + 8)>>4 ;
if ( abs(delta) < iThrCut )
{
delta = Clip3(-tc, tc, delta);
piSrc[-iOffset] = ClipBD((m3+delta), bitDepthLuma);
piSrc[0] = ClipBD((m4-delta), bitDepthLuma);
Int tc2 = tc>>1;
if(bFilterSecondP)
{
Int delta1 = Clip3(-tc2, tc2, (( ((m1+m3+1)>>1)- m2+delta)>>1));
piSrc[-iOffset*2] = ClipBD((m2+delta1), bitDepthLuma);
}
if(bFilterSecondQ)
{
Int delta2 = Clip3(-tc2, tc2, (( ((m6+m4+1)>>1)- m5-delta)>>1));
piSrc[ iOffset] = ClipBD((m5+delta2), bitDepthLuma);
}
}
}
if(bPartPNoFilter)
{
piSrc[-iOffset] = m3;
piSrc[-iOffset*2] = m2;
piSrc[-iOffset*3] = m1;
}
if(bPartQNoFilter)
{
piSrc[0] = m4;
piSrc[ iOffset] = m5;
piSrc[ iOffset*2] = m6;
}
}
色度边界的滤波:
当BS=2时,色度边界需要滤波。需要修正的像素为边界两侧各1个像素。
下面是色度滤波的实现:
/**
- Deblocking of one line/column for the chrominance component
.
\param piSrc pointer to picture data
\param iOffset offset value for picture data
\param tc tc value
\param bPartPNoFilter indicator to disable filtering on partP
\param bPartQNoFilter indicator to disable filtering on partQ
\param bitDepthChroma chroma bit depth
*/
__inline Void TComLoopFilter::xPelFilterChroma( Pel* piSrc, Int iOffset, Int tc, Bool bPartPNoFilter, Bool bPartQNoFilter, const Int bitDepthChroma)
{
Int delta;
Pel m4 = piSrc[0];
Pel m3 = piSrc[-iOffset];
Pel m5 = piSrc[ iOffset];
Pel m2 = piSrc[-iOffset*2];
//!<deta计算
delta = Clip3(-tc,tc, (((( m4 - m3 ) << 2 ) + m2 - m5 + 4 ) >> 3) );
piSrc[-iOffset] = ClipBD((m3+delta), bitDepthChroma);
piSrc[0] = ClipBD((m4-delta), bitDepthChroma);
if(bPartPNoFilter)
{
piSrc[-iOffset] = m3;
}
if(bPartQNoFilter)
{
piSrc[0] = m4;
}
}
感兴趣的请关注微信公众号Video Coding