源码阅读 x264 - 去块效应滤波

x264_deblock_init() 中初始化了一系列环路滤波函数。

  1. 包含 "v" 的是垂直滤波器,用于处理水平边界;包含 "h" 的是水平滤波器,用于处理垂直边界。
  2. 包含 "luma" 的是亮度滤波器,包含 "chroma" 的是色度滤波器。
  3. 包含 "intra" 的是处理边界强度 Bs 为 4 的强滤波器,不包含 "intra" 的是普通滤波器。

x264_deblock_init 函数代码如下所示:

void x264_deblock_init(uint32_t cpu, x264_deblock_function_t *pf, int b_mbaff) {
    pf->deblock_luma[1] = deblock_v_luma_c;
    pf->deblock_luma[0] = deblock_h_luma_c;
    pf->deblock_chroma[1] = deblock_v_chroma_c;
    pf->deblock_h_chroma_420 = deblock_h_chroma_c;
    pf->deblock_h_chroma_422 = deblock_h_chroma_422_c;
    pf->deblock_luma_intra[1] = deblock_v_luma_intra_c;
    pf->deblock_luma_intra[0] = deblock_h_luma_intra_c;
    pf->deblock_chroma_intra[1] = deblock_v_chroma_intra_c;
    pf->deblock_h_chroma_420_intra = deblock_h_chroma_intra_c;
    pf->deblock_h_chroma_422_intra = deblock_h_chroma_422_intra_c;
    pf->deblock_luma_mbaff = deblock_h_luma_mbaff_c;
    pf->deblock_chroma_420_mbaff = deblock_h_chroma_mbaff_c;
    pf->deblock_luma_intra_mbaff = deblock_h_luma_intra_mbaff_c;
    pf->deblock_chroma_420_intra_mbaff = deblock_h_chroma_intra_mbaff_c;
    pf->deblock_strength = deblock_strength_c;
    /* 此处省略大量平台的汇编函数初始化代码 */
    ......
}

环路滤波分类

环路滤波器根据滤波的强度可以分为两种:

  1. 普通滤波器。针对边界的 Bs(边界强度)为 1、2、3 的滤波器。
  2. 强滤波器。针对边界的 Bs(边界强度)为 4 的滤波器。

普通滤波器

此时环路滤波涉及到方块边界周围的 6 个点(边界两边各 3 个点):\(p2,p1,p0,q0,q1,q2\) 需要处理 4 个点(边界两边各 2 个点,只以 p 侧的点为例):

描述一个 4x4 块横向或者纵向边界的样点的惯例
p0' = p0 + (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3
p1' = (p2 + ( ( p0 + q0 + 1) >> 1) – 2p1 ) >> 1

强滤波器

此时环路滤波涉及到方块边界周围的 8 个点(边界两边各 4 个点):\(p3,p2,p1,p0,q0,q1,q2,q3\) 需要处理 6 个点(边界两边各 3 个点,只以 p 侧的点为例):

描述一个 4x4 块横向或者纵向边界的样点的惯例
p0' = (p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4) >> 3
p1' = (p2 + p1 + p0 + q0 + 2) >> 2
p2' = (2*p3 + 3*p2 + p1 + p0 + q0 + 4) >> 3

边界强度的计算和边界分析方法详见:编解码技术:H264 - 去块效应滤波

普通滤波函数

deblock_v_luma_c

deblock_v_luma_c() 是一个普通强度的垂直滤波器,用于处理边界强度 Bs 为 1,2,3 的水平边界。该函数的定义位于 common/deblock.c,如下所示:

/*
 * 去块效应滤波 - 普通滤波,Bs 为 1,2,3
 * 垂直 Vertical 滤波器
 *          x
 *          x
 * 边界 ==========
 *          x
 *          x
 */
static void deblock_v_luma_c(pixel *pix, intptr_t stride, int `alpha`, int beta, int8_t *tc0) {
    // xstride = stride(用于选择滤波的像素)
    // ystride = 1
    deblock_luma_c(pix, stride, 1, alpha, beta, tc0);
}

deblock_v_luma_c() 调用了另一个函数 deblock_luma_c()。需要注意传递给 deblock_luma_c() 是一个水平滤波器和垂直滤波器都会调用的通用滤波器函数。在这里传递给 deblock_luma_c() 第二个参数 xstride 的值为 stride,第三个参数 ystride 的值为 1。

