OpenCV-C++ 图像滤波(二)-中值滤波-双边滤波

这一节主要讲一下,中值滤波, 高斯双边滤波;

中值滤波

中值滤波(Median Filter)是一种典型的非线性滤波技术,基本思想是用像素点领域灰度值的中值来代替像素点的灰度值,该方法在去除脉冲噪声,椒盐噪声的同时又能保留图像边缘信息;

中值滤波是基于排序统计理论的一种能够有效地抑制噪声的非线性信号处理技术,其基本原理是把数字图像或数字序列中一点的值用该点的一个领域内各点值的中值代替,让周围的像素值接近真实值,从而消除孤立的噪声点,对于斑点噪声(Speckle noise)和椒盐噪声(salt-pepper noise)来说尤其有用,因为它不依赖于领域内那些与典型值差别很大的值;

中值滤波在处理连续图像窗函数时与线性滤波器的工作方式类似,但滤波过程却不再是加权运算;

中值滤波在一定的条件下,可以克服常见线性滤波器如最小均方滤波器, 方框滤波器, 均值滤波器带来的图像细节模糊,而且对滤除脉冲干扰及图像扫描噪声非常有效,也常用于保护边缘信息,保存边缘的特性使他在不希望出现出现边缘模糊的场合也很有用,是非常经典的平滑噪声处理方法;

优缺点:

  • 均值滤波的输出会受到到噪声点的影响,而中值滤波不会受其影响;
  • 中值滤波耗时在均值滤波的5倍以上;

计算过程:

  1. 获取目标像素值周围一定领域范围的像素值;
  2. 按灰度值进行从小到大的排序
  3. 选择排序后的中间值作为,替代掉目标像素值;

OpenCV中medianBlur API的介绍如下:

void medianBlur( InputArray src, OutputArray dst, int ksize );
  • src需要滤波的原图像
  • dst中值滤波后输出图像
  • ksize表示领域范围大小,必须是一个奇数;

示例使用如下:

// 增加椒盐噪声
Mat srcSaltPepper = addSaltNoise(src, 100);

// 中值滤波
Mat dstMedian;
medianBlur(srcSaltPepper, dstMedian, 3);

// 高斯滤波用于对比
Mat dstGaussian;
GaussianBlur(srcSaltPepper, dstGaussian, Size(3, 3), 3, 3);

namedWindow("src", WINDOW_AUTOSIZE);
imshow("src", src);
namedWindow("srcSaltPepper", WINDOW_AUTOSIZE);
imshow("srcSaltPepper", srcSaltPepper);
namedWindow("medianBlur", WINDOW_AUTOSIZE);
imshow("medianBlur", dstMedian);
namedWindow("GaussianBlur", WINDOW_AUTOSIZE);
imshow("GaussianBlur", dstGaussian);
  • 为了验证中值滤波对去除椒盐噪声的效果,对原始图像增加了椒盐噪声,加噪的代码最下面的完整代码中有;
  • 对比了中值滤波与高斯滤波对椒盐噪声的处理效果,如下图所示,很明显中值滤波效果要好;

下图,从左到右分别为: 噪声图像, 中值滤波效果,高斯滤波效果;

高斯双边滤波

双边滤波也是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的;

双边滤波的好处在于可以作边缘保存, 一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显的模糊边缘,对于高频细节的保护效果并不明显;

双边滤波顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存;

但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波不能干净的滤掉,只能够对于低频信息进行较好的滤波;

输出的像素值计算方式如下:

\[g(i, j) = \dfrac{\sum){k, l}f(k, l)w(i, j, k, l)}{\sum_{k, l}w(i, j, k, l)} \]

  • g(i, j)表示输出像素值;
  • f(k, l)表示卷积核;
  • w(i, j, k, l)表示加权系数,取决于定义域核和值域核的乘积;

有:

\[w(i,j, k, l) = d(i, j,k, l) \cdot r(i, j, k, l) \]

其中,定义域核\(d(i, j, k, l)\)表示为:

\[d(i, j, k, l) = exp(-\dfrac{(i-k)^2+(j-l)^2}{2\sigma_d^2}) \]

值域核\(r(i, j, k, l)\)表示为:

