openCV 中值滤波算法解析

中值滤波算法是使用一个模板,在图像中移动的过程中,取模板内的排列中间的值替代模板中心的值。

常用的中值滤波的快速算法见论文“A Fast Two-Dimensional Median Filtering Algorithm”。本人按照论文上的方法实现了一下,发现效率仅是openCV的十分之一,研究了一下openCV1.0的源码,发现他也是用的论文上的原理,但是,他的编程手法就高明多了。下面,咱们就看一下openCV是怎么处理的!

/*

1.src原图

2.src_step原图步长

3.dst处理后

4.dst图步长

5.size 图像大小

6.m模板大小

7.cn图像通道数

*/

static CvStatus CV_STDCALL icvMedianBlur_8u_CnR( uchar* src, int src_step, uchar* dst, int dst_step, CvSize size, int m, int cn )

{
   //定义16级灰度直方图
    #define N  16

    int zone0[4][N];

    //定义256级灰度直方图
    int zone1[4][N*N];

    int x, y;


    //中值的位置

    int n2 = m*m/2; 


   //每行(列)的中值位置
    int nx = (m + 1)/2 - 1;

    uchar*  src_max = src + size.height*src_step;
    uchar*  src_right = src + size.width*cn;


    //更新直方图
    #define UPDATE_ACC01( pix, cn, op ) \
    {                                   \
        int p = (pix);                  \
        zone1[cn][p] op;                \
        zone0[cn][p >> 4] op;           \
    }

    if( size.height < nx || size.width < nx )
        return CV_BADSIZE_ERR;


    //模板大小为3时候,单独处理(效率高)

    if( m == 3 )
    {
        size.width *= cn;


//列循环
        for( y = 0; y < size.height; y++, dst += dst_step )
        {
            const uchar* src0 = src + src_step*(y-1);
            const uchar* src1 = src0 + src_step;

            const uchar* src2 = src1 + src_step;

    //处理第一列的时候src0 = src1

            if( y == 0 )

                src0 = src1;

   //处理最后一列的时候 src2 = src1

            else if( y == size.height - 1 )
                src2 = src1;

           //考虑多通道循环
            for( x = 0; x < 2*cn; x++ )
            {
    int x0 = x < cn ? x : size.width - 3*cn + x;
int x2 = x < cn ? x + cn : size.width - 2*cn + x;
int x1 = x < cn ? x0 : x2, t;


                int p0 = src0[x0], p1 = src0[x1], p2 = src0[x2];
                int p3 = src1[x0], p4 = src1[x1], p5 = src1[x2];
                int p6 = src2[x0], p7 = src2[x1], p8 = src2[x2];


                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p1);

CV_MINMAX_8U(p3, p4); CV_MINMAX_8U(p6, p7);

//代码图解:0~5代表执行顺序,


   openCV 中值滤波算法解析_第1张图片
CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p3);
CV_MINMAX_8U(p5, p8); CV_MINMAX_8U(p4, p7);

                //代码图解:0~5代表执行顺序,

//经过第一步六次对比,再加上本次的0~2的三次对比,3*3的数据已经达到行向有序(从小到大)

                //P0 P3 P6 是行向最小值 ,P2 P5 P8 是行向最大值

openCV 中值滤波算法解析_第2张图片

                CV_MINMAX_8U(p3, p6); CV_MINMAX_8U(p1, p4);
                CV_MINMAX_8U(p2, p5); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p4, p2); CV_MINMAX_8U(p6, p4);

//代码图解:0~5代表执行顺序,

//经过第二步的六次对比,再加上本次的0~3的四次对比,中间列(P1、P4、P7)已到达列向有序,第一列和第三列都没有严格有序

//但是,可以确定的是P6是行向小值和列向最大值, P2 是行向最大值和列向最小值

openCV 中值滤波算法解析_第3张图片

                CV_MINMAX_8U(p4, p2);

//第三部经过4~5两次斜向对比,再加上本步一次对比,实现了(P2、P4、P6)的斜向有序。

//对比完成后P4就是中值

//总之,其算法的基本思路是先对数据进行排序(算法中的行向有序),然后再在三组有序的数据中找到数据合并后的中位数

//那就是,取出有序序列组的最小值中的最大值、最大值中的最小值、中间列向的中值,三个数进行比较,取其中值就是

//三个有序序列的中值

                dst[x1] = (uchar)p4;
            }


            for( x = cn; x < size.width - cn; x++ )
      {
int p0 = src0[x-cn], p1 = src0[x], p2 = src0[x+cn];
int p3 = src1[x-cn], p4 = src1[x], p5 = src1[x+cn];
int p6 = src2[x-cn], p7 = src2[x], p8 = src2[x+cn];
int t;


                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p1);
                CV_MINMAX_8U(p3, p4); CV_MINMAX_8U(p6, p7);
                CV_MINMAX_8U(p1, p2); CV_MINMAX_8U(p4, p5);
                CV_MINMAX_8U(p7, p8); CV_MINMAX_8U(p0, p3);
                CV_MINMAX_8U(p5, p8); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p3, p6); CV_MINMAX_8U(p1, p4);
                CV_MINMAX_8U(p2, p5); CV_MINMAX_8U(p4, p7);
                CV_MINMAX_8U(p4, p2); CV_MINMAX_8U(p6, p4);
                CV_MINMAX_8U(p4, p2);


                dst[x] = (uchar)p4;
            }
        }


        return CV_OK;
    }


    for( x = 0; x < size.width; x++, dst += cn )
    {
        uchar* dst_cur = dst;
        uchar* src_top = src;
        uchar* src_bottom = src;
        int k, c;
        int x0 = -1;


if( x <= m/2 )
nx++;


        if( nx < m )
            x0 = x < m/2 ? 0 : (nx-1)*cn;
            
        // init accumulator
        memset( zone0, 0, sizeof(zone0[0])*cn );
        memset( zone1, 0, sizeof(zone1[0])*cn );
        
        for( y = -m/2; y < m/2; y++ )
        {
            for( c = 0; c < cn; c++ )
            {
if( x0 >= 0 )
UPDATE_ACC01( src_bottom[x0+c], c, += (m - nx) );
for( k = 0; k < nx*cn; k += cn )
UPDATE_ACC01( src_bottom[k+c], c, ++ );
            }


            if( (unsigned)y < (unsigned)(size.height-1) )
                src_bottom += src_step;
        }


        for( y = 0; y < size.height; y++, dst_cur += dst_step )
        {
            if( cn == 1 )
            {
                for( k = 0; k < nx; k++ )
                    UPDATE_ACC01( src_bottom[k], 0, ++ );
            }
            else if( cn == 3 )
            {
                for( k = 0; k < nx*3; k += 3 )
                {
                    UPDATE_ACC01( src_bottom[k], 0, ++ );
                    UPDATE_ACC01( src_bottom[k+1], 1, ++ );
                    UPDATE_ACC01( src_bottom[k+2], 2, ++ );
                }
            }
            else
            {
                assert( cn == 4 );
                for( k = 0; k < nx*4; k += 4 )
                {
                    UPDATE_ACC01( src_bottom[k], 0, ++ );
                    UPDATE_ACC01( src_bottom[k+1], 1, ++ );
                    UPDATE_ACC01( src_bottom[k+2], 2, ++ );
                    UPDATE_ACC01( src_bottom[k+3], 3, ++ );
                }
            }


            if( x0 >= 0 )
            {
                for( c = 0; c < cn; c++ )
                    UPDATE_ACC01( src_bottom[x0+c], c, += (m - nx) );
            }


            if( src_bottom + src_step < src_max )
                src_bottom += src_step;


            // find median

  //重点介绍一下这部分代码

//计算过16级和256级的灰度直方图之后,查找中值就很简单了,相见论文“A Fast Two-Dimensional Median Filtering Algorithm”。

//这里看一下openCV的实现过程,论文中介绍了利用直方图进行中位数计算的方法,但是并未介绍使用16级的直方图,openCV编程的巧妙之处就在于他把两种直方图相

 //结合,这样就会大大加速程序的执行效率。因为遍历一个16级的直方图很快,但是256级的直方图就会非常慢。openCV的这种思路还是值得大家学习的。

  //至于里面的编程技巧,大家看着学吧!

            for( c = 0; c < cn; c++ )
            {
                int s = 0;
                for( k = 0; ; k++ )
                {
                    int t = s + zone0[c][k];
                    if( t > n2 ) break;
                    s = t;
                }


                for( k *= N; ;k++ )
                {
                    s += zone1[c][k];
                    if( s > n2 ) break;
                }


                dst_cur[c] = (uchar)k;
            }


            if( cn == 1 )
            {
                for( k = 0; k < nx; k++ )
                    UPDATE_ACC01( src_top[k], 0, -- );
            }
            else if( cn == 3 )
            {
                for( k = 0; k < nx*3; k += 3 )
                {
                    UPDATE_ACC01( src_top[k], 0, -- );
                    UPDATE_ACC01( src_top[k+1], 1, -- );
                    UPDATE_ACC01( src_top[k+2], 2, -- );
                }
            }
            else
            {
                assert( cn == 4 );
                for( k = 0; k < nx*4; k += 4 )
                {
                    UPDATE_ACC01( src_top[k], 0, -- );
                    UPDATE_ACC01( src_top[k+1], 1, -- );
                    UPDATE_ACC01( src_top[k+2], 2, -- );
                    UPDATE_ACC01( src_top[k+3], 3, -- );
                }
            }


            if( x0 >= 0 )
            {
                for( c = 0; c < cn; c++ )
                    UPDATE_ACC01( src_top[x0+c], c, -= (m - nx) );
            }


            if( y >= m/2 )
                src_top += src_step;
        }


        if( x >= m/2 )
            src += cn;
        if( src + nx*cn > src_right ) nx--;
    }
#undef N
#undef UPDATE_ACC
    return CV_OK;
}

你可能感兴趣的:(openCV算法解析)