본문 바로가기

Computer Vision by OpenCV

OpenCV Noise제거하기, Median filtering

반응형

Median Filtering을 이용한 잡음제거 (Noise Removal)


잡음을 없애기 위해 local averaging, gaussian smoothing 등을 사용해 보았지만 별로 신통치 않았다.

여기서는 기적처럼 작동하는 방법을 한 가지 소개해 본다. 물론 완벽하게 원래 영상을 복원하지는 못하지만, 최소한 보기 싫은 잡음들은 없앨 수 있다. 다만 사진의 sharpness (선 같은 것들이 명료하게 보이는 정도)가 훼손되기는 한다. 방법에 대해 설명하기 전에 우선 기적같은 결과부터 확인해보자.


그림 1과 2는 원본 영상과 salt and pepper 잡음으로 오염된 영상을 각각 보여준다.

그리고 그림 3은 기적같은 결과를 보여준다. Noise가 싹 다 사라졌다. 그리고 얼핏 보기에 원래 영상처럼 보인다. 하지만 자세히 들여다 보면 에펠탑의 선 같은 것들이 뭉개진 것을 알 수 있다. 


<그림 1> 원본 이미지


<그림 2> Salt & Pepper 잡음이 추가된 영상


<그림 3> Median filtering을 실시한 결과


Median Filtering

이것을 가능하게 한 방법은 주변 픽셀들의 값과 자신의 값들을 크기에 따라 정렬하고 중간값(median)을 선택해서 자신의 픽셀값으로 하는 것이다. 그래서 median filtering이라 불리운다. Noise는 주변 픽셀들과 차이가 많이 나는 값을 가지고 있으므로 local averaging 같이 단순 평균을 구하게 되면, noise에 의해 값이 왜곡되는 정도가 커서 제대로 noise 제거가 되지 않는다. 하지만, 중간값은 주변 픽셀들과 제일 유사한 값이 되기 때문에 noise를 없앨 수 있다. 대신, sharp한 선이나 edge등은 뭉개져 버리는 단점이 있다. OpenCV에서는 meidan filtering을 지원해주는 함수 medianBlur( )를 제공하고 있다.


<소스코드 1> Meidan filtering을 수행하는 코드

line 57-58이 median filtering에 해당하는 부분이고, 주변 픽셀의 범위를 3x3으로 하고 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

/*
img의 전체 픽셀중 noise_ratio 퍼센트 만큼의
픽셀을 salt & pepper noise로 바꾼다.
*/
void addSaltAndPepperNoise(Mat& img, double noise_ratio)
{
	int rows = img.rows;
	int cols = img.cols;
	int ch = img.channels();
	int num_of_noise_pixels = (int)((double)(rows * cols * ch)*noise_ratio);

	for (int i = 0; i < num_of_noise_pixels; i++)
	{
		int r = rand() % rows;  // noise로 바꿀 행을 임의로 선택
		int c = rand() % cols;  // noise로 바꿀 열을 임의로 선택
		int _ch = rand() % ch;  // noise로 바꿀 채널의 임의로 선택

		// img.ptr<uchar>(r)은 r번째 행의 첫번째 픽셀, 첫번째 채널에 대한 주소값을 반환한다.
		uchar* pixel = img.ptr<uchar>(r) +(c*ch) + _ch; // noise로 바꿀 정확한 위치를 계산

		*pixel = (rand() % 2 == 1) ? 255 : 0; // black(0) 혹은 white(255)로 교체
	}
}

int main(int argc, char** argv)
{

	if (argc != 2)
	{
		cout << " Provide image name to read" << endl;
		return -1;
	}

	Mat inputImg;
	Mat spImg;

	inputImg = imread(argv[1], CV_LOAD_IMAGE_COLOR);
	resize(inputImg, inputImg, Size(), 0.25, 0.25, CV_INTER_AREA);

	spImg = inputImg.clone();
	addSaltAndPepperNoise(spImg, 0.05);

	Mat localAvgImg;
	// Noise 제거를 위해 3 by 3 매트릭스를 이용하여
	// local averaing을 시행.
	blur(spImg, localAvgImg, Size(5, 5));  

	Mat gaussianSmoothedImg;
	GaussianBlur(spImg, gaussianSmoothedImg, Size(5, 5), 1.5);

	Mat medianFilteredImg;
	medianBlur(spImg, medianFilteredImg, 3);

	namedWindow("Original", CV_WINDOW_AUTOSIZE);
	namedWindow("SaltAndPepper", CV_WINDOW_AUTOSIZE);
	namedWindow("LocalAveraging", CV_WINDOW_AUTOSIZE);
	namedWindow("GaussianSmoothing", CV_WINDOW_AUTOSIZE);
	namedWindow("MedianFiltered", CV_WINDOW_AUTOSIZE);

	moveWindow("Original", 100, 100);
	moveWindow("SaltAndPepper", 120, 120);
	moveWindow("LocalAveraging", 140, 140);
	moveWindow("GaussianSmoothing", 150, 150);
	moveWindow("MedianFiltered", 160, 160);

	imshow("Original", inputImg);
	imshow("SaltAndPepper", spImg);
	imshow("LocalAveraging", localAvgImg);
	imshow("GaussianSmoothing", gaussianSmoothedImg);
	imshow("MedianFiltered", medianFilteredImg);

	waitKey(0);
	return 0;
}














반응형