【C++】图像处理中的滤波算法

【C++】图像处理中的滤波算法

2022年第一篇,来总结一下图像处理中常用的几种滤波算法,包括中值、均值、高斯、双边、引导滤波这五种,主要用于图像平滑去噪方面。
滤波算法的基本思想是利用周围的像素,通过加权平均来计算一个新的像素,以减少噪声对当前像素的影响。
【C++】图像处理中的滤波算法请添加图片描述

1)均值滤波:

均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围8个像素,构成一个滤波模板,即包括目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
请添加图片描述
opencv提供均值滤波函数blur():

CV_EXPORTS_W void blur( InputArray src, OutputArray dst,
                        Size ksize, Point anchor = Point(-1,-1),
                        int borderType = BORDER_DEFAULT );

C++实现:

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


using namespace std;
using namespace cv;

int main()
{
	Mat imgOri = imread("原图.jpg");
	namedWindow("原图");
	imshow("原图", imgOri);

	Mat imgOut;
	blur(imgOri, imgOut, Size(20, 20));
	namedWindow("效果图");
	imshow("效果", imgOut);

	waitKey(0);
	return true;
}

2)中值滤波:

中值滤波法是一种非线性平滑技术,将每个像素的灰度值设置为该点的某个邻域窗口内所有像素的所有灰度值的中值。采用窗口中值的方法有效去除异常亮或过暗的噪声,椒盐噪声去除效果较好,但实际图像会伴随边缘纹理。由于只考虑中值,图像的细节也会被去除。去除只比均值过滤好一点。

公式如下:
请添加图片描述
【C++】图像处理中的滤波算法
opencv提供高斯滤波函数medianBlur():

CV_EXPORTS_W void medianBlur(InputArray src,OutputArray dst,int Ksize //只能3,5,7,9后的奇数)

C++实现:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
 
using namespace cv;
 
int main()
{
 
	//载入图像
	Mat image = imread("1.jpg");
 
	//创建窗口
	namedWindow("中值滤波原图");
	namedWindow("中值滤波效果图");
 
	//进行滤波
	Mat out;
	medianBlur(image, out, 7);
	imshow("中值滤波原图", image);
	imshow("中值滤波效果图", out); 
	waitKey(0);
	return 0;
}

3)高斯滤波:

高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。 [1] 通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。

二维高斯函数:
请添加图片描述
5*5卷积核:
请添加图片描述
二维高斯函数公式如下:
【C++】图像处理中的滤波算法
opencv提供高斯滤波函数GaussianBlur():

CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );

C++实现:

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

#define GAUSS_NOISE			"GaussNoise_127"
#define FILE_TYPE			".png"
#define GAUSS_NOISE_FILE	string(GAUSS_NOISE)+string(FILE_TYPE)

#define MEANS				127
#define STANDARD_DAV		10

#define MAX_KERNEL_SIZE		500
#define MAX_SIGMA			255

using namespace std;
using namespace cv;

static void showImgPara(Mat &img);
static void showImgMinMaxMeansStdev(Mat &img);
static void kernelSizeCallBack(int, void*);
static void xSigmaCallBack(int, void*);
static void ySigmaCallBack(int, void*);

Mat gImgOri, gImgGauss, gImgGaussFilter;
int gKernelSize = 11;
int gXSigma = 10;
int gYSigma = 10;


int main()
{
	/* Original Image */
	gImgOri = imread("original.png", IMREAD_GRAYSCALE);
	if (gImgOri.empty())
	{
		cout << "Cannot find original picture!!" << endl;
		return false;
	}
	imshow("imgOri", gImgOri);
	//showImgPara(gImgOri);
	showImgMinMaxMeansStdev(gImgOri);


	/* Gauss Noise Picture Create */
	gImgGauss = imread(GAUSS_NOISE_FILE, IMREAD_GRAYSCALE);
	if (gImgGauss.empty())
	{
		cout << "Cannot find noise picture!!" << endl;
		return false;
	}
	imshow("imgGauss", gImgGauss);
	//showImgPara(gImgGauss);
	showImgMinMaxMeansStdev(gImgGauss);

	/* Gauss filter */
	//GaussianBlur(gImgGauss, gImgGaussFilter, Size(7, 7), 10, 10);
	GaussianBlur(gImgGauss, gImgGaussFilter, Size(gKernelSize, gKernelSize), gXSigma, gYSigma);
	imshow("Gauss Filter Out", gImgGaussFilter);
	showImgMinMaxMeansStdev(gImgGaussFilter);

	/* Add Bar */
	cv::createTrackbar("Kernel Size", "Gauss Filter Out", &gKernelSize, MAX_KERNEL_SIZE, kernelSizeCallBack);
	cv::createTrackbar("X Sigma", "Gauss Filter Out", &gXSigma, MAX_KERNEL_SIZE, xSigmaCallBack);
	cv::createTrackbar("Y Sigma", "Gauss Filter Out", &gYSigma, MAX_KERNEL_SIZE, ySigmaCallBack);

	waitKey(0);
	return true;
}

