본문 바로가기

영상처리/OpenCV 및 영상처리 이론

OpenCV 기초 - 8. 영상 필터링 (1) - 저주파 필터, 중간값 필터

 
영상 필터링에 대해서 알아보겠습니다.
 
영상처리에서 필터링 원치 않는 노이즈를 제거하거나 원하는 정보 ( feature )만 뽑아내 사용하는 형태로 구현됩니다. 이번 글에서는 영상 내의 노이즈를 제거하기 위한 필터링에 대해서 알아보겠습니다.
 
 
 

 

 

일반적으로 영상은 공간 영역에서 파란색 신호와 같이 저주파 신호의 형태를 보입니다. 경계나 모서리를 제외하면 밝기 값이 급격하게 변하지 않기 때문입니다. 반대로 노이즈 신호는 이전의 소금 후추 잡음에서 확인하였듯이 랜덤한 위치에 랜덤한 값을 가지는 밝기 값이 형성되기 때문에 상대적으로 고주파 신호의 성격을 가지게 됩니다. 이러한 특징을 이용하여 영상 내에 존재하는 고주파 신호 성분을 제거함으로써 영상의 노이즈를 제거할 수 있습니다.

 

 

 

 

 

 

- GaussianBlur

저주파 필터를 이용한 필터링에서 사용되는 대표적인 함수입니다. OpenCV에서 제공하는 함수 중 blur도 존재하는데 둘의 차이점은 blur의 경우 단순히 이웃 픽셀의 값을 보고 평균 값으로 값을 수정하지만 GaussianBlur의 경우 픽셀 거리에 따른 가중치를 설정하여 밝기값을 설정하게 됩니다. 이러한 필터를 박스 필터라고도 부르며 선형성을 가지는 특징이 있습니다. 또 이때 각 가중치에 해당하는 값을 getGaussianKernel을 통해 확인할 수 있습니다.

결과 영상을 통해 영상내 존재하는 엣지(고주파) 성분들이 상당부분 제거되어 흐릿해져 보이는 것을 확인할 수 있습니다. 하지만 영상에 존재하는 점잡음 형태의 노이즈는 잘 제거하지 못하는 것을 볼 수 있습니다. 이는 평균값을 취하는 필터의 성격상 커널내의 큰 데이터 값(노이즈)이 어느정도 영향을 끼치기 때문이라고 생각할 수 있습니다.

 

- medianBlur

이러한 평균값을 취하는 형태에서 발생하는 문제를 고려하여 만들어진 필터가 median filter이며 medianBlur라는 이름의 함수로 구현되어 있습니다. 동작 원리는 간단합니다. 커널 내의 픽셀들을 확인하고 이를 밝기 값 순으로 정렬하여 중간에 해당하는 밝기 값을 현재 픽셀에 넣어주는 방식으로 동작하게 됩니다. 이러한 필터 동작의 특성덕에 유별나게 크거나 작은 밝기 값을 가지는 소금-후추 노이즈에 아주 강한 모습을 보이게 됩니다.

 

 

※참고 : 저주파 필터는 영상의 사이즈를 줄이는 영상 감축에도 사용됩니다. 단순히 전체 영상의 행과 열에서 절반에 해당하는 사이즈로 번갈아가며 데이터를 남겨 사이즈를 줄이는 방법을 간단히 생각할 수 있지만 이러한 방법은 영상 내에 존재하는 비스듬한 형태의 엣지를 계단 모양으로 변경시킬 수 있습니다. 이러한 문제를 해결하기 위해 영상 사이즈를 줄이기 전에 고주파에 해당하는 성분들을 저주파 필터를 통해 어느정도 제거하고 감축을 수행하여 자연스러운 이미지를 얻어내는 방법을 사용하고 있습니다.

 

 

- 결과

 

 

저주파 필터를 적용하게 되면 smoothing된 결과 영상이 얻어지는 것을 확인할 수 있습니다. 이러한 특징을 이용하면 끊어진 부분들을 이어주는 효과를 볼 수 있다고 생각할수도 있습니다. (저주파 성분으로 구성된 1차원 신호에 갑작스럽게 튀는 노이즈가 발생하였을 때, 이러한 노이즈가 제거되기 때문에 원 신호를 이어주는 효과를 생각할 수 있음. ex) 스캔이미지의 text 끊어짐 현상을 제거하고 자연스럽게 하기위해 저주파 필터를 적용하여 스무딩)

 

- 코드 첨부

