공돌이 공룡의 서재

[python openCV] 이미지 처리 - Canny Edge Detector (+Gaussian Filter) 본문

코딩/opencv

[python openCV] 이미지 처리 - Canny Edge Detector (+Gaussian Filter)

구름위의공룡 2020. 12. 27. 21:43

<개념>

 

John F Canny라는 분이 개발한 알고리즘으로, 간단 요약하자면 이미지 상에서 edge, 즉 경계선 또는 테두리를 찾을 때 유용한 알고리즘이다. 코드는 원리에 비해 간단해서 원리를 몰라도 사용 가능하지만 어떻게 찾는지 정도만 읽으면 도움이 될 것이다. 

 

어떻게 찾을까.

 

5개의 과정을 거치는데 다음과 같다. (출처: en.wikipedia.org/wiki/Canny_edge_detector)

1. 가우시안 필터를 이용하여 이미지 상에서 노이즈를 제거하여 smooth 이미지를 얻는다.:

가우시안 필터란 저주파 필터에 해당하는 필터다.

Gaussian Filter

저주파 필터란 주파수가 작은 신호들은 통과시키고 높은 신호들은 거르는 역할을 하는 필터다. 일반적으로 5x5 크기의 필터를 사용하고, 필터 크기가 클수록 노이즈에 대한 민감도가 낮아진다. 회로나 통신도 아닌데 이미지에서 왜 갑자기 주파수에 관련된 내용이 나올까. 이미지는 픽셀들로 이루어져 있다. 픽셀의 값은 다른 게시글에서 살펴보았듯이 0부터 255까지의 값을 갖고, 컬러 이미지일 경우 RGB 3개 채널의 값들을, 흑백 이미지의 경우 Grey scale 1개 채널에서 값을 갖는다. 한 이미지 내에서 명암, 물체의 색, 등에 따라서 이 픽셀 값들이 변할 것이다. 이때 이 변함이 심하면 주파수가 높고, 변함이 적다면 주파수가 낮다고 할 수 있다. 간단한 예를 들어보자. 

(아래 더보기를 누르면 이미지 3개가 나올텐데 계속 보면 눈 아프니 접음으로 표시하겠다...)

더보기

 

출처: https://photo.stackexchange.com/questions/40401/what-does-frequency-mean-in-an-image#:~:text=In%20other%20words%2C%20you%20can,colors)%20contain%20only%20low%20frequencies.

위의 3개 그림을 보면 검정에서 흰색으로 갔다가 다시 검정으로 변하는 모양을 볼 수 있다. 다만 아래로 갈수록 변경 수가 많아지고 있으니 그 빈도수가 높아지고 있다고 볼 수 있다. 즉, 아래로 갈수록 주파수가 높다. 이 개념을 이미지에 적용시켜보면, 이미지에서 픽셀의 값들이 자주 바뀌는 부분은 주파수가 높은 부분이라고 할 수 있다. 

 

결론적으로 이미지에서의 주파수란 이처럼 픽셀값이 얼마나 변화하는지를 나타내는 정도라고 할 수 있겠다. 

아래 코드는 가우시안 필터를 확인해보는 코드다. 

import cv2

food = cv2.imread('tsukemen.jpg')
food = cv2.resize(food, dsize=(720, 720))
grey = cv2.cvtColor(food, cv2.COLOR_RGB2GRAY)

kernel = cv2.getGaussianKernel(5, 3) # 필터 수 / 필터 값의 sigma(표준편차)
after = cv2.filter2D(food, -1, kernel)

cv2.imshow('Before', food)
cv2.imshow('After', after)
cv2.waitKey(0)

cv2.filter2D는 적용할 이미지, ddepth(Destination Depth), filter 이렇게 3개의 인수를 갖는다. ddepth란 destination image(결과에 해당하는 이미지)에 정보의 차원을 결정한다. -1이면 이전 이미지와 같은 이미지 크기 및 차원을 얻을 수 있다. (참고: www.xspdf.com/resolution/54906452.html

필터를 적용하고나니 면이 있는 부분이나 일부가 뿌옇게 된 것이 보인다. 그래서 이렇게 가우시안 필터를 적용한 것을 Gaussian Blur라고도 한다. 카메라 어플에서 잡티 제거와 같은 보정도 이런 원리를 이용한 것이다.

 

2. 이미지에서 intensity gradient를 찾는다:

위의 필터를 거치고 난 이미지에서 edge를 찾으려면 픽셀 값들의 변화 방향 또한 고려되어야 할 것이다. 위의 음식 사진을 예로 들면 나무 받침대를 세로로 보면 줄무늬 때문에 값이 계속 바뀔 테지만 가로로 보면 선이 이어져서 값의 변화가 적을 것이다. 이런 요소를 고려하기 위한 과정이다. 

 

3. non-maximum suppression을 적용한다:

최대값이 아니면 억누른다는 의미인데, 위에서 구한 intensity gradient 중에서 가장 높은 값만 찾는다. 다시 말해서 값이 최대가 되는 방향을 찾고, 나머지는 무시한다는 뜻이다.

 

4. potential edge(실제 테두리라고 볼 수 있는 edge)를 결정하기 위해 double threshold를 적용한다. & 

5. Hysteresis로 edge를 찾는다: 

위의 과정을 거쳐도 색 변화나, 노이즈에 의해 약한 gradient들이 남아있을 수 있다. 이를 제거하기 위해 max와 min 값을 설정하여, max보다 큰 값만 strong edge로 본다. max와 min 사이는 weak edge로 본다. min보다 작으면 무시된다.(suppressed). B와 C처럼 max와 min 사이에 걸쳐있는 weak edge는 어떻게 처리할까. strong edge는 아니지만 연결되어 있는 B는 edge로 고려될 수 있다. C는 연결되어 있지 않으므로 무시된다. 위의 과정까지 마치면 결과물을 얻을 수 있다.

 

(Hysteresis는 말이 어려운데 약한 edge나 강한 edge에 연결되어 있지 않은 edge들을 제거하는 것을 말한다.) 

 

<코드>

import cv2

food = cv2.imread('tsukemen.jpg')
food = cv2.resize(food, dsize=(720, 720))
# canny를 적용하기 전에 흑백전환을 하지 않아도 된다. 
canny = cv2.Canny(food, 60, 200) # 처리할 이미지 사진 / min Threshold / max Threshold

cv2.imshow('before', food)
cv2.imshow('After', canny)
cv2.waitKey(0) 

 

100 , 150
60, 120

max 값이 작아질수록 strong edge로 판단, 즉 물체사이의 테두리로 볼 수 있는 edge들이 많아지기 때문에 선들이 더 복잡하게 표현됨을 알 수 있다. 

Comments