[OpenCV 4] 7. 필터링
일상생활에서도 널리 사용되는 필터(filter)라는 말은 무언가를 걸러 내고 일부만을 통과시키는 장치를 의미합니다 한다.
영상 처리에서 필터링(filtering)이란 영상에서 원하는 정보만 통과시키고 원치 않는 정보는 걸러 내는 작업이다. 예를 들어 영상에서 지저분한 잡음(noise)을 걸러 내어 영상을 깔끔하게 만드는 필터가 있고, 또는 부드러운 느낌의 성분을 제거함으로써 영상을 좀 더 날카로운 느낌이 나도록 만들 수도 있다.
영상의 필터링은 보통 마스크(mask)라고 부르는 작은 크기의 행렬을 이용한다. 마스크는 필터링의 성격을 정의하는 행렬이며 커널(kernel), 윈도우(window)라고도 부르며, 경우에 따라서는 마스크 자체를 필터라고 부르기도 한다.
마스크 행렬의 원소는 보통 실수로 구성된다.
마스크를 이용한 필터링 연산 방법을 그림으로 표현 했다.
OpenCV에서 필터 마스크를 사용하는 일반적인 필터링 함수 filter2D() 이다.
void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor = Point(-1,-1), double delta = 0, int borderType = BORDER_DEFAULT); |
|
• src |
입력 영상 |
• dst |
출력 영상. src와 같은 크기, 같은 채널 수를 갖습니다. |
• ddepth |
결과 영상의 깊이 |
• kernel |
필터링 커널. 1채널 실수형 행렬 |
• anchor |
고정점 좌표. Point(-1, -1)을 지정하면 커널 중심을 고정점으로 사용합니다. |
• delta |
필터링 연산 후 추가적으로 더할 값 |
• borderType |
가장자리 픽셀 확장 방식 |
엠보싱 필터링
엠보싱이란 직물이나 종이, 금속판 등에 올록볼록한 형태로 만든 객체의 윤곽 또는 무늬를 뜻하며, 엠보싱 필터는 입력 영상을 엠보싱 느낌이 나도록 변환하는 필터다.
위 그림의 마스크를 사용하여 필터링을 수행하면 대각선 방향으로 픽셀 값이 급격하게 변하는 부분에서 결과 영상 픽셀 값이 0보다 훨씬 크거나 또는 0보다 훨씬 작은 값을 가지게 된다. 입력 영상에서 픽셀 값이 크게 바뀌지 않는 평탄한 영역에서는 결과 영상의 픽셀 값이 0에 가까운 값을 가지게 된다. 이렇게 구한 결과 영상을 그대로 화면에 나타내면 음수 값은 모두 포화 연산에 의해 0이 되어 버리기 때문에 입체감이 크게 줄어들게 되는 엠보싱 효과를 볼 수 있다.
void filter_embossing()
{
Mat src = imread("image/rose.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
float data[] = { -1, -1, 0, -1, 0, 1, 0, 1, 1 };
Mat emboss(3, 3, CV_32FC1, data);
Mat dst;
filter2D(src, dst, -1, emboss, Point(-1, -1), 128);
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
data 배열을 만들고 이를 Mat으로 바꿔준다.
filter2D 함수를 이용해 필터링을 한다.
dst 영상을 보면 앞에서 설명했듯이 장미꽃 경계 부분이 입체감 있게 표현 되었다.
픽셀 값이 완만하게 바뀌는 부분에서는 필터링 결과 영상이 대체로 밝기 값 128에 가까운 회색으로 표현 되었다.
블러링 : 영상 부드럽게 하기
블러링(blurring)은 마치 초점이 맞지 않은 사진처럼 영상을 부드럽게 만드는 필터링 기법이며 스무딩(smoothing)이라고도 한다. 영상에서 인접한 픽셀 간의 픽셀 값 변화가 크지 않은 경우 부드러운 느낌을 받을 수 있다. 블러링은 거친 느낌의 입력 영상을 부드럽게 만드는 용도로 사용되기도 하고, 혹은 입력 영상에 존재하는 잡음의 영향을 제거하는 전처리 과정으로도 사용 된다.
평균값 필터
평균값 필터는 입력 영상에서 특정 픽셀과 주변 픽셀들의 산술 평균을 결과 영상 픽셀 값으로 설정하는 필터다. 평균값 필터에 의해 생성되는 결과 영상은 픽셀 값의 급격한 변화가 줄어들어 날카로운 에지가 무뎌지고 잡음의 영향이 크게 사라지는 효과가 있다. 그러나 평균값 필터를 너무 과도하게 사용할 경우 사물의 경계가 흐릿해지고 사물의 구분이 어려워질 수 있다.
이런식으로 1/N으로 모든 원소를 나눠준 행렬을 사용한다.
OpenCV에서는 blur() 함수를 이용하여 평균값 필터링을 수행할 수 있다.
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT); |
|
• src |
입력 영상. 다채널 영상은 각 채널별로 블러링을 수행합니다. 입력 영상의 깊이는 CV_8U, CV_16U, CV_16S, CV_32F, CV_64F 중 하나여야 합니다. |
• dst |
출력 영상. src와 같은 크기, 같은 채널 수를 갖습니다. |
• ksize |
블러링 커널 크기 |
• anchor |
고정점 좌표. Point(-1, -1)을 지정하면 커널 중심을 고정점으로 사용합니다. |
• borderType |
가장자리 픽셀 확장 방식 |
위 함수에서 사용하는 커널의 식은 다음과 같다.
평균값의 필터의 크기가 커질수록 결과 영상이 더욱 부드럽게 변경되는 것을 확인할 수 있다.
일반적으로 필터 마스크 행렬은 모든 원소 합이 1 또는 0이 되도록 설계한다. 필터 마스크 행렬의 원소 합이 1이면 필터링 결과 영상의 평균 밝기가 입력 영상 평균 밝기와 같게 유지된다. 입력 영상의 평균 밝기를 그대로 유지하려면 필터 마스크 행렬 원소의 합이 1이 되어야 한다.
가우시안 필터
가우시안 필터는 가우시안 분포(Gaussian distribution) 함수를 근사하여 생성한 필터 마스크를 사용하는 필터링 기법이다.
가우시안 분포는 평균을 중심으로 좌우 대칭의 종 모양(bell shape)을 갖는 확률 분포를 말하며 정규 분포(normal distribution)라고도 한다.
표준편차가 클수록 그래프가 넓게 퍼지면서 완만한 형태를 따른다. 즉 표준편차를 큰 가우시안 행렬을 사용하면
영상을 가우시안 필터 했을떄 마스크 행렬이 중앙부에 비교적 큰 값을 가지는데 이 부분이 줄어들면서 전체적으로 영상이 부드럽게 변경되는 것을 알 수 있다. 그래프의 개념과 같이 생각하면 좋다.
이런식으로 가우시안 마스크의 형태를 볼 수 있다. 표준편차 값에 따라 가운데에 값이 집중되는지 안되는지 알 수 있다.
OpenCV에서 가우시안 필터링을 수행하려면 GaussianBlur() 함수를 사용한다.
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT); |
|
• src |
입력 영상. 다채널 영상은 각 채널별로 블러링을 수행합니다. |
• dst |
출력 영상. src와 같은 크기, 같은 타입을 갖습니다. |
• ksize |
가우시안 커널 크기. ksize.width와 ksize.height는 0보다 큰 홀수이어야 합니다. ksize에 Size()를 지정하면 표준 편차로부터 커널 크기를 자동으로 결정합니다. |
• sigmaX |
x 방향으로의 가우시안 커널 표준 편차 |
• sigmaY |
y 방향으로의 가우시안 커널 표준 편차. 만약 sigmaY = 0이면 sigmaX와 같은 값을 사용합니다. 만약 sigmaX와 simgaY가 모두 0이면 ksize의 width와 height 값으로부터 표준 편차를 계산하여 사용합니다. |
• borderType |
가장자리 픽셀 확장 방식 |
샤프닝 : 영상 날카롭게 하기
샤프닝은 초점이 잘 맞은 사진처럼 사물의 윤곽이 뚜렷하고 선명한 느낌이 나도록 영상을 변경하는 필터링 기법이다.
블러링과 반대되는 개념이라고 생각하면 된다. 영상을 날카로운 느낌이 나도록 변경한다. 날카로운 느낌의 영상이란 초점이 잘 맞은 사진처럼 객체의 윤곽이 뚜렷하게 구분되는 영상을 의미한다.
에지 근방에 픽셀 값의 명암비가 커지도록 수정한다.
가로축은 픽셀 좌표의 이동을 나타내고 세로축은 픽셀 값을 나타낸다. a는 영상의 에지 부근에서 픽셀 값이 증가하는 모양이다. b에 실선은 블러링을 적용한 결과이다.
c는 입력 영상 f(x,y)에서 블러링된 영상을 뺀 결과이다.
d는 f(x,y)에 g(x,y)를 더한 에지가 강조된 함수이다. 샤프닝이 적용된 결과이다.
g(x,y)는 입력 여앗ㅇ에서 블러링된 영상을 뺀 결과이므로 g(x,y)는 입력 영상에 오직 날카로운 성분만 가지고 있는 함수이다.
가중치 알파를 곱해서 샤프닝을 진행 할 수 있다
여기서 식을 g(x,y)를 가우시안 필터된 영상f다시로 표현하면 아래 그림과 같다.
OpenCV는 언샤프 마스크 필터 함수를 따로 제공하지 않는다. 다만 위에 있는 수식을 그대로 소스 코드 형태로 작성하면 어렵지 않게 샤프닝 결과 영상을 얻을 수 있다. f(x,y) 는 입력 영상에 블러링이 적용된 영상이고 이 때 블러링 영상을 구하기 위해 평균값 필터나 가우시안 필터를 사용하면 된다.
void unsharp_mask()
{
Mat src = imread("rose.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
imshow("src", src);
for (int sigma = 1; sigma <= 5; sigma++) {
Mat blurred;
GaussianBlur(src, blurred, Size(), sigma);
float alpha = 1.f;
Mat dst = (1 + alpha) * src - alpha * blurred;
String desc = format("sigma: %d", sigma);
putText(dst, desc, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1.0,
Scalar(255), 1, LINE_AA);
imshow("dst", dst);
waitKey();
}
destroyAllWindows();
}
Mat dst = ( 1 + alpha ) * src - alpha * blurred;
앞의 for문에서 표준편차 값을 1~5까지 증가시키면서 언샤프 마스크 필터링을 수행한다.
dst는 위에서 말한 수식을 코드로 나타낸 식이다.
잡음 제거 필터링
영상을 획득하는 과정에서 항상 원치 않은 잡음이 포함될 수 있다. 컴퓨터 비전 시스템이 전처리 과정으로 잡음 제거 필터를 사용한다. 신호 처리 관점에서 잡음(noise)이란 원본 신호에 추가된 원치 않은 신호를 의미한다.
디지털 카메라에서 카메라 렌즈가 바라보는 장면을 원본 신호 s(x,y)라고 하고, 여기에 추가되는 잡음을 n(x,y)라고 표현한다면 실제로 카메라에서 획득되는 영상 신호 f(x,y)는 다음 그림과 같다.
잡음이 생성되는 방식을 잡음 모델(noise model)이라고 하며, 다양한 잡음 모델 중에서 가장 대표적인 잡음 모델은 가우시안 잡음 모델(Gaussian noise model)이다. 가우시안 잡음 모델은 보통 평균이 0인 가우시안 분포를 따르는 잡음을 의미한다.
평균이 0이고 표준편차가 10인 가우시안 그래프이다.
위 가우시안분포를 따르는 잡음 모델은 67%의 확률로 -10 ~ 10 사이의 값이 잡음으로 추가 된다.
그래프를 보면 표준 편차가 작은 가우시안 잡음 모델일수록 잡음에 의한 픽셀 값 변화가 적다고 생각 할 수 있다.
OpenCV 함수를 이용하여 영상에 가우시안 모델을 따르는 잡음을 인위적으로 추가할 수 있다. randn() 함수는 가우시안 잡음으로 구성된 행렬을 생성하여 반환한다.
void randn(InputOutputArray dst, InputArray mean, InputArray stddev); |
|
• dst |
가우시안 난수로 채워질 행렬. dst 행렬은 미리 할당이 되어 있어야 합니다. |
• mean |
가우시안 분포 평균 |
• stddev |
가우시안 분포 표준 편차 |
평균이 0인 가우시안 잡음을 생성할 경우 양수와 음수가 섞여 있는 난수가 발생하므로 CV_32S, CV_32F 처럼 부호가 있는 자료형 행렬을 사용 해야 한다.
void noise_gaussian()
{
Mat src = imread("image/lenna.bmp", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
imshow("src", src);
for (int stddev = 10; stddev <= 30; stddev += 10) {
Mat noise(src.size(), CV_32SC1);
randn(noise, 0, stddev);
Mat dst;
add(src, noise, dst, Mat(), CV_8U);
String desc = format("stddev = %d", stddev);
putText(dst, desc, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255), 1, LINE_AA);
imshow("dst", dst);
waitKey();
}
destroyAllWindows();
}
표준 편차가 커질수록 잡음의 영향이 커지고 결과 영상이 더욱 지저분해지는 것을 볼 수 있다.
양방향 필터
픽셀 값이 급격하게 변경되는 에지 근방에 동일한 가우시안 필터가 적용되면 잡음뿐만 아니라 에지 성분까지 함께 감소하게 된다. 즉, 잡음이 줄어들면서 함께 에지도 무뎌지기 때문에 객체의 윤곽이 흐릿하게 바뀐다.
이러한 단점을 보완하기 위해 많은 사람들이 에지 정보는 그대로 유지하면서 잡음만 제거하는 에지 보전 잡음 제거 필터(edge-preserving noise removal filter)에 대해 연구했다.
양방향 필터(bilateral filter)는 에지 성분은 그대로 유지하면서 가우시안 잡음을 효과적으로 제거하는 알고리즘이다.
양방향 필터는 다음 공식을 사용하여 필터링을 수행한다.
f는 입력, g는 출력 그리고 p와 q는 픽셀의 좌표를 나타낸다. fp와 fq는 각각 p점과 q점에서의 입력 영상 픽셀 값이고, gp는 p점에서의 출력 영상 픽셀 값이다. Gσs와 Gσr는 각각 표준 편차가 σs와 σr인 가우시안 분포 함수다. S는 필터 크기를 나타내고, Wp는 양방향 필터 마스크 합이 1이 되도록 만드는 정규화 상수이다.
복잡해보이지만 간단히 말하면 두 개의 가우시안 함수 곱으로 구성된 필터이다.
Gσs(||p-q||) 함수는 두 점 사이의 거리에 대한 가우시안 함수이다. Gσr(|fp-fq|) 함수는 두 점의 픽셀 값 차이에 의한 가우시안 함수이다. 두 점의 픽셀 밝기 값의 차이가 적은 평탄한 영역에서는 큰 가중치를 갖게 만든다. 반면에 에지를 사이에 두고 있는 두 픽셀에 대해서는 fp-fq 값이 크게 나타나므로 가우시안 값은 거의 0에 가까워진다.
이로 인해 에지 근방에서는 가우시안 블러링 효과가 거의 나타나지 않고 에지가 보존된다.
양방항 필터 수식이 픽셀 값의 차이에 의존적이기 때문에 양방향 필터 마스크는 영상의 모든 픽셀에서 서로 다른 형태를 갖게 되므로 모든 픽셀 위치에서 주변 픽셀과의 밝기 차이에 의한 고유의 필터 마스크 행렬을 만들어서 마스크 연산을 수행해야 한다.
OpenCV에서는 bilateralFilter() 함수를 이용하여 양방향 필터를 수행할 수 있다.
void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT); |
|
• src |
입력 영상. 8비트 또는 실수형, 1채널 또는 3채널 영상 |
• dst |
출력 영상. src와 같은 크기, 같은 타입을 갖습니다. |
• d |
필터링에 사용할 이웃 픽셀과의 거리(지름). 양수가 아닌 값(예를 들어 -1)을 지정하면 sigmaSpace로부터 자동 계산됩니다. |
• sigmaColor |
색 공간에서의 가우시안 필터 표준 편차 |
• sigmaSpace |
좌표 공간에서의 가우시안 필터 표준 편차 |
• borderType |
가장자리 픽셀 확장 방식 |
sigmaSpace 값은 일반적인 가우시안 필터링에서 사용하는 표준 편차와 같은 개념이다. 값이 클수록 더 많은 주변 픽셀을 고려하여 블러링을 수행한다.
sigmacolor 값을 작게 지정할 경우, 픽셀 값 차이가 큰 주변 픽셀과는 블러링이 적용되지 않는다. 즉 sigmaColor 값을 이용하여 어느 정도 밝기 차를 갖는 에지를 보존할 것인지를 조정할 수 있다.
미디언 필터
미디언 필터(median filter)는 입력 영상에서 자기 자신 픽셀과 주변 픽셀 값 중에서 중간값(median)을 선택하여 결과 영상 픽셀 값으로 설정하는 필터링 기법이다. 미디언 필터는 마스크 행렬과 입력 영상 픽셀 값을 서로 곱한 후 모두 더하는 형태의 연산을 사용하지 않는다.
주변 픽셀 값들의 중간값을 선택하기 위해 내부에서 픽셀 값 정렬 과정이 사용된다. 특히 잡음 픽셀 값이 주변 픽셀 값과 큰 차이가 있는 경우에 효과적으로 동작한다.
영상에 추가되는 잡음 중에 소금&후추 잡음(salt & pepper noise)은 픽셀 값이 일정 확률로 0 또는 255로 변경되는 형태의 잡음이다. 이 소금 후추 잡음에 미디언 필터를 적용하면 잡음이 효과적으로 제거된다.
3*3 정방형 마스크를 사용하는 미디언 필터 동작 방식이다. 일렬로 스트레치 해서 세운 후 픽셀 값 크기 순으로 ㅈ어렬한다. 중앙에 있는 픽셀 값을 선택하고 결과 영상의 픽셀값으로 설정한다.
OpenCV에서는 medianBlur() 함수를 이용하여 미디언 필터링을 수행할 수 있다.
void medianBlur(InputArray src, OutputArray dst, int ksize); |
|
• src |
입력 영상. 1, 3, 4채널 영상. ksize가 3 또는 5이면 src는 CV_8U, CV_16U, CV_32F의 깊이를 가질 수 있고, 그 이상의 필터 크기에서는 CV_8U 깊이만 사용할 수 있습니다. |
• dst |
출력 영상. src와 같은 크기, 같은 타입을 갖습니다. |
• ksize |
필터 크기. 3보다 같거나 큰 홀수를 지정합니다. |
medianBlur() 함수는 ksize×ksize 필터 크기를 이용하여 미디언 필터링을 수행한다. 다채널 영상인 경우 각 채널별로 필터링을 수행한다. 가장자리 외각 픽셀 값을 설정하여 필터리을 수행한다.
참고자료 : OpenCV 4로 배우는 컴퓨터 비전과 머신러닝