C++手敲灰度图均值滤波中值滤波高斯滤波

一、均值滤波(Meaning Filtering)概念

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

f9fb918225b3482ea03ca1b51df7aa96.png

代码实现:

①先引入头文件,声明核的大小为3*3

#include<opencv2/opencv.hpp>

#define filter_size 3

using namespace cv;

 ②导入原始图片,生成高斯噪声。显示原始图片生成的噪声图片以及噪声噪声和原图片的叠加

注意噪声图片的大小和原始图片保持一致。其中原图加噪声的窗口我设置为可以随机改变大小,方便后面结果对比。

Mat image = imread("C:\\Users\\Czhannb\\Desktop\\CV.png", IMREAD_GRAYSCALE);
Mat noise = Mat::zeros(Size(image.cols, image.rows), image.type());
imshow("原图", image);

//加入Gau斯噪声
RNG rng;
rng.fill(noise, RNG::NORMAL, 10, 30);
imshow("噪声", noise);

//拼起来
image = image + noise;
namedWindow("原图加噪声", WINDOW_FREERATIO);
imshow("原图加噪声", image);

  原图:                                                                       噪声:
24b9b6e8ceab4a228922e6ae941843bd.png77bacae91fa14c60bf1461380d5ae81b.png

 合起来:
97f2f6f261e84ce9b8587fd221b193e8.png

③得到待处理图片之后,依据均值滤波原理处理图片。

首先创建一张空白图片,其维度和原始图片一致。

Mat module_mean = Mat::zeros(Size(image.cols, image.rows), image.type());

由于核的大小为3*3,也就是说核只能够向右移动image.cols-3次,向下移动image.rows-3次。

对于每一次遍历,九个像素点的均值将替代核中心的像素。这样的话,如果不对图片进行处理(Padding等等),图片最外面一层的像素值是不会改变的。不过对于我们由几百行几百列像素点组成的图片而言,这点可以忽略掉。

我们先用两个for循环,遍历到每一个像素点(其中最右边的两列以及最下面的两行不能到达)。

40b8fad43b354c68bba8cae7a42fa17c.png

遍历到能够到达的像素点之后,求九个像素点的均值,然后赋给中间的像素。代码如下:

for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {

			int sum = 0;     
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					sum += image.at<uchar>(row + i, col + j);  //求每一个3x3小矩阵的像素之和
				}
			}
			module_mean.at<uchar>(i + 1, j + 1) = sum / 9;     //把均值赋予空白图片对应的像素
		}
	}

图中最后一行代码:把像素点均值赋予给了中心像素的位置表示是(i + 1, j + 1)。这是一个相对坐标,当i=j=0时,也就是第一轮遍历,像素的中心出现在(1,1)。后面也是这样处理。

里面的两个for循环作用是计算每一次遍历中3*3像素点之和。

这样一来就大功告成了!让我们来看看均值滤波之和的效果。

④结果对比

imshow("均值滤波之后", module_mean);

c954498db0884843b2f2bd902a171913.png

 可见效果还行吧。。(均值滤波本来就有模糊的作用,加之截图又给了一层buff,大伙将就!)

讲完均值滤波来讲讲中值滤波。

二、中值滤波

中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值.

中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近真实值,从而消除孤立的噪声点。方法是用某种结构的二维滑动模板,将板内像素按照像素值的大小进行排序,生成单调上升(或下降)的为二维数据序列。二维中值滤波输出为g(x,y)=med{f(x-k,y-l),(k,l∈W)} ,其中,f(x,y),g(x,y)分别为原始图像和处理后图像。W为二维模板,通常为3*3,5*5区域,也可以是不同的形状,如线状,圆形,十字形,圆环形等。

代码实现:

①和均值滤波类似,我们先生成一张空白图片(全黑,形状和原始图像大小一致)

Mat module_mid = Mat::zeros(Size(image.cols, image.rows), image.type());

②相似的,我们亦需用两个for循环遍历能到达的像素。然后,与均值滤波不同的是,我们要做的不是把后面两个for循环遍历得到的像素值相加做平均,而是把它们放到一个vector容器中。这样做的原因是vector自带排序函数,这样就可以偷懒不用再给数组排序了。而我们想要的中值像素,就出现在vector[4]当中。

for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {

			vector<int>vec;     // 创建容器来装每一轮的九个像素值
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					vec.push_back(image.at<uchar>(row + i, col + j));    // 每一个像素追加到容器后面
				}
			}
			sort(vec.begin(), vec.end());     // 排序(升序降序都可以)
			module_mid.at<uchar>(i + 1, j + 1) = vec[4];     //第五个元素就是中间值
		}
	}

每一轮遍历拿到中值像素vec[4]之后,赋给module_mid空白图片对应的像素点,即可大功告成。

