※ 1차 미분 마스크

1차 미분 마스크로는 로버츠 마스크, 프리윗 마스크, 소벨 마스크 등이 있다. 이런 마스크들은 엣지를 검출할 때 사용한다. 어떻게 해당 마스크들이 엣지를 검출할 수 있는 지 알아보자. 


에지는 화소의 밝기가 급격히 변하는 부분이다. 따라서 화소의 변화율을 취하는 미분 연산을 이용해서 에지를 검출 할 수 있다. 미분 연산은 즉, 기울기를 계산하는 것이기에 현재 화소에서 밝기의 기울기를 계산하면 된다. 하지만 디지털 영상은 연속이 아닌 이산된 데이터가 나열되어 있기 때문에 미분 연산을 할 수 없다. 그래서 다음과 같이 근사하여 계산한다. 



영상은 2차원이기 때문에 가로 방향, 세로 방향 각각 미분을 구해야한다. 그리고 최종적으로 영상에 나타낼 때는 각각의 제곱을 더한 것에 제곱근을 한다. 이러한 1차 미분 공식을 영상에 구현하는 쉬운 방법이 1차 미분 마스크로 회선을 적용하는 것이다. 위의 수식은 다음의 마스크와 같다. 

이렇게 회선의 수식을 이요해서 화소간 차분을 계산할 수 있도록 마스크의 원소를 구성하면 1차 미분 마스크가 된다. 이 마스크를 적용해서 입력 영상에 회선을 수행하면 에지 검출이 가능하다. 이 때 마스크 계수의 합은 0이 되어야 한다.


- 로버츠 마스크


로버츠 마스크는 대각선 방향으로 1, -1을 배치하여 구성된다. 나머지 원소의 값이 모두 0이어서 다른 1차 미분 마스크에 비해서 수행속도가 빠르다. 한번만 차분을 계산하기 때문에 차분의 크기가 작고 이로 인해서 경계가 확실한 에지만을 추출하지만 잡음에 매우 민감하고 에지의 강도가 약하다.


- 프리윗 마스크

로버츠 마스크와 달리 세 번의 차분을 합하기 때문에 에지의 강도가 강하며, 수직과 수평 에지를 동등하게 찾는 데 효과적이다. 수직과 수평 차분을 이용하기 때문에 전반적으로 대각선 방향보다는 수직, 수평 방향의 에지를 잘 검출한다.


- 소벨 마스크


에지 추출을 위한 가장 대표적인 1차 미분 연산자이다. 프리윗 마스크와 유사하지만 중심화소의 차분에 대한 비중을 2배로 키운 것이 특징이다. 해당 마스크는 수평, 수직 방향의 에지도 잘 추출하며 중심화소의 차분 비중을 높였기 때문에 대각선 방향의 에지도 잘 검출한다.


- 영상 처리 응용에서 필요한 에지들은 대부분 특정 방향의 에지들이다. 따라서 수평이나 수직으로 한쪽 방향의 에지만을 검출하고자 할 경우에는 두 개의 마스크를 같이 적용하지 않고 하나의 마스크만 적용할 수 있다.


※ 2차 미분 마스크

1차 미분 마스크는 밝기가 급격하게 변화하는 영역뿐 아니라 점진적으로 변화하는 부분까지 민감하게 에지를 검출하여 너무 많은 에지가 나타날 수 있다. 이를 보완하기 위한 방법으로 2차 미분 연산이 있다. 2차 미분 연산자는 변화하는 영역의 중심에 위치한 에지만을 검출하며, 밝기가 점진적으로 변화되는 영역에 대해서는 반응을 보이지 않는다.


- 라플라시안 에지 검출

대표적인 2차 미분 마스크로 라플라시안이 있다. 다음은 라플라시안의 수식을 나타낸다.



3*3 마스크를 예로 라플라시안 마스크 공식에 적용하면 중심화소를 4배로 하고 상하좌우 화소를 중심화소와 반대 부호를 갖게 구선한다. 또한, 마스크 원소의 전체 합은 0이 되어야 한다. 다음은 라플라시안 마스크의 예이다.

라플라시안은 중심 화소와 4방향 또는 8방향의 주변 화소와 차분을 합하여 에지를 검출하기 때문에 주변 화소에 잡음 성분이 있으면 잡음 성분에 매우 민감하여 실제보다 더 많은 에지를 검출하는 경향이 있다. 따라서 라플라시안 마스크를 적용하기 전에 잡음을 제거하면 좋다.

해당 포스트는 "OpenCV로 배우는 영상 처리 및 응용", "C++ API OpenCV 프로그래밍" 책의 내용을 요약한 것이다.



※ 2D 필터링 함수

- void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BOARDER_DEFAULT)

: src에 윈도우 kernel을 적용하여 회선 연산을 해 dst에 저장한다. ddepth는 dst의 깊이를 말하며 -1이라면 src와 같은 깊이를 가진다. anchor는 커널의 중심점을 말하며 디폴트 값이 Point(-1,-1)을 하면 내부적인 처리에 의해 자동적으로 커널의 중심을 가리킨다. delta는 필터링 결과에 더해지는 값이고, borderType은 경계값 처리방식이다. 디폴트 값은 BOARDER_DEFAULT로 BORDER_REFLECT101과 같다. 해당 함수를 통해서 kernel 값에 따라 잡음 제거, 블러딩(스무딩), 샤프닝이 가능하다.


