해당 포스팅은 OpenCV 4로 배우는 컴퓨터 비전과 머신러닝 (황선규 저)를 보고 공부하며 개인적인 용도를 위해 정리한 글이다.
캐니 에지 검출
- 기존 방식의 에지 검출기는 임계값에 민감하여 에지 픽셀이 두껍게 표현 되는 등의 문제가 발생한다.
- 해당 문제를 해결하기 위해 198년 캐니가 신규 에지 검출기를 3가지의 조건을 통해 제시했다.
- 정확한 검출 (good detection): 에지를 검출하지 못하거나 또는 에지가 아닌데 에지로 검출하는 확률을 최소화한다.
- 정확한 위치 (good localization): 실제 에지의 중심을 찾을 수 있어야한다.
- 단일 에지 (single edge): 하나의 에지는 하나의 점 (픽셀) 으로 표현 되어야한다.
- 기존의 소벨 필터 방법은 그래디언트의 크기만을 고려했지만 캐니는 크기와 방향을 모두 고려하였다.
- 서로 연결 되어있는 가능성이 높다는 점을 고려해 에지 특성이 다소 약하게 나타나는 에지도 놓치지 않고 찾을 수 있었다.
캐니 에지 검출의 4단계 과정
가우시안 필터링
- 제일 먼저 입력 영상에서 가우시안 필터링을 수행한다.
- 잡음 제거가 목적이며 영상 내의 노이즈가 많지 않다면 생략하기도 한다.
그래디언트 계산
- $(3, 3)$ 형태의 소벨 필터 마스크를 사용한다.
- 각 축으로 필터링을 수행한 후 그래디언트 크기와 방향을 모두 계산한다.
- 2차원에 정의 된 함수 $f(x, y)$의 그래디언트 크기를 $f$ 함수에 대한
L2 Norm
이라고 하여 연산하지만 연산속도의 향상을 위해L1 Norm
으로 계산한다.
비최대 억제
- 단순 그래디언트 크기가 임계값보다 클 때를 기준으로 에지를 검색하면 주변 픽셀들도 함께 에지로 검출 될 수 있다.
- 에지가 두껍게 검출 되는 것을 막기 위해 비최대 억제 (non-maximum supperession) 과정을 수행한다.
- 그래디언트 크기가 국지적 최대 (local maximum) 인 픽셀만을 에지 픽셀로 정의하는 기법이다.
- 상대적으로 국지적 최대가 아닌 픽셀은 에지 픽셀에서 제외하기 때문에 비최대 억제라는 용어를 사용했다.
- 일반적인 영상에서 국지적 최대를 찾기 위해선 특정 픽셀을 둘러싼 모든 픽셀을 검사하여 국지적 최대를 판명하지만 해당 연산은 그래디언트 벡터의 방향에 있는 인접한 픽셀끼리만 국지적 최대를 수행하므로 가장 변화율이 큰 위치의 픽셀만 에지로 검출 된다.
이중 임계값을 이용한 히스테리시스 에지 트래킹
- 일반적인 에지 검출은 그래디언트 크기만을 고려하므로 조명이 바뀌거나 임게값을 조금만 조절해도 에지 판단의 결과가 크게 달라질 수 있다.
- 하나의 임계값을 사용할 경우 이분법으로 결과가 판단 되기 때문에 환경 변화에 몹시 민감해지므로 캐니 에지에서는 2개의 임계값을 사용한다.
- $T_high$: 해당 임계값보다 높은 값이면 에지로 판단한다.
- $T_low$: 해당 임계값보다 낮은 값이면 일반 픽셀로 판단한다.
- 픽셀의 에지 특성값이 두 임계값의 중간이면 추가적인 검사를 수행한다.
- 해당 값이 위치한 픽셀이 에지와 연결 되어있는 픽셀이면 에지, 그렇지 않으면 일반 픽셀로 판단한다.
cv::Canny
OpenCV
에서는 캐니 에지 검출 함수를 2가지 제공한다.cv::Canny(image, edges, threshold1, threshold2, apertureSize=3, L2gradient=false);
- 일반 영상을 입력으로 받는다.
threshold1, threshold2
: 각각 임계값의 최저, 최대치를 지정해준다.edges
: 입력 영상과 크기와 타입이 같은 8비트 단일 채널의 출력 영상이다.apetureSize
: 그래디언트 계산을 위한 소벨 마스크의 크기이다.L2gradient
: 그래디언트 크기 계산시L2 Norm
의 사용 여부를 지정해준다.
cv::Canny(dx, dy, edges, threshold1, threshold2, L2gradient=false);
- 미분값 행렬들을 입력으로 받는다.
dx, dy
: 타입이CV16SC1
혹은CV16SC3
으로 지정 된 입력 영상의x, y
방향의 미분 행렬이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "opencv2/opencv.hpp"
int main(void)
{
cv::Mat src= cv::imread("path/to/src", cv::IMREAD_GRAYSCALE);
cv::Mat edges1, edges2, dx, dy;
cv::Sobel(src, dx, CV_16SC1, 1, 0);
cv::Sobel(src, dy, CV_16SC1, 0, 1);
cv::Canny(src, edges1, 50, 150);
cv::Canny(dx, dy, edges2, 50, 150);
cv::imshow("src", src);
cv::imshow("image", edges1);
cv::imshow("dxdy", edges2);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}