③结果对比

imshow("中值滤波之后", module_mid);

807f306160b84a1f90871c42d4fab4d4.png

 可见效果也挺不错。。

三、高斯滤波

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

3*3的高斯核如下:
df13c2aa5f9d4a7db1a63f0672bd267e.png

代码实现:

①先创建一个二维数组充当高斯核(越靠近中心像素,权重越大),后面再通过索引得到高斯核的值与原图像的像素点进行相乘。得到的结果做平均就可以赋给中心像素了。

int Gau_filter[3][3];
	for (int i = 0; i < filter_size; i++) {
		for (int j = 0; j < filter_size; j++) {
			if ((i==1)&&(j==1)) {
				Gau_filter[i][j] = 8;
			}
			else if ((i+j==filter_size-2)||(i+j==filter_size)) {
				Gau_filter[i][j] = 2;
			}
			else {
				Gau_filter[i][j] = 1;
			}
		}
	}

②与上述两个滤波方法一致,这里先生成一张空白图像。

Mat module_Gau = Mat::zeros(Size(image.cols, image.rows), image.type());

再通过两个for循环遍历,创建一个整型变量sum来记录下来每一轮加权求和的结果。

在嵌套的两个for循环中注意:对原图的遍历记为(row+i, col+j),而对高斯核的遍历记为[row][col]。

这样可以用相同的高斯核对每一次遍历的九个像素点做处理。

for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {
			int sum = 0;
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					sum +=image.at<uchar>(row+i, col+j)*Gau_filter[row][col];
				}
			}
			module_Gau.at<uchar>(i + 1, j + 1) = sum / 20;
		}
	}

20是高斯核的值求和。

③结果对比

imshow("高斯滤波之后", module_Gau);

044c2e4403d74b95b6342986aa611b87.png

效果也还行吧。

四、3*3核均值滤波中值滤波高斯滤波代码汇总

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

using namespace cv;
using namespace std;

#define filter_size 3
#define Filter_Size 5

int main() {
	// 3*3模板
	
	Mat image = imread("C:\\Users\\Czhannb\\Desktop\\gray.png", IMREAD_GRAYSCALE);
	Mat noise = Mat::zeros(Size(image.cols, image.rows), image.type());
	imshow("原图", image);
	imshow("零噪声", noise);

	//加入Gau斯噪声
	RNG rng;
	rng.fill(noise, RNG::NORMAL, 10, 30);
	imshow("噪声", noise);

    //拼起来
	image = image + noise;
	namedWindow("原图加噪声", WINDOW_FREERATIO);
	imshow("原图加噪声", image);

    // 定义高斯核
	int Gau_filter[3][3];
	for (int i = 0; i < filter_size; i++) {
		for (int j = 0; j < filter_size; j++) {
			if ((i==1)&&(j==1)) {
				Gau_filter[i][j] = 8;
			}
			else if ((i+j==filter_size-2)||(i+j==filter_size)) {
				Gau_filter[i][j] = 2;
			}
			else {
				Gau_filter[i][j] = 1;
			}
		}
	}


	
    // 均值滤波 - 图像边缘不做处理,即无padding
	// 前两个for循环遍历整张图
	Mat module_mean = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {

			int sum = 0;     
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					sum += image.at<uchar>(row + i, col + j);  //求每一个3x3小矩阵的像素之和
				}
			}
			module_mean.at<uchar>(i + 1, j + 1) = sum / 9;     //把均值赋予新的像素矩阵
		}
	}
	imshow("均值滤波之后", module_mean);
	
	
	
	//中值滤波 - 图像边缘不做处理
	// 前两个for循环遍历整张图
	Mat module_mid = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {

			vector<int>vec;     // 创建容器来装每一轮的九个像素值
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					vec.push_back(image.at<uchar>(row + i, col + j));    // 每一个像素追加到容器后面
				}
			}
			sort(vec.begin(), vec.end());     // 排序(升序降序都可以)
			module_mid.at<uchar>(i + 1, j + 1) = vec[4];     //第五个元素就是中间值
		}
	}
	imshow("中值滤波之后", module_mid);
	*/
	

	
	//高斯滤波 - 图像边缘不做处理
	// 前两个for循环遍历整张图
	Mat module_Gau = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 2; i++) {
		for (int j = 0; j < image.cols - 2; j++) {
			int sum = 0;
			for (int row = 0; row < filter_size; row++) {
				for (int col = 0; col < filter_size; col++) {
					sum +=image.at<uchar>(row+i, col+j)*Gau_filter[row][col];
				}
			}
			module_Gau.at<uchar>(i + 1, j + 1) = sum / 20;
		}
	}
	imshow("高斯滤波之后", module_Gau);
	
	waitKey(0);
	destroyAllWindows();
	return 0;
}

 五、拓展

    ①既然有3*3,那么就会有5*5,6*6等等等等,但不是核越大越好,常言道做什么都有个界限。我还多准备了一份5*5核的均值滤波中值滤波核高斯滤波代码如下。要注意的当然就是边界问题、核以及图片的遍历问题、高斯核的大小更换等等问题。不过相信只要懂得了其中的原理,那么无论是几成几,做的过程大概的思路都是一样滴~

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

