OpenCV中值滤波器详解及代码实现

中值滤波(Median Filter)是一种非线性滤波技术,其基本思想是在单通道中将像素点邻域的灰度值进行排序,取中间值来代替原来的像素点的灰度值。中值滤波是目前处理椒盐噪声最好的滤波方式。
椒盐噪声(salt-and-pepper noise)又称脉冲噪声,在传感器或者远程传输的过程中,原本的数据会被影响,导致图像出现大量散粒状的噪声点。而在图像中,能量大部分集中在低频和中频,因此将邻域的数据排序取的中间值很少会取到被污染的高频点。

一、几个基本概念

(1)什么是线性滤波和非线性滤波?

线性滤波:
    指两个或多个信号之和的响应等于它们各自响应之和。这么说过于学术化了,简单来说就是图像上的每一个像素点的输出值是等于一些输入像素点的加权和。

非线性滤波: 
    在计算中包含了取绝对值、置零等非线性运算。

(2) 像素点的邻域
顾名思义,一个像素点的邻域是指该像素点相邻的一些像素点。
这里写图片描述

二、中值滤波器原理

如果不在边缘区域,图像的数据是平缓的,没有太大的差值。因此,一个噪声点的值要么过大,要么过小。比如下图,左图是没有处理的原图,250在该区域由为突出,通过对3*3的9个数据进行排序,将中间值150重新填入,即滤波完成,原本的噪声点被去掉,该区域恢复平缓。同理,在边缘区域中,对于边界来说,高频不会影响,而过低数值将会突出,中值的选择将不会受到影响,除非3*3的整块区域都被污染,这时我们可以考虑更大的核来处理。
这里写图片描述

三、中值滤波实现过程

首先来看看程序效果:
这里写图片描述
(1) main函数: 读取图片—中值滤波——显示

int main(void)
{
    // [1] src读入图片
    cv::Mat src = cv::imread("pic3.jpg");
    // [2] dst目标图片
    cv::Mat dst;
    // [3] 中值滤波 RGB (5*5)的核大小
    dealMedianRGB(src, dst, 5, 5);
    // [4] 窗体显示
    cv::imshow("src", src);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    cv::destroyAllWindows();
    return 0;
}

(2) 彩色图像的通道分离
中值滤波是在一个通道上对像素点的值进行操作的,因此,将一幅彩色图像(RGB)进行分离,分别对R通道、G通道、B通道进行中值滤波处理,最后合并。

void dealMedianGary(const cv::Mat *src, cv::Mat *dst, int n, int m)
{
    // [1] 初始化
    *dst = (*src).clone();
    // [2] 彩色图片通道分离
    std::vector<cv::Mat> channels;
    cv::split(*src, channels);
    // [3] 滤波
    for (int i = 0; i < 3; i++) {
        MedianGary(&channels[i], n, m);
    }
    // [4] 合并返回
    cv::merge(channels, *dst);
    return ;
}

(3) 中值滤波处理主程序,通过扫描图像的每一个像素点,然后根据一个像素点确定一个5*5的矩形区域,将区域中的数据排序后获得一个返回值,最后将该值填入该输出像素点。
该代码只能完成5*5处理,博主比较懒就不写分类处理了,有兴趣自己写,哈哈哈哈~

void MedianGary(cv::Mat *dst, int n, int m)
{
   unsigned char *_medianArray = NULL;

    // [2] 初始化
    _medianArray = (unsigned char *)malloc(sizeof(unsigned char)*(n*m));

    printf(" 5 * 5 start ... ... \n");
    // [2-1] 扫描
    for (int i = 0; i < dst->height; i++) {
        for (int j = 0; j < dst->width; j++) {
            // [2-2] 忽略边缘
            if ((i>1) && (j>1) && (i<dst->height-2) 
                      && (j<dst->width-2)) 
            {
                // [2-3] 保存数组
                int _count = 2;
                for (int num=0; num<(n*m); num+=5,_count--) 
                {
                   _medianArray[num] = *((unsigned char*)(dst->imageData+(i-_count)*dst->widthStep+(j-2)));
                 _medianArray[num+1] = *((unsigned char*)(dst->imageData+(i-_count)*dst->widthStep+(j-1)));
                 _medianArray[num+2] = *((unsigned char*)(dst->imageData+(i-_count)*dst->widthStep+(j)));
                 _medianArray[num+3] = *((unsigned char*)(dst->imageData+(i-_count)*dst->widthStep+(j+1)));
                 _medianArray[num+4] = *((unsigned char*)(dst->imageData+(i-_count)*dst->widthStep+(j+2)));
                }
            // [2-5] 求中值并保存
            *((unsigned char*)(dst->imageData+i*dst->widthStep+j)) = medianValue(_medianArray, (n*m));
            }//for[2-2]
        }
    }//for[2-1]
}

(4) 矩阵排序, 这里采用的冒泡排序,当然你也可以用快排或者其他的

// [## 求中值 冒泡 ##]
unsigned char medianValue(unsigned char * _medianArray, int count)
{
    unsigned char temp;
    int i, j;

    // 冒泡
    for (j = 0; j < count - 1; j++) {
        for (i = 0; i < count - 1 - j; i++)
        {
            if (_medianArray[i] > _medianArray[i + 1])
            {
                temp = _medianArray[i];
                _medianArray[i] = _medianArray[i + 1];
                _medianArray[i + 1] = temp;
            }
        }
    }
    return _medianArray[count/2];
}

猜你喜欢

转载自blog.csdn.net/qq_36359022/article/details/80116137