static void showImgPara(Mat &img)
{
	cout << "sizeof(img) is: " << sizeof(img) << ", img size is: " << img.size << endl;
	cout << "rows x cols: (" << img.rows << " x " << img.cols << ")" << endl;
	cout << "dims: " << img.dims << endl;
	cout << "channels: " << img.channels() << endl;
	cout << "type: " << img.type() << endl;
	cout << "depth:" << img.depth() << endl;
	cout << "elemSize:" << img.elemSize() << " (Bytes per element)" << endl;
	cout << "elemSize1:" << img.elemSize1() << "(Bytes per channel)" << endl;
	cout << "step[0]: " << img.step[0] << " (Bytes per cows only when 2 dims)" << endl;
	cout << "step[1]: " << img.step[1] << " (Bytes per element only when 2 dims)" << endl;
	cout << "step1(0): " << img.step1(0) << ", step1(1): " << img.step1(1) << " (step / elemSize1)" << endl;
	cout << "----showImgPara End----" << endl;
}


static void showImgMinMaxMeansStdev(Mat &img)
{
	/* Max and Min value and location */
	double minValue, maxValue;
	Point minLdx, maxLdx;
	minMaxLoc(img, &minValue, &maxValue, &minLdx, &maxLdx, Mat());
	cout << "Min: " << minValue << "[" << minLdx << "]" << endl;
	cout << "Max: " << maxValue << "[" << maxLdx << "]" << endl;

	/* means and stdev*/
	Mat means, stdev;
	meanStdDev(img, means, stdev);
	cout << "Means: " << means.at<double>(0, 0) << endl;
	cout << "Standard Deviationst: " << stdev.at<double>(0, 0) << endl;
	cout << "----showImgMinMaxMeansStdev End----\n" << endl;
}

static void kernelSizeCallBack(int, void*)
{
	if (gKernelSize % 2 == 0)
	{
		gKernelSize = gKernelSize + 1;
	}
	GaussianBlur(gImgGauss, gImgGaussFilter, Size(gKernelSize, gKernelSize), gXSigma, gYSigma);
	imshow("Gauss Filter Out", gImgGaussFilter);
	showImgMinMaxMeansStdev(gImgGaussFilter);
}


static void xSigmaCallBack(int, void*)
{
	GaussianBlur(gImgGauss, gImgGaussFilter, Size(gKernelSize, gKernelSize), gXSigma, gYSigma);
	imshow("Gauss Filter Out", gImgGaussFilter);
	showImgMinMaxMeansStdev(gImgGaussFilter);
}


static void ySigmaCallBack(int, void*)
{
	GaussianBlur(gImgGauss, gImgGaussFilter, Size(gKernelSize, gKernelSize), gXSigma, gYSigma);
	imshow("Gauss Filter Out", gImgGaussFilter);
	showImgMinMaxMeansStdev(gImgGaussFilter);
}

4)双边滤波:

双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。
请添加图片描述

空间距离:指当前点到中心点的欧式距离。
请添加图片描述

灰度距离:指当前点灰度与中心点灰度之差的绝对值。
请添加图片描述
请添加图片描述
【C++】图像处理中的滤波算法
OpenCV提供bilateralFilter()函数,API的介绍如下:

CV_EXPORTS_W void bilateralFilter( InputArray src, OutputArray dst, int d,
                     double sigmaColor, double sigmaSpace,
                     int borderType = BORDER_DEFAULT );

C++实现:

#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;
}

5)引导滤波:

导向图滤波(Guided Filter)是一种能使视频平滑化的非线性滤波器,通过一张引导图G,对目标图像P(输入图像)进行滤波处理,使得最后的输出图像大体上与目标图像P相似,但是纹理部分与引导图G相似。应用有两个:保边图像平滑,抠图。
引导滤波是由何凯明等人于2010年发表在ECCV的文章《Guided Image Filtering》中提出的,后续于2013年发表了改进算法快速引导滤波的实现。它与双边滤波最大的相似之处,就是同样具有保持边缘特性。该模型认为,某函数上一点与其邻近部分的点成线性关系,一个复杂的函数就可以用很多局部的线性函数来表示,当需要求该函数上某一点的值时,只需计算所有包含该点的线性函数的值并做平均即可。这种模型,在表示非解析函数上,非常有用。
就滤波效果而言,引导滤波与双边滤波类似,在某些细节上,引导滤波更胜一筹。引导滤波的最大优点是可以编写时间复杂度与窗口大小无关的算法,因此处理大窗口的图像效率更高。
请添加图片描述
引导图像I,原图P,公式如下:
请添加图片描述
引导滤波算法,目前opencv没有提供API,伪代码如下:
请添加图片描述
实现这种算法的关键思想是盒式滤波(box filter),而且必须是通过积分图来实现的盒式滤波,否则不可能与窗口大小无关,好在OpenCV的boxFilter函数满足这个要求。与均值滤波不同的是,方框滤波不会计算像素的均值。在均值滤波中,滤波结果的像素值是任意一个点的邻域平均值,等于各邻域像素值之和除以邻域面积。而在方框滤波中,可以自由选择是否对均值滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。