using namespace cv;
using namespace std;

#define filter_size 3
#define Filter_Size 5

int main() {
	// 5*5模板
	
	Mat image = imread("C:\\Users\\Czhannb\\Desktop\\gray.png", IMREAD_GRAYSCALE);
	Mat noise = Mat::zeros(Size(image.cols, image.rows), image.type());
	imshow("原图", image);
	imshow("零噪声", noise);

	RNG rng;
	rng.fill(noise, RNG::NORMAL, 10, 30);
	imshow("噪声", noise);


	image = image + noise;
	namedWindow("原图加噪声", WINDOW_FREERATIO);
	imshow("原图加噪声", image);

	// 定义高斯核
	
	int Gau_filter[5][5] = { 1,1,2,1,1,1,2,4,2,1,2,4,8,4,2,1,2,4,2,1,1,1,2,1,1};
	
	
	// 均值滤波 - 图像边缘不做处理,即无padding
	// 前两个for循环遍历整张图
	Mat module_mean = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 4; i++) {
		for (int j = 0; j < image.cols - 4; j++) {

			int sum = 0;
			for (int row = 0; row < Filter_Size; row++) {
				for (int col = 0; col < Filter_Size; col++) {
					sum += image.at<uchar>(row + i, col + j);  //求每一个5*5小矩阵的像素之和
				}
			}
			//cout << sum1 << "   "<<sum2 <<"   "<< sum3;
			module_mean.at<uchar>(i + 1, j + 1) = sum / 25;     //把均值赋予新的像素矩阵
		}
	}
	imshow("均值滤波之后", module_mean);
	

	
	//中值滤波 - 图像边缘不做处理
	// 前两个for循环遍历整张图
	Mat module_mid = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 4; i++) {
		for (int j = 0; j < image.cols - 4; j++) {

			vector<int>vec;     // 创建容器来装每一轮的九个像素值
			for (int row = 0; row < Filter_Size; row++) {
				for (int col = 0; col < Filter_Size; col++) {
					vec.push_back(image.at<uchar>(row + i, col + j));    // 每一个像素追加到容器后面
				}
			}
			sort(vec.begin(), vec.end());     // 排序(升序降序都可以)
			module_mid.at<uchar>(i + 1, j + 1) = vec[12];     //第五个元素就是中间值
		}
	}
	imshow("中值滤波之后", module_mid);
	

	//高斯滤波 - 图像边缘不做处理
	// 前两个for循环遍历整张图
	Mat module_Gau = Mat::zeros(Size(image.cols, image.rows), image.type());
	for (int i = 0; i < image.rows - 4; i++) {
		for (int j = 0; j < image.cols - 4; j++) {
			int sum = 0;
			for (int row = 0; row < Filter_Size; row++) {
				for (int col = 0; col < Filter_Size; col++) {
					sum += image.at<uchar>(row + i, col + j) * Gau_filter[row][col];
				}
			}
			module_Gau.at<uchar>(i + 1, j + 1) = sum / 52;
		}
	}
	
	imshow("高斯滤波之后", module_Gau);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

    ②如下给出了5*5核完成的均值滤波中值滤波以及高斯滤波,效果可以说比3*3好了不是一点点。(仅从去噪角度而言)

20721a816e064392b8cb47e284c125f0.png

133b5a819c0b4fc3b4cde05938e01ea3.png

a0ddf17dcf134961a6b27450f665b9c0.png

补充:
-2的原因是用了3*3的模板,模板从左上角开始向右移以及向下移,都是只能够移动image.rows-3次(比如说图像只有四列的话,3*3的模板只能移4-3=1次)这样一来的话最右边两列和最下面两行是遍历不到的。每一次移动都会锁定3*3个像素,也就是接下来两个for循环的操作(针对每次遍历到的9个像素),进行累加求平均或者排序完之后得到一个值赋给3*3模板的中间位置(这个位置的表示是i+1,j+1)(比如说第一次遍历的得到的结果应该放在0+1,0+1也就是1,1处)。创建一张空白图片的原因是拿来存放上面的运算结果,如果直接在原图上面更改的话会越算越错。遍历完了之后,空白图片的最外面的边没作处理,像素值还是原来的0。

猜你喜欢

转载自blog.csdn.net/m0_64007201/article/details/127908663