C++ hand-knocking grayscale image mean filtering median filtering Gaussian filtering

1. The concept of Meaning Filtering

    Mean filtering is a typical linear filtering algorithm, which refers to giving a template to the target pixel on the image, which includes the surrounding adjacent pixels (8 pixels around the target pixel as the center constitute a filtering template, that is, including The target pixel itself), and then replace the original pixel value with the average value of all pixels in the template.

f9fb918225b3482ea03ca1b51df7aa96.png

Code:

①Introduce the header file first, and declare the size of the core as 3*3

#include<opencv2/opencv.hpp>

#define filter_size 3

using namespace cv;

 ② Import the original image and generate Gaussian noise. Displays the original image , the generated noise image , and an overlay of the noise noise and the original image

Note that the size of the noise image remains the same as the original image. Among them , I set the window with noise added to the original image so that the size can be changed randomly to facilitate the comparison of the results later.

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);

  Original image: Noise:
24b9b6e8ceab4a228922e6ae941843bd.png77bacae91fa14c60bf1461380d5ae81b.png

 Put it all together:
97f2f6f261e84ce9b8587fd221b193e8.png

③ After obtaining the picture to be processed, process the picture according to the mean filtering principle.

First create a blank image with the same dimensions as the original image.

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

Since the size of the kernel is 3*3, that is to say, the kernel can only move image.cols-3 times to the right and image.rows-3 times down.

For each pass, the mean of nine pixels is substituted for the pixel in the center of the kernel. In this way, if the image is not processed (Padding, etc.), the pixel value of the outermost layer of the image will not change. But for our pictures composed of hundreds of rows and hundreds of columns of pixels, this can be ignored.

We first use two for loops to traverse to each pixel (the two rightmost columns and the bottom two rows cannot be reached).

40b8fad43b354c68bba8cae7a42fa17c.png

After traversing to the pixels that can be reached, calculate the average value of the nine pixels, and then assign it to the middle pixel. code show as below:

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;     //把均值赋予空白图片对应的像素
		}
	}

The last line of code in the figure: assign the average value of the pixel point to the position of the central pixel , which is (i + 1, j + 1). This is a relative coordinate. When i=j=0, that is, the first round of traversal, the center of the pixel appears at (1,1). The latter is also handled in the same way.

The function of the two for loops inside is to calculate the sum of 3*3 pixels in each traversal.

That's it! You're done! Let's look at the effect of mean filtered sum.

④Comparison of results

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

c954498db0884843b2f2bd902a171913.png

 It can be seen that the effect is okay. . (The mean filter has a blurring effect, and the screenshot gives another layer of buff, so everyone will do it!)

After talking about mean filtering, let's talk about median filtering.

2. Median filtering

The median filtering method is a non-linear smoothing technique, which sets the gray value of each pixel to the median value of the gray values ​​of all pixels in a certain neighborhood window of the point .

Median filtering is a nonlinear signal processing technology that can effectively suppress noise based on sorting statistics theory. The basic principle of median filtering is to use the value of a point in a digital image or digital sequence with the value of each point in a neighborhood of the point Instead of the median value of , the surrounding pixel values ​​are close to the real value, thereby eliminating isolated noise points. The method is to use a two-dimensional sliding template of a certain structure to sort the pixels in the board according to the size of the pixel value, and generate a monotonically rising (or falling) two-dimensional data sequence. The output of two- dimensional median filtering is g(x,y)=med{f(xk,yl),(k,l∈W)}, where f(x,y) and g(x,y) are the original images and processed images. W is a two-dimensional template, usually 3*3, 5*5 area, and it can also be in different shapes, such as line, circle, cross, ring, etc.

Code:

①Similar to the mean filter, we first generate a blank image (all black, with the same shape and size as the original image)

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

②Similarly, we also need to use two for loops to traverse the reachable pixels. Then, unlike the mean filter, what we have to do is not to add and average the pixel values ​​​​obtained by the next two for loop traversals, but to put them into a vector container. The reason for this is that vector has its own sorting function, so that you can be lazy and don't need to sort the array. And the median pixel we want appears in 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];     //第五个元素就是中间值
		}
	}

After each round of traversal gets the median pixel vec[4], assign the pixel corresponding to the module_mid blank image, and you're done.

③Comparison of results

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

807f306160b84a1f90871c42d4fab4d4.png

 It can be seen that the effect is also quite good. .

3. Gaussian filtering

    Gaussian filtering is a linear smoothing filter, which is suitable for eliminating Gaussian noise and is widely used in the noise reduction process of image processing. [1] In layman's terms, Gaussian filtering is a process of weighted average of the entire image , and the value of each pixel is obtained by weighted average of itself and other pixel values ​​in the neighborhood. The specific operation of Gaussian filtering is: use a template (or convolution, mask) to scan each pixel in the image, and use the weighted average gray value of the pixels in the neighborhood determined by the template to replace the value of the pixel in the center of the template.

The 3*3 Gaussian kernel is as follows:
df13c2aa5f9d4a7db1a63f0672bd267e.png

Code:

① First create a two-dimensional array to act as a Gaussian kernel (the closer to the center pixel, the greater the weight), and then multiply the value of the Gaussian kernel obtained by indexing with the pixels of the original image. The obtained results can be assigned to the center pixel after averaging.

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

② Consistent with the above two filtering methods, a blank image is first generated here.

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

Then traverse through two for loops to create an integer variable sum to record the results of each round of weighted summation.

Note in the nested two for loops: the traversal of the original image is recorded as (row+i, col+j), and the traversal of the Gaussian kernel is recorded as [row][col].

In this way, the same Gaussian kernel can be used to process the nine pixels of each traversal.

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 is the sum of the values ​​of the Gaussian kernel.

③Comparison of results

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

044c2e4403d74b95b6342986aa611b87.png

The effect is also okay.

Four, 3*3 kernel mean filter median filter Gaussian filter code summary

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

 5. Expansion

    ①Since there are 3*3, then there will be 5*5, 6*6, etc., but the bigger the core, the better. As the saying goes, there is a limit for everything. I also prepared an extra 5*5 core mean filter median filter kernel Gaussian filter code as follows. Of course, we should pay attention to the boundary problem, the traversal problem of the kernel and the picture, the size replacement of the Gaussian kernel and so on. But I believe that as long as you understand the principle, then no matter how much or how much, the general idea of ​​the process is the same~

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

    ② The average filter, median filter and Gaussian filter completed by 5*5 cores are given as follows. The effect can be said to be better than 3*3. (Only from the perspective of denoising)

20721a816e064392b8cb47e284c125f0.png

133b5a819c0b4fc3b4cde05938e01ea3.png

a0ddf17dcf134961a6b27450f665b9c0.png

Supplement:
The reason for -2 is that a 3*3 template is used. The template moves to the right and down from the upper left corner, and it can only move image.rows-3 times (for example, if the image has only four columns, 3* The template of 3 can only be moved 4-3=1 time) In this way, the rightmost two columns and the bottom two rows cannot be traversed. Each movement will lock 3*3 pixels, which is the operation of the next two for loops (for the 9 pixels traversed each time), accumulate and average or get a value after sorting and assign it to the 3*3 template The middle position (the representation of this position is i+1, j+1) (for example, the result of the first traversal should be placed at 0+1, 0+1 is 1, 1). The reason for creating a blank image is to store the calculation results above. If you change it directly on the original image, the calculation will become more and more wrong. After the traversal, the outermost edge of the blank image is not processed, and the pixel value is still the original 0.

Guess you like

Origin blog.csdn.net/m0_64007201/article/details/127908663