opencv c++ 평균 필터를 활용한 노이즈 제거
- 최초 작성일: 2022년 1월 11일(화)
목차
[TOC]
목표
보통 노이즈하면 라디오 같은 전자 기기에서 지지직 거리는 소리로 알고 있다. 이와 유사하게 영상 처리에서는 노이즈를 주변과 비교했을 때 픽셀 값이 뜬금 없는 경우를 말한다. 원본 영상에 노이즈가 같이 섞이게 되면 영상의 선명도가 떨어질 것이다. 그렇기 때문에 영상 처리에서 노이즈 제거는 아주 중요한 작업이라고 할 수 있다.
노이즈 제거를 위한 방법은 여러 가지가 있지만, 대표적으로 blur (흐릿함) 처리를 하는 것이 있다. blur 는 말 그대로 “영상을 흐릿하게 하다” 인데, opencv에 내장되어있는 filter2D라는 함수를 통해 처리할 수 있다.
이번에는, 평균 필터(Average Filter)를 이용하여 노이즈 제거를 해볼 것이다. 평균필터는 평균값을 이용해 필터링을 하는 작업인데, 이 작업에는 필터 연산에 포함될 범위를 나타내는 커널 혹은 마스크 크기가 필요하다.
아래의 이미지들이 평균 필터를 위한 계산 작업의 설명이다. 이 계산 작업을 반복하여 모든 행렬의 값들을 교체한다. 그러면 이미지의 픽셀들 중유독 튀던 값들이 주변값에 의해 부드러워(?)진다.
근데 그러면 하나의 의문점이 생길 것이다. 그러면 가장자리의 값들은 어떻게 계산할 수 있을까?
여러가지 방법이 있는데, 아래의 사진처럼 가장자리 값들을 무시하거나, 가장자지 밖의 값들을 모두 0으로 간주하거나, 가장자리 밖의 값들이 인접한 값을 따라가는 방법들이 있다.
이 방법들은 각자 상황이나 의도에 따라 다르게 쓰일 수 있다.
실습에 앞서, 우리가 쓰게 될 filter2D 함수를 간단히 살펴보자.
filter2D(src, dst, ddepth, kernel, Point anchor, delta, borderType)
- src: 입력 이미지
- dst: 출력 이미지
- ddepth: 목적 이미지의 깊이, 지원해주는 깊이 (음수면 src와 같음)
- kernel: 커널의 합성곱(convolution), float형의 matrix형 배열
- anchor: 커널이 가리키는 상대적 위치의 포인트, 즉 커널의 초점. (-1, -1)이면 커널의 한 가운데로 잡힘.
- delta: 결과 이미지에 추가적인 가중치
- borderType: 픽셀의 보외법, 외삼법. (참조링크)
여기서, 커널의 개념이 중요한데 커널은 마스크 크기와 연산 결과를 포함하는 행렬이다.
그렇기 때문에, mask 크기가 (3, 3)이라면 평균필터 커널은 아래의 이미지와 같아야 한다.
이걸 코드로 나타내면,
Mat kernel = Mat::ones(3, 3, CV_32F) / 9; 가 된다.
그러면 추가로 이헤를 돕기 위해,
mask 크기가 (5, 5)인 평균필터 커널식은
Mat kernel = Mat::ones(5, 5, CV_32F) /25;
mask 크기가 (5, 7)인 평균필터 커널식은
Mat kernel = Mat::ones(4, 7, CV_32F) / 35; 가 된다.
실습
자 이제 평균 필터가 어떤 원리로 작동하는지는 알았으니, 직접 코드를 돌려보며 결과를 비교해보자.
평균 필터에도 여러 종류가 있다고 말했듯이
이미지 테두리 밖의 픽셀 처리방법인 ‘border type’을 다르게 설정해주면 다른 결과가 나오는 것을 볼 수 있다.
소스 코드
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int ac, char** av) {
Mat img = imread("doctor.png", 0); //이미지를 grayscale로 불러옴
Mat img_average_constant;
Mat img_average_REPLICATE;
Mat img_average_REFLECT;
Mat img_average_REFLECT101;
Mat img_average_ISOLATED;
Mat avg_kernel = Mat::ones(5, 5, CV_32F) / 25; // mask 가 (5,5) 인 평균필터 커널
filter2D(img, img_average_constant, -1, avg_kernel, Point(-1, -1), (0, 0), BORDER_CONSTANT); // 0
filter2D(img, img_average_REPLICATE, -1, avg_kernel, Point(-1, -1), (0, 0), BORDER_REPLICATE); // 1
filter2D(img, img_average_REFLECT, -1, avg_kernel, Point(-1, -1), (0, 0), BORDER_REFLECT); // 2
filter2D(img, img_average_REFLECT101, -1, avg_kernel, Point(-1, -1), (0, 0), BORDER_REFLECT101); // 4
filter2D(img, img_average_ISOLATED, -1, avg_kernel, Point(-1, -1), (0, 0), BORDER_ISOLATED); // 16
// BORDER_CONSTANT //0
// BORDER_DEFAULT //4
// BORDER_ISOLATED //16
// BORDER_REFLECT //2
// BORDER_REFLECT101 //4
// BORDER_TRANSPARENT //5
// BORDER_REPLICATE //1
imshow("img_average_constant", img_average_constant);
imshow("img_average_REPLICATE", img_average_REPLICATE);
imshow("img_average_REFLECT", img_average_REFLECT);
imshow("img_average_REFLECT101", img_average_REFLECT101);
imshow("img_average_ISOLATED", img_average_ISOLATED);
waitKey(0);
return 0;
}
결과
솔직히 원본을 제외하곤 무슨 차이가 있는지 잘 안 보이지만 카메라 영상처리나 이미지 크기가 커지면 차이가 잘 보일 거라고 예상된다.