- void sepFilter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernelX, InputArray kernelY, Point anchor=cPoint(-1,-1), double deltac=c0, int borderTypec=cBORDER_DEFAULT)

: sepFilter2D는 filter2D와 kernel을 제외하고 똑같다. 이 함수에서 커널의 의미를 예로써 설명하자면 filter2D 함수에서 모든 요소가 1/9인 3*3 마스크를 커널로 했다고 하자. 이는 sepFilter2D에서 요소가 1/3인 3*1행렬을 kernelX로 1*3 행렬을 kernelY로 한 것과 같다. 참고로 요소가 1/3인 행렬 3*1과 1*3을 곱하면 요소가 1/9인 3*3 행렬이 된다.



※ 스무딩 연산

- void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), bool normalize=true, int borderType=BORDER_DEFAULT)

: filter2D 함수와 비슷하다. 다만 kernel이 아닌 ksize를 매개변수로 받는다. boxFilter는 스무딩 연산을 위한 함수이기 때문에 마스크 모든 요소가 1이다. 다만, normalize가 true라면 마스크 값들이 커널 크기로 나누어져 평균 필터와 같게 된다. 


- void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)

: 양방향 필터를 수행하는 함수이다. 양방향 필터는 가우스안 함수 필터와 비슷하나 가우시안 함수 필터는 주변 값과 상관없는 에지에 대해서도 스무딩 연산을 해 에지를 없애버리기도 한다. 양방향 필터는 연산의 속도는 가우시안 함수 필터보다 더 걸리지만 에지를 보존하면서 스무딩 연산을 한다. d 값은 각 화소의 이웃을 결정할 지름 즉, 필터 크기이다. 만약 d<0이라면 sigmaSpace에 의해서 필터 크기가 결정되는 데 2*3*sigmaSpace+1로 계산한다. sigmaColor는 컬러 공간에서 필터 표준편차를 말한다. sigmaColor가 큰 값을 가지면 이웃 화소 내의 화소 중에서 색상 공간에서 멀리 떨어진 색상을 혼합하여 유사한 색상으로 뭉쳐서 큰 영역으로 만든다. 


- void medianBlur(InputArray src, OutputArray dst,int ksize)

: src 영상에 대해 ksize*ksize 크기의 마스크를 사용하여 중간값 필터를 한 결과를 dst에 저장한다. 중간값 필터에 관한 자세한 내용은 영상처리 카테고리 포스트에 있다.


- void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType = BORDER_DEFAULT)

: blur 함수는 위에 나와있는 boxFilter(src, dst, -1, ksize, anchor, true, borderType) 이랑 똑같다.


- Mat getGaussianKernel(int ksize, double sigma, int ktype=CV_64F)

: ksize*1 크기의 카우시안 마스크를 행렬로 반환한다. sigma는 가우시안 표준편차이며, sigma가 0보다 같거나 작을 경우 sigma= 0.3* ((ksize-1) * 0.5 -1) + 0.8로 계산한다. ktype은 마슼의 자료형으로 CV_32F 또는 CV_64F이다. 


- void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType = BORDER_DEFAULT)

: ksize 크기의 2차원 가우시안 마스크와 회선을 수행한다. sigmaX는 X-축 방향으로의 가우시안 커널 표준편차, sigmaY는 Y-축 방향으로의 커널 표준편차이다. sigmaX가 0이 아니고 sigmaY가 0일 경우 sigmaY는 sigmaX 값을 가진다. 둘 다 0일 경우 ksize.width와 ksize.height에 따라 자동적으로 설정된다. 2차원 가우시안 마스크 회선은 다음과 같이 구현할 수도 있다. getGaussianKernel 함수로 x축과 y축 방향 가우시안 커널을 얻고 sepFilter2D 함수로 가우시안 필터링 할 수 있다. 또한 두 가우시안 커널을 곱한 후 filter2D함수로 가우시안 필터링을 할 수 있다. 이에 대해선 다음 코드를 통해서 자세히 확인해보자.

#include "opencv2\opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
	uchar dataA[] = { 1, 2, 4, 5, 2, 1,
		3, 6, 6, 9, 0, 3,
		1, 8, 3, 7, 2, 5,
		2, 9, 8, 9, 9, 1,
		3, 9, 8, 8, 7, 2,
		4, 9, 9, 9, 9, 3 };
	Mat A(6, 6, CV_8U, dataA);

	int ksize = 3;
	double sigma = 0.0;
	Mat kx = getGaussianKernel(ksize, sigma);
	Mat ky = getGaussianKernel(ksize, sigma);

	Mat kxy = ky*kx.t();

	Mat dst1;
	sepFilter2D(A, dst1, -1, kx, ky);
	cout << "dst1 = " << dst1 << endl;

	Mat dst2;
	filter2D(A, dst2, -1, kxy);
	cout << "dst2 = " << dst2 << endl;

	Mat dst3;
	GaussianBlur(A, dst3, Size(ksize, ksize), 0.0, 0.0);
	cout << "dst3 = " << dst3 << endl;

	waitKey();
	return 0;
}

위 코드에서 dst1, dst2, dst3의 결과는 똑같이 나온다. 


+ Recent posts