본문 바로가기

Project

[스마트팜 드론 자율주행] Image Segmentation using OpenCV

이 포스팅에서는 많이들 사용하는 YOLO와 같은 딥러닝 모델이 아닌 openCV로 image segmentation을 하는 방법을 다뤄보려고 합니다. 

 


먼저 제 졸업 프로젝트이기도 한 이 프로젝트에 대해 간략히 설명드리자면,

드론을 사용해 포도의 병충해, 익은 정도를 판별해서 농부나 농협과 같은 기관에 알려주는 서비스입니다. 

여기서 제가 맡은 파트는 드론 자율주행입니다. 

 

드론 자율주행도 최근에는 상용화된 기술이 있는데요, 완전한 자율주행은 아니고 바닥에 드론이 인식할 수 있는 그림과 같은 것을 놓아 따라 가게 하는 방법도 있고 사람을 따라가게 하는 방법도 있습니다.

저는 드론이 완전하게 자율주행을 할 수 있도록 스캔할 범위를 정하고 직접 path planning을 하여 자율 주행하는 것을 목표로 하고 있습니다.

 

대략적인 프로세스는,

일정 고도까지 올라감 → image 촬영 → 장애물 범위 감지 → AoI(Area of Interest), 장애물, altitude 바탕으로 coverage path planning, 고도를 가지고 실제 거리로 변환하는 계산 수행 → 타겟이 되는 고도, 시작점으로 내려감 → 자율비행 수행

 

왜 딥러닝이 아닌 openCV로 image segmentation을 하는지를 설명하기 위한 서론이 길었는데요,

드론에 장애물 범위를 감지하는 코드를 넣어야하기 때문에 최대한 가볍고, 데이터가 없어도 가능하게 하기 위해, 그리고 딥러닝 서버를 거치거나 하는 복잡한 과정을 줄이기 위해 openCV로 하는 방법을 선택했습니다.

 


openCV를 사용한 image segmentation에는 여러가지 알고리즘이 있는데요, 

저는 Watershed algorithm을 사용해보려고 합니다!

Watershed Algorithm

이미지를 grayscale로 변환하면 각 픽셀의 값(0 ~ 255)으로 나오게 되는데요, watershed 알고리즘은 두 가지 색이 섞이는 부분에 경계선을 만들어 섞이지 않게 하는 것입니다. 그 경계선을 바로 이미지의 구분지점으로 파악하고 이미지 분할을 합니다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

Let's do it

이제 코딩을 해보겠습니다! 저는 파이썬을 사용했습니다. 

저는 이 알고리즘을 사용하는 아주아주 대표 이미지격인 동전들 사진으로 먼저 진행해보았습니다.

먼저 필요한 패키지를 임포트하고 저장한 사진을 불러와줍니다. 

import numpy as np
import cv2 as cv
img = cv.imread('coins.jpg')

 

다음으로 watershed는 greyscale, 흑백으로 변환하여 진행하기 때

문에 이미지를 흑백으로 바꾸고 이진화를 적용합니다. 

gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV+cv.THRESH_OTSU) # 이진화

cv.imshow("result", thresh) ##이미지 출력
cv.waitKey(0)

완전 뚜렷한 흑백 이미지가 됐습니다!

 

이제 opening으로 노이즈를 제거하고 dilate 함수로 결과를 증폭하여 배경과 객체의 경계를 뚜렷하게 만듭니다. 

sure_bg는 배경이 확실하다고 생각되는 영역입니다.

kernel = np.ones((3,3), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)

sure_bg = cv.dilate(opening, kernel, iterations=3)

그리고 이제 foreground가 확실하다고 생각되는 영역을 찾습니다. 

distance transform으로 객체의 중앙 부분을 인식하고 이진화를 적용해 전경 영역을 찾습니다.

dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
result_dist_transform = cv.normalize(dist_transform, None, 255, 0, cv.NORM_MINMAX, cv.CV_8UC1)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,cv.THRESH_BINARY)

cv.imshow("dist_transform", result_dist_transform)
cv.waitKey(0)

 

꽤 잘 찾은 것 같네요!

 

배경 영역에서 전경 영역을 빼줍니다. 그러면 객체인지, 배경인지 알 수 없는 영역이 나오게 됩니다. 

sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg)

cv.imshow("unknown", unknown)
cv.waitKey(0)

 

객체 중앙 영역을 라벨링하고, 배경이 확실한 영역은 1로, unknown 영역은 0으로 라벨링, mark를 해줍니다. 

ret, markers = cv.connectedComponents(sure_fg)
markers = markers+1
markers[unknown==255] = 0

Watershed 알고리즘을 적용해줍니다. 알고리즘 적용 후에는 객체의 경계 영역은 -1의 라벨값을 가집니다. 

markers = cv.watershed(img, markers)
img[markers == -1] = [255, 0, 0]

cv.imshow("final_result", img)
cv.waitKey(0)

배경은 라벨값 1을 가지는데 아래와 같이 배경값에 [255, 255, 0]으로 색을 지정해주면 제대로 인식된 부분만 걸러서 볼 수 있습니다. 

img[markers == 1] = [255, 255, 0]

어설프지만 그래도 동전들을 잘 찾아낸 것 같습니다!

 

설명드린 watershed 알고리즘 이외에도

watershed 알고리즘에 distance transform을 사용하는 알고리즘, 색 범위로 객체를 분리해서 인식하는 알고리즘 등 다양한 알고리즘이 있습니다. 

 


to Ours

저는 위의 많은 알고리즘들 중, watershed, distance transform watershed 두 개를 복잡한 여러 이미지들(포도 밭, 마을에 있는 여러 개 밭, 주차장)에 시도해 보았는데요, distance transform watershed가 그냥 watershed 보다는 많이 인식하지만 두 개 모두 여전히 조금이라도 복잡한 그림에서는 segmentation이 잘 이뤄지지 않습니다.. openCV의 기존의 알고리즘을 이용해서 segmentation을 진행하는 것에 한계가 있는 것 같습니다

 

저희 프로젝트에 적용하기 위해 포도 밭 이미지를 사용해 segmentation을 진행한 결과를 보여드리려고 합니다

제 기준에 좀 희망적이고 앞에서 계속 다루기도 한, watershed 알고리즘을 사용해 나온 결과를 보여드리겠습니다

 

 

인터넷에서 저희가 찍고자 하는 구도와 가장 유사한 view를 가진 포도밭 사진을 가져왔습니다

아래는 중간 결과인 unknown인데요, 

위의 포도가 있는 위치를 얼추 비슷하게 인식한 모습을 보입니다

즉, 배경인지, 전경인지 확실하지 않은 부분(= watershed 첫번째 그래프의 경계에 있는 부분)이 모두 장애물이 있는 위치인 것이죠 (땅은 기가막히게 배경으로 인식함..! 전경을 가려내지 못할 뿐..)

결과는.. 이 알고리즘으로 포도밭의 작물을 인식하지 못하는 결과가 나왔습니다....

 

그래서 역으로 unknown을 이용해서 장애물을 segmentation하는 방법을 시도하거나

앞에서 말씀드린 여러 알고리즘을 사용해 배경과 전경의 차이를 더 증폭시켜 배경과 전경의 경계를 분류해보려고 합니다

그래도 뭔가 배경은 걸러냈다는데에서 아주 약간의 희망을 보았습니다..! 

다음 포스팅에는 완벽하게 map extraction을 구현한 결과를 가져오겠습니다!!