static inline void deblock_luma_c(pixel *pix, intptr_t xstride, intptr_t ystride, int alpha, int beta, int8_t *tc0) {
    for (int i = 0; i < 4; i++) {
        if (tc0[i] < 0 ) {
            pix += 4*ystride;
            continue;
        }
        for (int d = 0; d < 4; d++, pix += ystride)
            deblock_edge_luma_c(pix, xstride, alpha, beta, tc0[i] );
    }
}

具体的滤波在 deblock_edge_luma_c() 中完成。处理完一个像素后,会继续处理与当前像素距离为 ystride 的像素

static inline void deblock_edge_luma_c(pixel *pix, intptr_t xstride, int alpha, int beta, int8_t tc0) {
    /*
     * p 和 q
     * 如果 xstride = stride,ystride = 1
     * 就是处理纵向的 6 个像素
     * 对应的是方块的横向边界的滤波,即如下所示:
     *         p2
     *         p1
     *         p0
     * ===== 图像边界 =====
     *         q0
     *         q1
     *         q2
     *
     * 如果 xstride = 1,ystride = stride
     * 就是处理纵向的 6 个像素
     * 对应的是方块的横向边界的滤波,即如下所示:
     *           ||
     *  p2 p1 p0 || q0 q1 q2
     *           ||
     *          边界
     *
     * 注意:这里乘的是 xstride
     */
    int p2 = pix[-3*xstride];
    int p1 = pix[-2*xstride];
    int p0 = pix[-1*xstride];
    int q0 = pix[0*xstride];
    int q1 = pix[1*xstride];
    int q2 = pix[2*xstride];

    // 计算方法参考相关的标准
    // alpha 和 beta 是用于检查图像内容的 2 个参数
    // 只有满足 if() 里面 3 个取值条件的时候(只涉及边界旁边的 4 个点),才会滤波
    if (abs( p0 - q0) < alpha && abs( p1 - p0 ) < beta && abs( q1 - q0 ) < beta ) {
        int tc = tc0;
        int delta;
        // 上面 2 个点(p0,p2)满足条件的时候,滤波 p1
        if (abs( p2 - p0) < beta ) {
            // p1' = (p2 + ( ( p0 + q0 + 1) >> 1) – 2p1 ) >> 1
            //     = (p2 + ( ( p0 + q0 + 1) >> 1)) >> 1 - p1
            if (tc0) pix[-2*xstride] = p1 + x264_clip3( (( p2 + ((p0 + q0 + 1) >> 1)) >> 1) - p1, -tc0, tc0 );
            tc++;
        }
        // 下面 2 个点(q0,q2)满足条件的时候,滤波 q1
        if (abs( q2 - q0) < beta ) {
            if (tc0) pix[1*xstride] = q1 + x264_clip3( (( q2 + ((p0 + q0 + 1) >> 1)) >> 1) - q1, -tc0, tc0 );
            tc++;
        }
        delta = x264_clip3((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3, -tc, tc );
        // p0' = p0 + (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3
        pix[-1*xstride] = x264_clip_pixel( p0 + delta );    /* p0' */
        pix[0*xstride] = x264_clip_pixel( q0 - delta );     /* q0' */
    }
}

static inline int x264_clip3(int v, int i_min, int i_max) {   // 将 v 限制在 i_min 和 i_max 之间
    return ((v < i_min) ? i_min : (v > i_max) ? i_max : v );
}
static inline pixel x264_clip_pixel(int x) {                  // 将 x 限制在 0 和 255 之间
    return ((x & ~PIXEL_MAX) ? (-x)>>31 & PIXEL_MAX : x );
}

deblock_h_luma_c

deblock_h_luma_c() 是一个普通强度的水平滤波器,用于处理边界强度 Bs 为 1,2,3 的垂直边界。该函数的定义如下所示:

/*
 * 去块效应滤波 - 普通滤波,Bs 为 1,2,3
 * 水平 Horizontal 滤波器
 *      边界
 *       ||
 * x x x || x x x
 *       ||
 */
static void deblock_h_luma_c(pixel *pix, intptr_t stride, int alpha, int beta, int8_t *tc0)
{
    // xstride = 1(用于选择滤波的像素)
    // ystride = stride
    deblock_luma_c(pix, 1, stride, alpha, beta, tc0);
}

deblock_v_luma_c() 类似,deblock_h_luma_c() 同样调用了 deblock_luma_c() 函数。唯一的不同在于它传递给 deblock_luma_c() 的第 2 个参数 xstride 为 1,第 3 个参数 ystridestride

强滤波函数

deblock_v_luma_intra_c

deblock_v_luma_intra_c() 是一个强滤波的垂直滤波器,用于处理边界强度 Bs 为 4 的水平边界。该函数的定义位于 common/deblock.c,如下所示:

/*
 * 垂直 Vertical 强滤波器 - Bs 为 4
 *        边界
 *          x
 *          x
 * 边界 ----------
 *          x
 *          x
 */
static void deblock_v_luma_intra_c(pixel *pix, intptr_t stride, int alpha, int beta) {
    // 注意
    // xstride = stride
    // ystride = 1
    // 处理完 1 个像素点之后,pix 增加 ystride

    // 水平滤波和垂直滤波通用的强滤波函数
    deblock_luma_intra_c(pix, stride, 1, alpha, beta);
}

deblock_v_luma_intra_c() 调用了另一个函数 deblock_luma_intra_c()。需要注意 deblock_luma_intra_c() 是一个水平滤波器和垂直滤波器都会调用的通用滤波器函数。在这里传递给 deblock_luma_intra_c() 第二个参数 xstride 的值为 stride,第三个参数 ystride 的值为 1。

// 水平滤波和垂直滤波通用的强滤波函数 - Bs 为 4
static inline void deblock_luma_intra_c(pixel *pix, intptr_t xstride, intptr_t ystride, int alpha, int beta) {
    // 循环处理 16 个点
    // 处理完 1 个像素点之后,pix 增加 ystride
    for (int d = 0; d < 16; d++, pix += ystride)
        // 每次处理 1 个点
        deblock_edge_luma_intra_c(pix, xstride, alpha, beta);
}

具体的滤波在 deblock_edge_luma_intra_c() 中完成。处理完一个像素后,会继续处理与当前像素距离为 ystride 的像素。

// 水平滤波和垂直滤波通用的强滤波函数 - 处理 1 个点 - Bs 为 4
// 注意涉及到 8 个像素
static inline void deblock_edge_luma_intra_c(pixel *pix, intptr_t xstride, int alpha, int beta) {
    /*
     * 如果 xstride = stride,ystride = 1
     * 就是处理纵向的 6 个像素
     * 对应的是方块的横向边界的滤波。如下所示:
     *         p2
     *         p1
     *         p0
     * ===== 图像边界 =====
     *         q0
     *         q1
     *         q2
     *
     * 如果 xstride = 1,ystride = stride
     * 就是处理纵向的 6 个像素
     * 对应的是方块的横向边界的滤波,即如下所示:
     *           ||
     *  p2 p1 p0 || q0 q1 q2
     *           ||
     *          边界

     * 注意:这里乘的是 xstride
     */
    int p2 = pix[-3*xstride];
    int p1 = pix[-2*xstride];
    int p0 = pix[-1*xstride];
    int q0 = pix[0*xstride];
    int q1 = pix[1*xstride];
    int q2 = pix[2*xstride];
    // 满足条件的时候,才滤波
    if (abs( p0 - q0) < alpha && abs( p1 - p0 ) < beta && abs( q1 - q0 ) < beta ) {
        if (abs( p0 - q0) < ((alpha >> 2) + 2) ) {
            if (abs( p2 - p0) < beta ) /* p0', p1', p2' */ {
                const int p3 = pix[-4*xstride];
                // p0' = (p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4) >> 3
                pix[-1*xstride] = ( p2 + 2*p1 + 2*p0 + 2*q0 + q1 + 4 ) >> 3;
                // p1' = (p2 + p1 + p0 + q0 + 2) >> 2
                pix[-2*xstride] = ( p2 + p1 + p0 + q0 + 2 ) >> 2;
                // p2' = (2*p3 + 3*p2 + p1 + p0 + q0 + 4) >> 3
                pix[-3*xstride] = ( 2*p3 + 3*p2 + p1 + p0 + q0 + 4 ) >> 3;
            } else /* p0' */ pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2;
            if (abs( q2 - q0) < beta ) /* q0', q1', q2' */ {
                const int q3 = pix[3*xstride];
                pix[0*xstride] = ( p1 + 2*p0 + 2*q0 + 2*q1 + q2 + 4 ) >> 3;
                pix[1*xstride] = ( p0 + q0 + q1 + q2 + 2 ) >> 2;
                pix[2*xstride] = ( 2*q3 + 3*q2 + q1 + q0 + p0 + 4 ) >> 3;
            } else /* q0' */ pix[0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2;
        } else /* p0', q0' */ {
            pix[-1*xstride] = ( 2*p1 + p0 + q1 + 2 ) >> 2;
            pix[0*xstride] = ( 2*q1 + q0 + p1 + 2 ) >> 2;
        }
    }
}

参考资料