C++ ハンドノック グレースケール画像の平均値フィルタリング メディアン フィルタリング ガウス フィルタリング

1. 意味フィルタリングの概念

    平均値フィルタリングは典型的な線形フィルタリング アルゴリズムであり、画像上の対象ピクセルに周囲の隣接ピクセルを含むテンプレートを与えることを指します (対象ピクセルを中心として周囲 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 回下にのみ移動できます。

各パスでは、9 つ​​のピクセルの平均がカーネルの中心のピクセルに置き換えられます。このように、画像にパディングなどの加工を施さなければ、画像の最外層の画素値は変化しません。しかし、数百の行と数百の列のピクセルで構成される画像の場合、これは無視できます。

まず、2 つの for ループを使用して各ピクセルを移動します (右端の 2 つの列と下の 2 行には到達できません)。

40b8fad43b354c68bba8cae7a42fa17c.png

到達可能なピクセルまで移動した後、9 つのピクセルの平均値を計算し、それを中央のピクセルに割り当てます。コードは以下のように表示されます。

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,1) に表示されます。後者も同様に扱われます。

内部の 2 つの for ループの機能は、各走査で 3*3 ピクセルの合計を計算することです。

以上です!完了です! 平均フィルタリング合計の効果を見てみましょう。

④結果の比較

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

c954498db0884843b2f2bd902a171913.png

 効果は問題ないことがわかります。(平均値フィルターにはぼかし効果があり、スクリーンショットにはさらなるバフ層が与えられています。誰もがそれを行うでしょう!)

平均値フィルタリングについて説明した後、メディアン フィルタリングについて説明します。

2.メディアンフィルタリング

メディアン フィルタリング手法は、各ピクセルグレー値を、点の特定の近傍ウィンドウ内のすべてのピクセルのグレー値の中央値に設定する非線形平滑化手法です

メディアンフィルタリングは、分類統計理論に基づいてノイズを効果的に抑制できる非線形信号処理テクノロジです。メディアン フィルタリングの基本原理は、デジタル画像またはデジタル シーケンス内の点の値を、近傍の各点の値とともに使用することです。点の中央値の代わりに、周囲のピクセル値が実際の値に近くなり、それによって孤立したノイズ点が除去されます。その方法は、ある構造の二次元スライドテンプレートを用いて、基板内の画素を画素値の大小に応じて並び替え、単調上昇(または下降)する二次元データ列を生成するというものである。2 次元メディアンフィルターの出力は g(x,y)=med{f(xk,yl),(k,l∈W)} です。ここで、f(x,y) と g(x,y) は次のようになります。元の画像と加工された画像。W は 2 次元のテンプレートで、通常は 3*3、5*5 の領域ですが、線、円、十字、リングなどのさまざまな形状にすることもできます。

コード:

①平均値フィルターと同様に、最初に空白の画像(元の画像と同じ形状、サイズの真っ黒)を生成します。

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

②同様に、到達可能なピクセルを横断するために 2 つの for ループを使用する必要があります。次に、平均値フィルターとは異なり、次の 2 つの for ループ走査で得られたピクセル値を加算して平均するのではなく、それらをベクトル コンテナーに入れる必要があります。その理由は、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

 効果もかなり高いことがわかります。

3. ガウスフィルタリング

    ガウス フィルタリングは、ガウス ノイズの除去に適した線形平滑化フィルタであり、画像処理のノイズ低減プロセスで広く使用されています。[1] 平たく言えば、ガウス フィルタリングは画像全体の加重平均の処理であり、各ピクセルの値はそれ自体と近傍の他のピクセル値の加重平均によって得られます。ガウス フィルタリングの具体的な操作は次のとおりです。テンプレート (または畳み込み、マスク) を使用して画像内の各ピクセルをスキャンし、テンプレートによって決定された近傍のピクセルの加重平均グレー値を使用して、画像内のピクセルの値を置き換えます。テンプレートの中心。

3*3 ガウス カーネルは次のとおりです。
df13c2aa5f9d4a7db1a63f0672bd267e.png

コード:

① まずガウス カーネルとなる 2 次元配列を作成し (中心ピクセルに近いほど重みが大きくなります)、インデックス付けして得られたガウス カーネルの値を元の画像のピクセルに乗算します。得られた結果は、平均化後に中心ピクセルに割り当てることができます。

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

② 上記の 2 つのフィルタリング方法と同様に、ここでは最初に空白の画像が生成されます。

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

次に、2 つの for ループをたどって整数変数の合計を作成し、加重合計の各ラウンドの結果を記録します。

ネストされた 2 つの for ループに注意してください。元のイメージの走査は (row+i,col+j) として記録され、ガウス カーネルの走査は [row][col] として記録されます。

このようにして、同じガウス カーネルを使用して、各トラバーサルの 9 ピクセルを処理できます。

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

エフェクトも大丈夫です。

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

 5. 拡張

    ①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回しか移動できません(たとえば、画像に4 列のみ、3* 3 のテンプレートは 4-3=1 回しか移動できません) このように、右端の 2 列と下 2 行は移動できません。各移動は 3*3 ピクセルをロックします。これは、次の 2 つの for ループ (毎回走査される 9 ピクセルの操作) の操作であり、累積して平均するか、ソート後に値を取得し、それを 3*3 テンプレートに割り当てます。 (この位置の表現は i+1, j+1 です) (たとえば、最初の走査の結果は 0+1 に配置される必要があり、0+1 は 1, 1 です)。空白の画像を作成するのは上記の計算結果を保存するためなので、元の画像に直接変更するとどんどん計算が狂ってしまいます。走査後、空白のイメージの最も外側のエッジは処理されず、ピクセル値は元の 0 のままです。

おすすめ

転載: blog.csdn.net/m0_64007201/article/details/127908663