포스트

Edge detection

Edge detection

엣지(edge) 검출은 객체의 경계를 찾는 방법으로, 객체 판별의 전처리 과정에서 사용해요. 이 글에서는 대표적인 엣지(이하 경계) 검출에 필요한 수학적 배경과 알고리즘을 설명할게요.


미분과 변화량

경계 검출의 핵심은 변화를 찾는 것이에요. 객체와 배경은 밝기 차이가 있을 것이라고 가정하고, 밝기 변화가 일정 수준을 넘어가면 경계로 예측해요. 이미지가 복잡하면 잘못 검출될 가능성도 있지만, 합리적인 아이디어라고 볼 수 있어요.

객체와 배경 밝기 비교

변화를 정의하기 위해 수학에서는 변화율을 미분으로 정의해요. 연속 함수 $f(x)$에 대해 미분은 아래와 같아요.

\[f'(x) = \cfrac{df}{dx}=\lim_{\bigtriangleup x \to 0}\cfrac{f(x+\bigtriangleup x)-f(x)}{\bigtriangleup x}\]

$\bigtriangleup x$는 변화량을 의미해요. 미분값은 변화량이 0에 가까워질 때 함수 값의 차이를 뜻해요. 쉽게 말해, 특정 시점에서 함수 값의 변화를 볼 수 있어요. 위 파란 그래프는 함수 $f(x)$를, 아래 빨간 그래프는 $f(x)$를 미분한 $f’(x)$를 나타내요. 변화가 멈춘 순간에 미분값은 0이 되고, 급격한 변화가 발생하면 미분값이 0에서 멀어져요.

미분 함수 그래프

이산 함수 미분

위에서 살펴본 미분법은 함수가 연속적일 때 적용할 수 있어요. 하지만 이미지는 독립된 픽셀로 이루어져 있기 때문에 이산 값에 대한 미분을 다시 정의해야 해요.

\[f'(x) = \cfrac{df}{dx}\approx \cfrac{f(x+\bigtriangleup h)-f(x)}{\bigtriangleup h}\]

여기서 변화량 $\bigtriangleup h$는 픽셀 간의 거리를 뜻해요. 이미지는 2차원 좌표 $(x,y)$를 가지므로, x 방향과 y 방향에 대한 미분을 모두 정의해야 해요.

\[f'_x(x,y) = \cfrac{df}{dx}\approx \cfrac{f(x+\bigtriangleup h,y)-f(x,y)}{\bigtriangleup h}\] \[f'_y(x,y) = \cfrac{df}{dy}\approx \cfrac{f(x,y+\bigtriangleup h)-f(x,y)}{\bigtriangleup h}\]

이를 시각화하면 인접한 픽셀과의 차를 구하는 식으로 쉽게 이해할 수 있어요.

이산 미분 마스크

\[f'_x\approx \cfrac{f(x+1,y)-f(x,y)}{1}=59 - 30\] \[f'_y\approx \cfrac{f(x,y+1)-f(x,y)}{1}=87 - 30\]

중앙 차분

중앙 차분은 인접한 두 픽셀의 미분 값을 구하는 방식이에요.

중앙 차분 마스크

\[f'_x\approx \cfrac{f(x+1,y)-f(x-1,y)}{2}\] \[f'_y\approx \cfrac{f(x,y+1)-f(x,y-1)}{2}\]

정의대로라면 픽셀 간 거리인 $h$가 2이므로 2로 나누어야 해요. 하지만 우리가 필요한 건 상대적인 크기예요. 물체와 배경의 밝기가 상대적으로 얼마나 다른가를 보기 때문에 2로 나누는 과정을 생략하고 약식으로 계산해요.

\[f'_x\approx f(x+1,y)-f(x-1,y)=59-17\] \[f'_y\approx f(x,y+1)-f(x,y-1)=87-40\]

결국은 인접한 두 픽셀의 차를 구하는 식이 된답니다.

행렬 연산

행렬 연산을 이용하면 효율적으로 연산할 수 있어요. x 방향 미분 식을 다시 살펴볼게요.

\[f'_x\approx f(x+1,y)\cdot 1 + f(x,y)\cdot 0 - f(x-1,y)\cdot 1\] \[f'_x\approx\begin{bmatrix} f(x-1,y) & f(x,y) & f(x+1,y) \end{bmatrix}\begin{bmatrix}-1 \\ 0\\ 1 \end{bmatrix}\]

y 방향도 같은 방법으로 행렬을 만들 수 있어요.

마스크 연산 예시

정리하면, $f(x,y)$와 인접한 픽셀의 변화량을 통해 현재 위치가 경계인지 판별할 수 있어요. 이때 효율적인 연산을 위해 행렬을 이용해요.


Gradient 정의

미분은 gradient를 설명하기 위한 준비 단계였어요. Gradient는 x 방향과 y 방향의 미분값을 나타내는 벡터예요.

\[\bigtriangledown f=\begin{bmatrix} f_x \\ f_y \end{bmatrix}=f_x i + f_y j\]

$i,j$는 각 방향에 대한 단위 벡터를 뜻해요. 벡터의 크기는 $\parallel \bigtriangledown f\parallel $, 벡터의 방향은 $\theta$로 표현해요.

\[\parallel \bigtriangledown f\parallel =\sqrt{f_x^2+f_y^2}\] \[\theta =tan^{-1}(\cfrac{f_y}{f_x})\]

gradient 벡터 예시

이미지 일부를 확대한 뒤 2차원 공간에 gradient 벡터를 나타냈어요. 경계로 판단되는 부분은 벡터의 크기가 매우 커요. 벡터의 방향은 변화가 발생하는 방향을 나타내고, 벡터에 수직인 방향이 경계라고 볼 수 있어요. 확실히 경계가 아니라고 판단되는 곳은 크기와 방향 모두 0을 가집니다.


다양한 마스크

앞서 행렬 연산을 이용한다고 했어요. 이 행렬을 마스크(mask), 필터(filter) 또는 커널(kernel) 등으로 불러요. 본 글에서는 “마스크”로 통일하겠습니다. 앞서 소개한 [-1 0 +1] 형태의 단순한 마스크 외에도 더 정교한 경계 검출을 위해 여러 마스크가 개발되었어요.

Sobel

Sobel 마스크는 가장 대표적인 마스크예요. 인접한 두 픽셀뿐만 아니라 근접한 픽셀까지 고려해요.

sobel 마스크

벡터의 크기를 통해 경계가 맞는지 확인한다고 했지만, 의미 없는 노이즈도 섞여 있을 수 있어요. 따라서 벡터가 특정 범위를 넘어서면 경계로 판별해요. 이때 기준이 되는 값을 threshold 또는 임계값이라고 해요. threshold는 상황에 맞게 직접 설정해야 해요.

sobel 결과 이미지

1
2
3
4
5
6
7
8
9
10
11
12
Mat dx, dy;
Sobel(img, dx, CV_32FC1, 1, 0);
Sobel(img, dy, CV_32FC1, 0, 1);

Mat mag_float, mag;
magnitude(dx, dy, mag_float);
mag_float.convertTo(mag, CV_8UC1);

int threshold = 150;
Mat edge = mag > threshold;

imshow("edge", edge);

Scharr

Scharr 마스크는 인접한 픽셀에 더 큰 가중치를 줘서 Sobel보다 변화에 더 민감해요.

scharr 마스크

threshold를 높게 설정했음에도 신발 얼룩까지 포함하는 모습을 보여줘요. 얼룩도 밝기 변화가 있는 영역이기 때문이에요.

scharr 결과 이미지

1
2
3
4
5
6
7
8
9
10
Scharr(img, dx, CV_32FC1, 1, 0);
Scharr(img, dy, CV_32FC1, 0, 1);

magnitude(dx, dy, mag_float);
mag_float.convertTo(mag, CV_8UC1);

int threshold = 250;
Mat edge = mag > threshold;

imshow("edge", edge);

Canny edge detector

Canny 검출기는 단순한 마스크보다 더 정확한(tight) 테두리를 검출하기 위해 개발됐어요.

  1. Gaussian Filter
  2. Gradient
  3. NMS: non-maximum suppression
  4. Double thresholding
  5. Hysteresis edge tracking

Gaussian Filter

Gaussian Filter는 가우시안 정규분포를 활용해 노이즈를 제거하는 과정이에요. 노이즈는 주변과 다른 형태를 띠는 값이라서 미분을 수행했을 때 큰 값으로 나타날 수 있어요. 따라서 노이즈의 영향을 줄이기 위해 필터를 사용해요. 평균이 0, 표준편차가 $\sigma$라고 할 때, 2차원 가우시안 분포는 아래와 같아요.

\[G_{\sigma_x\sigma_y}(x,y)=\cfrac{1}{2\pi\sigma_x\sigma_y}e^{-(\cfrac{x^2}{2\sigma^2_x}+\cfrac{y^2}{2\sigma^2_y})}\]

가우시안 분포 시각화

가우시안 필터를 사용하면 중앙에 비교적 큰 값이 곱해지고, 주변은 작은 값이 곱해져요. 주변 상황을 약하게 반영하는 과정에서 비교적 완만한 값이 만들어지고 부드러운 이미지를 만드는 블러 효과로 사용돼요.

평균이 0이고 표준편차가 $\sigma$일 때, $[-4\sigma ,4\sigma]$ 사이에 99.99%의 값이 들어가 있기 때문에 마스크 크기는 $8\sigma +1$이나 그보다 작은 크기를 사용해요.

가우시안 필터 결과 이미지

동일한 조건에서 5 x 5 가우시안 필터를 적용했을 때와 적용하지 않았을 때 검출된 경계의 모습이에요. 신발 발등의 불규칙한 얼룩이 제거됐어요.

Gradient

Sobel 마스크를 활용해 gradient를 계산해요. 하지만 앞서 소개한 L2 norm을 이용한 크기 계산은 과정이 복잡하니 간단한 L1 norm을 사용해 단순하게 연산해요.

\[\parallel \bigtriangledown f\parallel \approx |f_x|+|f_y|\]

추가로 gradient 방향도 함께 계산해요. 계산된 방향은 각 픽셀이 사각형 형태로 붙어 있기 때문에 4가지 방향[0, 45, 90, 135]으로 단순화할 수 있어요.

gradient 방향 시각화

NMS: Non-maximum suppression

Sobel을 거친 gradient는 비슷한 지역에서 여러 경계를 만들기도 해서 일부 경계가 두껍게 나타나요.

두꺼운 경계의 픽셀 확대

NMS: non-maximum suppression은 경계로 판단되는 픽셀 중 가장 확실한 픽셀만 선택해요. gradient 방향으로 인접한 두 픽셀을 비교하고 가운데 픽셀이 가장 클 경우 경계로 사용하며 그렇지 않을 경우 0으로 처리해요.

nms 실행 과정

이 과정을 통해 겹쳐있는 경계 영역 중 정확한 경계를 가려내죠.

nms 결과 이미지

동일한 조건에서 NMS를 실행했을 때와 실행하지 않았을 때의 모습이에요. 겹쳐있던 선이 제거됐어요.

Double thresholding

Double thresholding은 임계값 두 개를 이용해 경계를 판별해요. 높은 임계값을 $T_{high}$, 낮은 임계값을 $T_{low}$라고 하죠.

  • $\parallel \bigtriangledown f\parallel \ge T_{high}$: 확실한 경계로 판별
  • $ \parallel \bigtriangledown f\parallel \le T_{low}$: 경계가 아님
  • $else$: edge tracking 진행

두 임계값 사이에 있는 픽셀은 추가 검사를 진행해요.

Hysteresis edge tracking

Hysteresis edge tracking은 확실한 경계를 넓혀가는 방식으로 경계를 추가해요.

edge tracking 실행 과정

확실하게 경계로 판별된 픽셀에 대해 주변 픽셀을 검사하고 만약 주변 픽셀 중 $T_{high}$보다는 작지만 $T_{low}$보다 큰 값이 있다면 경계로 판별해요. 다시 말해 $T_{high}$와 연결된 픽셀은 경계로 인정하지만 $T_{low}$와 연결된 사이 값은 경계로 인정하지 않아요. tracking을 통해 연결된 테두리를 추가로 찾을 수 있답니다.

edge tracking 결과

정리

Canny 알고리즘의 각 단계가 어떤 과정으로 진행되고 적용했을 때와 적용하지 않을 때의 결과 차이를 알아보았어요. 전체 과정을 정리하면 아래와 같아요.

gaussian-gradient-nms-edge tracking

각 단계를 거친 이미지 행렬이에요. OpenCV는 Canny 함수를 통해 이 복잡한 과정을 한 번에 처리할 수 있어요.

1
Canny(img, dst, 100, 200);

만약 구현 과정이 궁금하다면 Github(denev6/deep-learning-codes)를 참고하면 돼요.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

인기 태그