\[r(i, j, k, l) = exp(-\dfrac{||f(i,j) - f(k, l)||^2}{2\sigma_r^2}) \]

因此, 有:

\[w(i, j, k, l) = exp(-\dfrac{(i-k)^2+(j-l)^2}{2\sigma_d^2} -\dfrac{||f(i,j) - f(k, l)||^2}{2\sigma_r^2}) \]

OpenCV中bilateralFilterAPI的介绍如下:

void bilateralFilter( InputArray src, OutputArray dst, int d,
                     double sigmaColor, double sigmaSpace,
                     int borderType = BORDER_DEFAULT );
  • src需要去噪的图像
  • dst输出的图像
  • d表示每个像素领域的直径
  • sigmaColor,颜色空间滤波器的sigma值;这个参数值越大,就表明该像素领域内有更宽广的颜色会被混合到一起,产生较大的半相等的颜色区域;
  • sigmaSpace坐标空间中的滤波器的sigma值,坐标空间的标准方差;数值越大,意味着越远的像素会互相影响,从而使得更大的区域足够相似的颜色获取相同的颜色;当\(d>0\),\(d\)指定了领域大小且与sigmaSpace,否则,d正比于sigmaSpace;

示例, 关于bilateralFilterAPI的使用:

// 双边滤// 双边滤波
Mat dstBilateralFilter;
bilateralFilter(srcSaltPepper, dstBilateralFilter, 25, 25*2, 25/2);

结果如下图所示,从左到右分别为:噪声图像, 双边滤波,高斯滤波;

可以发现,的确对图像边缘细节进行了保留;

最后,完整的代码如下:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

Mat addSaltNoise(const Mat src, int n);  // 添加椒盐噪声

int main(){

    // 读取图像
    Mat src = imread("/home/chen/dataset/lena.jpg");
    if (src.empty()){
        cout << "cloud not load image." << endl;
        return -1;
    }
    // 增加椒盐噪声
    Mat srcSaltPepper = addSaltNoise(src, 100);

    // 中值滤波
    Mat dstMedian;
    medianBlur(srcSaltPepper, dstMedian, 3);

    Mat dstGaussian;
    GaussianBlur(srcSaltPepper, dstGaussian, Size(3, 3), 3, 3);

    // 双边滤波
    Mat dstBilateralFilter;
    bilateralFilter(srcSaltPepper, dstBilateralFilter, 25, 25*2, 25/2);

    namedWindow("src", WINDOW_AUTOSIZE);
    imshow("src", src);
    namedWindow("srcSaltPepper", WINDOW_AUTOSIZE);
    imshow("srcSaltPepper", srcSaltPepper);
    namedWindow("medianBlur", WINDOW_AUTOSIZE);
    imshow("medianBlur", dstMedian);
    namedWindow("GaussianBlur", WINDOW_AUTOSIZE);
    imshow("GaussianBlur", dstGaussian);
    namedWindow("bilateralFilter", WINDOW_AUTOSIZE);
    imshow("bilateralFilter", dstBilateralFilter);

    waitKey(0);
    return 0;
}

// 添加椒盐噪声
Mat addSaltNoise(const Mat src, int n){

    Mat dst = src.clone();
    for (int k = 0; k < n; k++){
        // 随机选择行列
        int i = rand() % dst.rows;
        int j = rand() % dst.cols;

        if (dst.channels() == 1){
            dst.at<uchar>(i, j) = 255;  // 盐噪声
        } else{
            dst.at<Vec3b>(i, j)[0] = 255;
            dst.at<Vec3b>(i, j)[1] = 255;
            dst.at<Vec3b>(i, j)[2] = 255;
        }
    }
    for (int k = 0; k < n; k++)
	{
		//随机取值行列
		int i = rand() % dst.rows;
		int j = rand() % dst.cols;
		//图像通道判定
		if (dst.channels() == 1)
		{
			dst.at<uchar>(i, j) = 0;  // 椒噪声
		} else
		{
			dst.at<Vec3b>(i, j)[0] = 0;
			dst.at<Vec3b>(i, j)[1] = 0;
			dst.at<Vec3b>(i, j)[2] = 0;
		}
	}
    return dst;
}

Reference:

posted @ 2021-04-07 00:47  chenzhen0530  阅读(1911)  评论(0编辑  收藏  举报