DevelopmentTool/OpenCV

[OpenCV] 필터링 개념과 filter2D함수

유제필 2022. 11. 18. 10:35

영상 처리에서 필터링(filtering)이란 원하는 정보만 통과시키고 원하지 않는 정보는 걸러 내는 작업을 뜻한다.

필터링에는 영상의 노이즈 성분을 걸러 영상을 깔끔하게 만드는 필터가 있고,

부드러운 느낌의 성분을 제거해 영상을 날카로운 느낌이 들도록 하는 필터가 있다.

영상의 필터링은 보통 마스크(mask)라고 부르는 작은 크기의 행렬을 이용한다.

마스크는 필터링의 성격을 정의하는 행렬이며, 커널(kernel), 윈도우(window) 라고도 부른다. 마스크 자체를 필터라고 부르기도 한다.

마스크는 다양한 크기와 모양으로 정의할 수 있으며, 마스크 행렬의 원소는 보통 실수로 구성된다.

1 x 3, 3 x 1 형태의 직사각형 행렬, 5 x 5, 3 x 3 등 형태의 정사각형 행렬, 십자가 모양 등

여러가지 모양의 필터 마스크를 사용할 수 있다.

위 이미지는 다양한 필터 마스크를 나타내는 이미지이다.

필터 마스크 중 진한 검정색으로 표시한 위치는 고정점(anchor point)을 나타낸다.

고정점이란 현재 필터링 작업을 수행하고 있는 기준 픽셀 위치를 나타내며,

대부분의 경우 마스크 행렬 정중앙을 고정점으로 사용한다.

여러 가지 모양의 필터 마스크 중 3x 3 정사각형 형태의 행렬이 가장 널리 사용되고 있다.

필터링 연산의 결과는 마스크 행렬의 모양과 원소 값에 의해 결정된다.

즉, 마스크 행렬을 어떻게 정의하는가에 따라 영상의 분위기를 정할 수 있다.

또는 영상에서 노이즈 성분을 제거하거나 에지(edge) 성분만 나타나도록 할 수 있다.

마스크를 이용한 필터링은 입력 영상의 모든 픽셀 위로 마스크 행렬을 이동시키면서 마스크 연산을 수행하는 방식이다.

마스크 연산이란 마스크 행렬의 모든 원소에 대하여 마스크 행렬 원소 값과 같은 위치에 있는 입력 영상 픽셀 값을 서로

곱한 후에 그 결과를 모두 더하는 연산이다.

마스크 연산의 결과를 출력 영상에서 고정점 위치에 대응되는 픽셀 값에 설정한다.

필터링 결과 영상의 픽셀 값은 다음과 같이 계산한다.

(x, y) 좌표에서 마스크 연산을 통해 결과 영상의 픽셀 값g(x, y)를 구한 후,

마스크를 한 픽셀 옆으로 이동하여 이동한 좌표에서 다시 마스크 연산을 수행하고,

전체 영상의 픽셀에 대해 수행하면 필터링이 완료된다.

OpenCV에서 필터 마스크를 사용하는 일반적인 필터링은 filter2D() 함수를 이용한다.

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 : 가장자리 픽셀 확장 방식

 

filter2D() 함수는 src 영상에 kernel 필터를 이용하여 필터링을 수행하고 결과를 dst에 저장한다.

src 인자와 dst 인자에 같은 변수를 지정하면 필터링 결과를 입력 영상에 덮어쓰게 된다.

anchor, delta, borderType 인자는 기본 값을 가지고 있기 때문에 생략할 수 있다.

영상의 필터링을 수행할 때, 영상의 가장자리 픽셀을 확장하여 영상 바깥쪽에 가상의 픽셀을 만든다.

이때 영상의 바깥쪽 가상의 픽셀 값에 따라 필터링 연산 결과가 달라진다.

즉, borderType 인자에 따라 연산 결과가 달라진다.

borderType에는 BorderTypes 열거형 상수를 사용할 수 있다.

