opencv 라이브러리를 사용하여 c++언어로 가우시안 필터를 구현해보았다.
int myKernelCon9x9(uchar* arr, int kernel[][9], int x, int y, int width, int height) //convolution
{
int sum = 0;
int sumKernel = 0;
for (int j = -1; j <= 7; j++)
{
for (int i = -1; i <= 7; i++)
{
if ((y + j) >= 0 && (y + j) < height && (x + i) >= 0 && (x + i) < width)
{
sum += arr[(y + j) * width + (x + i)] * kernel[i + 1][j + 1];
sumKernel += kernel[i + 1][j + 1];
}
}
}
if (sumKernel != 0) { return sum / sumKernel; } //합이 1로 정규화되도록 해 영상의 밝기 변화 방지
else { return sum; }
}
가우시안 필터에서는 convolusion이라는 연산 과정이 필요하다. 따라서 다음과 같이 9x9 행렬에서 convolution연산을 수행해주는 함수를 생성하였다.
Mat myGaussianfilter(Mat srcImg)
{
int width = srcImg.cols;
int height = srcImg.rows;
int kernel[9][9] = { 0,1,1,2,2,2,1,1,0,
1,2,4,5,5,5,4,2,1,
1,4,5,3,0,3,5,4,1,
2,5,3,12,24,12,3,5,2,
2,5,0,24,40,24,0,5,2,
2,5,3,12,24,12,3,5,2,
1,4,5,3,0,3,5,4,1,
1,2,4,5,5,5,4,2,1,
0,1,1,2,2,2,1,1,0
};
Mat dstImg(srcImg.size(), CV_8UC1);
uchar* srcData = srcImg.data;
uchar* dstData = dstImg.data;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
dstData[y * width + x] = myKernelCon9x9(srcData, kernel, x, y, width, height);
}
}
return dstImg;
}
다음 행렬은 각 행렬마다 가중치를 주는 정규분포를 따르는 가우시안 분포에 해당하는 행렬이다. 저 행렬과 내가 가우시안 필터링을 하고 싶은 이미지를 convolution해주면 가우시안 필터가 씌워진 이미지가 만들어진다. 가우시안 필터는 고주파 영역을 뭉개뜨리는 역할을 하기 때문에 이미지를 처리하면 좀 흐려진 이미지가 생성될 것이다. 가우시안 매트릭스는 가운데가 볼록하고 가장자리로 갈수록 퍼지는 형태로 좀 더 극적인 가우시안 필터링을 수행하고 싶다면 최고점과 최하점의 차이를 크게 하면 된다.
Mat GetHistogram(Mat& src)
{
Mat histogram;
const int* channel_numbers = { 0 };
float channel_range[] = { 0.0, 255.0 };
const float* channel_ranges = channel_range;
int number_bins = 255;
//히스토그램 계산
calcHist(&src, 1, channel_numbers, Mat(), histogram, 1, &number_bins, &channel_ranges);
//히스토그램 plot
int hist_w = 500;
int hist_h = 1000;
int bin_w = cvRound((double)hist_w / number_bins);
Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));
normalize(histogram, histogram, 0, histImage.rows, NORM_MINMAX, -1, Mat());//정규화
for (int i = 1; i < number_bins; i++)
{
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(histogram.at<float>(i - 1))),
Point(bin_w * (i - 1), hist_h - cvRound(histogram.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0); // 값과 값을 잇는 선을 그리는 방식으로 plot
}
return histImage;
}
가우시안 필터링을 통해 생성된 이미지와 원본을 비교하기 위해 생성한 히스토그램 생성 함수이다. 밝기에 따른 픽셀 값을 그래프로 보여준다.
int main()
{
Mat gear = imread("gear.jpg", 0); // 원본 이미지
Mat colorgear = imread("gear.jpg", 1); // 컬러
imshow("gear", gear); // 원본 이미지
Mat bgear = GetHistogram(gear); // 가우시안 전 히스토그램
imshow("before gaussian", bgear); // 가우시안 전 히스토그램
Mat blurgear = myGaussianfilter(gear); // 가우시안 처리
imshow("gear1", blurgear); // 가우시안 후 이미지
Mat agear = GetHistogram(blurgear); // 가우시안 후 이미지
imshow("after gaussian", agear); // 가우시안 후 이미지 보여주기
SpreadSalts_blue(gear, 100); // 점 찍기
imshow("salt gear", gear); // 점찍은 이미지 보여주기
Mat saltblurgear = myGaussianfilter(gear); // 점 찍은 거 블러 처리하기 (가우시안)
imshow("salt after gaussian", saltblurgear); // 보여주기
Mat sobelgear = mySobelFilter(gear); // 45 135도 소벨 필터
imshow("sobel", sobelgear); // 처리 값
vector <Mat> gau = myGaussianPyramid(gear);
imshow("2nd", gau[2]);
imshow("1st", gau[1]);
imshow("0st", gau[0]);
imshow("colorgear", colorgear);
waitKey(0);
}
메인 함수는 다음과 같다. 다른 sobel filter와 같은 것은 다음 포스팅에서 설명하겠다.
히스토그램을 출력해 비교하면 다음과 같다.
1. 원본
2. 가우시안 필터 처리 후
원본에 비해 고주파 성분이 많이 뭉개져 전체적으로 좀 흐린 이미지를 얻을 수 있다.
1. 원본 이미지의 히스토그램
2. 가우시안 필터를 적용한 이미지의 히스토그램
이것은 가우시안 필터를 적용한 이미지의 히스토그램이다. 히스토그램의 차이를 확인해보면 가우 시안 블러를 처리한 후의 히스토그램 또한 부드럽게 처리가 되어 있는 것을 볼 수 있다. 블러 처 리를 했기 때문에 픽셀 값이 discrete하지 않고 연속적으로 된 것이다.
자 그럼 가우시안 필터는 왜 사용하는 것일까?
영상과 이미지에서 노이즈란 아주 극적인 고주파에 해당하는 경우가 많다. 원본 이미지와 동떨어져 생뚱맞은 값을 가지기 때문이다. 따라서 가우시안 필터를 적용하면 이런 outlier들을 제거하여 노이즈를 줄일 수 있다.
앞선 포스팅에서 적용했던 salt pepper noise를 이미지에 적용하면 다음과 같다.
1. salt pepper noise를 적용한 이미지
중간 중간 하얀점이 보이는 것을 확인할 수 있다.
2. noise가 존재하는 이미지에 가우시안 필터를 씌운다.
가우시안 필터를 씌우자 salt pepper noise가 보이지 않는 것을 확인할 수 있었다. 따라서 가우시안 블러는 노이즈 제거에 탁월하다는 것을 알 수 있다.
'open-cv 영상처리' 카테고리의 다른 글
opencv C++ BandPass Filter (2) | 2022.03.24 |
---|---|
opencv c++ sobel filter (0) | 2022.03.23 |
opencv 사진 합성 (0) | 2021.06.18 |
opencv spread salt (0) | 2021.06.18 |
댓글