CV_EXPORTS_W void boxFilter(InputArray src,OutputArray dst, int ddepth, 
							Size ksize, Point anchor=Point(-1,-1), boolnormalize=true, 
							int borderType=BORDER_DEFAULT) 

请添加图片描述
请添加图片描述
C++实现:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
 
//
//   GUIDEDFILTER   O(1) time implementation of guided filter.
//   -guidance image : I(should be a gray - scale / single channel image)
//   -filtering input image : p(should be a gray - scale / single channel image)
//   -local window radius : r
//   -regularization parameter : eps
/
cv::Mat GuidedFilter(cv::Mat& I, cv::Mat& p, int r, double eps){
	int wsize = 2 * r + 1;
	//数据类型转换
	I.convertTo(I, CV_64F, 1.0 / 255.0);
	p.convertTo(p, CV_64F, 1.0 / 255.0);
 
	//meanI=fmean(I)
	cv::Mat mean_I;
	cv::boxFilter(I, mean_I, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
 
	//meanP=fmean(P)
	cv::Mat mean_p;
	cv::boxFilter(p, mean_p, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
 
	//corrI=fmean(I.*I)
	cv::Mat mean_II;
	mean_II = I.mul(I);
	cv::boxFilter(mean_II, mean_II, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
 
	//corrIp=fmean(I.*p)
	cv::Mat mean_Ip;
	mean_Ip = I.mul(p);
	cv::boxFilter(mean_Ip, mean_Ip, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
 
	//varI=corrI-meanI.*meanI
	cv::Mat var_I, mean_mul_I;
	mean_mul_I=mean_I.mul(mean_I);
	cv::subtract(mean_II, mean_mul_I, var_I);
 
	//covIp=corrIp-meanI.*meanp
	cv::Mat cov_Ip;
	cv::subtract(mean_Ip, mean_I.mul(mean_p), cov_Ip);
 
	//a=conIp./(varI+eps)
	//b=meanp-a.*meanI
	cv::Mat a, b;
	cv::pide(cov_Ip, (var_I+eps),a);
	cv::subtract(mean_p, a.mul(mean_I), b);
 
	//meana=fmean(a)
	//meanb=fmean(b)
	cv::Mat mean_a, mean_b;
	cv::boxFilter(a, mean_a, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
	cv::boxFilter(b, mean_b, -1, cv::Size(wsize, wsize), cv::Point(-1, -1), true, cv::BORDER_REFLECT);//盒子滤波
 
	//q=meana.*I+meanb
	cv::Mat q;
	q = mean_a.mul(I) + mean_b;
 
	//数据类型转换
	I.convertTo(I, CV_8U, 255);
	p.convertTo(p, CV_8U, 255);
	q.convertTo(q, CV_8U, 255);
 
	return q;
 
}
 
int main(){
	cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\woman.jpg");
	if (src.empty()){
		return -1;
	}
 
	//if (src.channels() > 1)  
	//	cv::cvtColor(src, src, CV_RGB2GRAY);
	
	//自编GuidedFilter测试
	double t2 = (double)cv::getTickCount(); //测时间
 
	cv::Mat dst1, src_input, I;
	src.copyTo(src_input);
	if (src.channels() > 1)
	   cv::cvtColor(src, I, CV_RGB2GRAY); //若引导图为彩色图,则转为灰度图
	std::vector<cv::Mat> p,q;
	if (src.channels() > 1){             //输入为彩色图
		cv::split(src_input, p);
		for (int i = 0; i < src.channels(); ++i){
			dst1 = GuidedFilter(I, p[i], 9, 0.1*0.1);
			q.push_back(dst1);
		}
		cv::merge(q, dst1);
	}
	else{                               //输入为灰度图
		src.copyTo(I);
		dst1 = GuidedFilter(I, src_input, 9, 0.1*0.1);
	}
 
	t2 = (double)cv::getTickCount() - t2;
	double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
	std::cout << "MyGuidedFilter_process=" << time2 << " ms. " << std::endl << std::endl;
 
	cv::namedWindow("GuidedImg", CV_WINDOW_NORMAL);
	cv::imshow("GuidedImg", I);
	cv::namedWindow("src", CV_WINDOW_NORMAL);
	cv::imshow("src", src);
	cv::namedWindow("GuidedFilter_box", CV_WINDOW_NORMAL);
	cv::imshow("GuidedFilter_box", dst1);
	cv::waitKey(0);
 
}

版权声明:本文为博主Nirvana;原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/weixin_45355387/article/details/122938637

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2022年2月19日 下午12:33
下一篇 2022年2月19日

相关推荐