더보기
/*------------------------------------------------------------------------------------------*\This file contains material supporting chapter 6 of the cookbook:Computer Vision Programming using the OpenCV Library.by Robert Laganiere, Packt Publishing, 2011.
This program is free software; permission is hereby granted to use, copy, modify,and distribute this source code, or portions thereof, for any purpose, without fee,subject to the restriction that the copyright notice may not be removedor altered from any source or altered source distribution.The software is released on an as-is basis and without any warranties of any kind.In particular, the software is not guaranteed to be fault-tolerant or free from failure.The author disclaims all warranties with regard to this software, any use,and any consequent failure, is purely the responsibility of the user.
Copyright (C) 2010-2011 Robert Laganiere, www.laganiere.name\*------------------------------------------------------------------------------------------*/
#include <iostream>#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>
int main(){ // 입력 영상 읽기 cv::Mat image = cv::imread("robert/salted.bmp", 0); if (!image.data) return 0;
// 영상 띄워 보기 cv::namedWindow("Original Image"); cv::imshow("Original Image", image);
// 영상 부드럽게 하기 cv::Mat result; cv::GaussianBlur(image, result, cv::Size(5, 5), 1.5);
// 부드럽게 된 영상 띄워 보기 cv::namedWindow("Gaussian filtered Image"); cv::imshow("Gaussian filtered Image", result);
// 가우시안 커널(1.5) 가져오기 cv::Mat gauss = cv::getGaussianKernel(9, 1.5, CV_32F);
// 커널값 출력 cv::Mat_<float>::const_iterator it = gauss.begin<float>(); cv::Mat_<float>::const_iterator itend = gauss.end<float>(); std::cout << "["; for (; it != itend; ++it) { std::cout << *it << " "; } std::cout << "]" << std::endl;
// 가우시안 커널(0.5) 가져오기 gauss = cv::getGaussianKernel(9, 0.5, CV_32F);
// 커널값 출력 it = gauss.begin<float>(); itend = gauss.end<float>(); std::cout << "["; for (; it != itend; ++it) { std::cout << *it << " "; } std::cout << "]" << std::endl;
// 가우시안 커널(2.5) 가져오기 gauss = cv::getGaussianKernel(9, 2.5, CV_32F);
// 커널값 출력 it = gauss.begin<float>(); itend = gauss.end<float>(); std::cout << "["; for (; it != itend; ++it) { std::cout << *it << " "; } std::cout << "]" << std::endl;
// 데리브 커널(2.5) 가져오기 cv::Mat kx, ky; cv::getDerivKernels(kx, ky, 2, 2, 7, true);
// 커널값 출력 cv::Mat_<float>::const_iterator kit = kx.begin<float>(); cv::Mat_<float>::const_iterator kitend = kx.end<float>(); std::cout << "["; for (; kit != kitend; ++kit) { std::cout << *kit << " "; } std::cout << "]" << std::endl;
// 평균 필터로 영상을 부드럽게 하기 cv::blur(image, result, cv::Size(5, 5));
// 부드럽게 된 영상 띄워 보기 cv::namedWindow("Mean filtered Image"); cv::imshow("Mean filtered Image", result);
// 소금&후추 잡음이 있는 입력 영상 읽기 image = cv::imread("robert/salted.bmp", 0); if (!image.data) return 0;
// 소금&후추 영상 띄워 보기 cv::namedWindow("S&P Image"); cv::imshow("S&P Image", image);
// 평균 필터로 영상을 부드럽게 하기 cv::blur(image, result, cv::Size(5, 5));
// 부드럽게 된 영상 띄워 보기 cv::namedWindow("Mean filtered S&P Image"); cv::imshow("Mean filtered S&P Image", result);
// 중간값 필터 적용하기 cv::medianBlur(image, result, 5);
// 부드럽게 된 영상 띄워 보기 cv::namedWindow("Median filtered S&P Image"); cv::imshow("Median filtered S&P Image", result);
// 영상의 크기를 4배로 감축하기(잘못된 방법) image = cv::imread("robert/salted.bmp", 0); cv::Mat reduced(image.rows / 2, image.cols / 2, CV_8U);
for (int i = 0; i<reduced.rows; i++) for (int j = 0; j<reduced.cols; j++) reduced.at<uchar>(i, j) = image.at<uchar>(i * 2, j * 2);
// 감축한 영상을 띄워 보기 cv::namedWindow("Badly reduced Image"); cv::imshow("Badly reduced Image", reduced);
cv::waitKey(); return 0;

 

}