BorderTypes 열거형 상수
설명
BorderTypes.Constant
고정 값으로 픽셀을 확장
BorderTypes.Replicate
테두리 픽셀을 복사해서 확장
BorderTypes.Reflect
픽셀을 반사해서 확장
BorderTypes.Wrap
반대쪽 픽셀을 복사해서 확장
BorderTypes.Reflect101
이중 픽셀을 만들지 않고 반사해서 확장
BorderTypes.Default
Reflect101 방식을 사용
BorderTypes.Transparent
픽셀을 투명하게 해서 확장
BorderTypes.Isolated
관심 영역(ROI) 밖은 고려하지 않음

filter2D() 함수가 수행하는 연산을 수식으로 표현할 수 있다.

 

예제 코드

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat src = imread("C:/opencv/ch02/Project1/lenna.bmp", IMREAD_GRAYSCALE);
	imshow("src", src);

	Mat Constant, Replicate, Reflect, Wrap, Reflect101, Default, Transparent, Isolated;
	Mat Kernel = Mat::ones(3, 3, CV_32F) / 5;

	filter2D(src1, Constant, -1, Kernel, Point(-1, -1), (0, 0), BORDER_CONSTANT);
	filter2D(src1, Replicate, -1, Kernel, Point(-1, -1), BORDER_REPLICATE);
	filter2D(src1, Reflect, -1, Kernel, Point(-1, -1), BORDER_REFLECT);
	filter2D(src1, Wrap, -1, Kernel, Point(-1, -1), BORDER_WRAP);
	filter2D(src1, Reflect101, -1, Kernel, Point(-1, -1), BORDER_REFLECT101);
	filter2D(src1, Default, -1, Kernel, Point(-1, -1), BORDER_DEFAULT);
	filter2D(src1, Transparent, -1, Kernel, Point(-1, -1), BORDER_TRANSPARENT);
	filter2D(src1, Isolated, -1, Kernel, Point(-1, -1), BORDER_ISOLATED);

	imshow("Constant", Constant);
	imshow("Replicate", Replicate);
	imshow("Reflect", Reflect);
	imshow("Wrap", Wrap);
	imshow("Reflect101", Reflect101);
	imshow("Default", Default);
	imshow("Transparent", Transparent);
	imshow("Isolated", Isolated);

	waitKey();
}

 

Mat 객체 src1에 레나 영상을 저장하고 filter2D() 함수를 이용하여 필터링 작업을 수행한다.

 

같은 영상의 같은 크기, 같은 타입의 영상이지만, 가장자리 픽셀 확장 방식에 따라 차이가 있다.

엠보싱 필터링

엠보싱(embossing)이란 직물이나 종이, 금속판 등에 올록볼록한 형태로 만든 객체의 윤곽 또는 무늬를 뜻한다.

엠보싱 필터는 입력 영상을 엠보싱 느낌이 나도록 변환하는 필터이다.

 

 

간단한 형태의 3 x 3 엠보싱 필터 마스크이다.

대각선 방향으로 +1, -1의 값이 지정되어 있다. 이 필터 마스크를 사용하여 필터링을 수행하면 대각선 방향으로 픽셀 값이 급격하게

변하는 부분에서 결과 영상 픽셀 값이 0보다 훨씬 크거나 0보다 훨씬 작은 값을 가지게 된다.

입력 영상에서 값이 크게 바뀌지 않는 평탄한 영역에서는 결과 영상의 픽셀 값이 0에 가까운 값을 가진다.

음수 값은 모두 포화 연산에 의해 0이 되어 버려 입체감이 크게 줄어들게 된다.

그래서 엠보싱 필터를 구현할 때 결과 영상에 128의 값을 더하는 것이 보기에 좋다.

예제 코드

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat src = imread("C:/opencv/ch02/Project1/lenna.bmp", IMREAD_GRAYSCALE);

	float data[] = { -1, -1, 0, -1, 0, 1, 0, 1, 1 }; 
	
	Mat embosssing(3, 3, CV_32FC1, data);
	

	Mat dst, dst1;
	filter2D(src, dst, -1, embosssing, Point(-1, -1), 128);
	filter2D(src, dst1, -1, embosssing, Point(-1, -1), 0);
	

	imshow("src", src);
	imshow("dst", dst);
	imshow("dst1", dst1);
	
	waitKey();
}