정말정말 오랜만에 티스토리 OpenCV 포스팅해요
영상에서 경계선을 검출하는 방법은 영상의 1차 미분 값을 이용하는 방법이 대표적이다.
미분이란 함수의 변화량을 의미한다.
미분을 적용한 그래프는 보통 y값을 고정한 상태에서 x값의 변화에 따른 함수의 변화량을 보여주게된다.
(출처 :http://kylog.tistory.com)
사진에서 볼 수 있듯, 이러한 함수의 변화는 경계선 부근에서 나타나는 현상이며, 실제 영상에서는 x값이 증가함에 따라 밝기가 급격하게 증가하는 현상으로 나타나기도 한다. 그러므로 영상에서 경계선을 검출하기 위해서는 영상을 미분한 후, 미분 값이 특정 임계값(Threshold)보다 큰 부분을 찾으면 된다.
참고로,
영상처리에서는 기울기를 그레디언트라고 한다.
그래서 자료를 찾다보면 그레디언트를 적용한 엣지추출이라는 자료들을 볼 수 있는데,
이는 모두 미분을 이용하여 엣지 추출을 한 것이다.
영상의 경계선(Edge) 즉, 엣지를 검출하기 위해서 다양한 경계선 검출 마스크가 존재한다. 다양한 마스크 중(이 외에도 로버츠 마스크 등이 있음)에서 OpenCV 기반으로 오늘은 Sobel Mask(소벨 마스크)와 Prewitt Mask(프리윗,프리위트 마스크)를 살펴 볼 것이다.
Sobel Mask(소벨 마스크)
소벨 마스크는 영상에서 윤곽선을 검출하는데 자주 쓰이는 마스크이며, 모든 방향의 윤곽선, 즉 엣지를 추출 할 수 있다. 엣지를 추출 할 때, 수직마스크와 수평마스크를 적용하여 엣지를 추출하는 과정을 거친다.
또한, 소벨 마스크는 잡음에 강한편이라 프리위트 마스크보다 더 자주 쓰인다.
마스크의 크기가 3 X 3으로 고정되는 것은 아니고, 5 X 5로 나타낼 수 도 있다. 하지만 여기서는 3 X 3 행렬로 살펴 볼 것이다.
(참고로, 마스크 행렬이 커질수록 엣지는 두꺼워져서 선명하게 나타나게 된다.)
아래는 소벨마스크의 형태이다.
다음 결과는 영상에 Sobel Mask(소벨 마스크)를 적용한 결과이다.
왼쪽 그림부터 수직 엣지를 검출한 결과, 수평 엣지를 검출한 결과, 수직과 수평 엣지를 동시에 검출한 결과를 볼 수 있다.
아래 결과는 Prewitt Mask(프리윗, 프리위트 마스크)를 적용한 결과이다. 사실 결과화면 띄워놓고 뭐가 다른지 비교하려고 프리윗 설명도 하지 않은 채 사진을 바로 밑에 넣어본당.. 확실히 소벨 마스크보다 옅은 엣지가 검출되는 것을 볼 수 있다.
왜냐면 Prewitt Mast 는 소벨 마스크보다 마스크를 정의하는 부분에서, 변화에 대한 비중을 비교적 적게 주었기 때문에 Edge 추출 시 Edge가 덜 부각된다는 특징을 가지고 있다. 말하자면 단점이다..
그리고 소벨마스크를 회선한 결과와 비슷하고, 응답시간이 다소 빠르다는 장점이 있다. 소벨마스크는 대각선 방향 에지에 민감하게 반응하지만, 프리윗 마스크는 대각선 방향 엣지보다 수직/수평 방향 엣지에 더 민감하게 반응한다는 특징이 있다.
다음은 소벨마스크를 구현한 OpenCV C++ 핵심 코드이다. 메인코드와, RGB 영상을 GRAY로 변환하는 코드는 내용에 넣지 않았고, 마스크를 지정해주는 메인코드에서 값만 바꾸어서 Prewitt Mask 를 구현하였다.
void SobelEdgeDetect(const Mat& image, Mat& result, uchar thresh){ // 수직마스크 Mat maskX = (Mat_<double>(3, 3) << 1, 0, -1, 2, 0, -2, 1, 0, -1); // 수평마스크 Mat maskY = (Mat_<double>(3, 3) << 1, 2, 1, 0, 0, 0, -1, -2, -1); int filterOffset = 3 / 2; // Mat 초기화 result = Mat::zeros(image.rows - filterOffset * 2, image.cols - filterOffset * 2, image.type()); double sumEdgeX; double sumEdgeY; double magnitude; for (int yimage = filterOffset; yimage < image.rows - filterOffset; ++yimage){ for (int ximage = filterOffset; ximage < image.cols - filterOffset; ++ximage){ sumEdgeX = 0; sumEdgeY = 0; for (int ymask = -filterOffset; ymask <= filterOffset; ++ymask){ for (int xmask = -filterOffset; xmask <= filterOffset; ++xmask){ // 영상에 마스크를 연산하는 부분 // sumEdgeX : 수직마스크적용, sumEdgeY : 수평마스크적용 sumEdgeX += image.at<uchar>(yimage + ymask, ximage + xmask) * maskX.at<double>(filterOffset + ymask, filterOffset + xmask); sumEdgeY += image.at<uchar>(yimage + ymask, ximage + xmask) * maskY.at<double>(filterOffset + ymask, filterOffset + xmask); } } magnitude = sqrt(pow(sumEdgeY, 2) + pow(sumEdgeX, 2)); //threshold 적용 result.at<uchar>(yimage - filterOffset, ximage - filterOffset) = ((magnitude > thresh) ? 0 : 255); } } }
구현을 소벨마스크와 프리윗마스크만 해보았는데, 특징을 살펴보자면 남은 유명한 마스크 중에서 로버츠 마스크라는게 있는데 이 마스크는 소벨, 프리윗 마스크에 비해 매우 빠른 계산속도를 나타낸다. 또한 주변과 관계 없이 경계가 확실한 엣지를 추출 할 수 있다.
그러나 치명적인 단점이 있는데 다른 마스크보다 크기가 훨씬 작기 때문에 돌출한 화소값을 평균화 할 수 없다는 단점이 있다. 이로인해 잡음을 해결 할 수가 없다. 아래는 로버츠 마스크의 형태이다.
'Programming > OpenCV' 카테고리의 다른 글
OpenCV 기본클래스 (1) (7) | 2016.08.13 |
---|---|
OpenCV 응용프로그램을 제대로 시작하지 못했습니다(0xc000007b) 오류 해결법 (11) | 2016.08.13 |
OpenCV Adaptive thresholding(적응적 경계화) (0) | 2016.01.18 |
OpenCV 히스토그램 구현하기 (0) | 2016.01.18 |
OpenCV 픽셀 값 접근하기 - cvMat 데이터 구조 (4) | 2016.01.06 |