반응형


Watershed 알고리즘을 사용하여 영상 분할(Image segmentation) 하는 방법에 대해 설명합니다.




다음 OpenCV Python 튜토리얼을 참고하여 강좌를 비정기적로 포스팅하고 있습니다.


https://docs.opencv.org/4.0.1/d6/d00/tutorial_py_root.html







그레이스케일 이미지에서 높은 픽셀값을 가지는 부분을 언덕으로 보고, 낮은 픽셀값을 가지는 부분을 계곡으로 볼 수 있습니다.

한 이미지에 여러 개의 고립된 계곡(극소점)이 있을 수 있습니다. 각각 다른 색의 물(라벨)로 물을 채운다고  합시다.


수위가 상승함에 따라 다른색의 물로 채워지던 계곡들이 하나로 합쳐질 수 있습니다.  이것을 방지하기 위해 물이 합류되는 지점에 벽을 쌓습니다.


모든 언덕이 물에 잠길때까지 물을 채우고 벽을 쌓는 것을 반복합니다. 그런 다음 생성한 벽을 사용하여 영상 분할을 할 수 있습니다.


아래 그림처럼 수위(h)가 높아짐에 따라 다른 계곡과의 경계점에 벽(수직선)을 추가하게 됩니다.


이미지 출처 - https://imagej.net/Classic_Watershed




하지만 이런 접근은 노이즈 등으로 인해 영상 분할이 필요 이상으로 될 수 있습니다.

그래서 OpenCV에서는 마커 기반 watershed 알고리즘을 구현합니다. 병합될 계곡점과 그렇지 않을 계곡점을 지정해주는 방법입니다.



watershed 알고리즘을 적용하기 전에 영역을 나누어 라벨을 부여해줍니다.  오브젝트라고 생각되는 영역과 배경이라고 생각되는 영역에 라벨을 부여합니다. 아직 배경인지 오브젝트인지 확실치 않은 영역은 라벨로 0을 부여합니다. 이런 식으로 이미지에 라벨을 부여하는 것을 마커라고 합니다.


이제  watershed 알고리즘을 적용합니다. 마커 지점부터 시작하여 라벨값을 주변으로 확장하며 라벨값이 0인 영역에 대한  라벨값을 업데이트합니다. 오브젝트간 경계의 라벨은 -1이 됩니다.



아래 이미지에서 동전이 서로 붙어있습니다. watershed 알고리즘과 Distance Transform을 사용하여 서로 붙어있는 동전을 분리해보겠습니다.




이진화를 적용하여 배경과 동전을 분리합니다.


import numpy as np
import cv2 as cv


img = cv.imread('coins.jpg')
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)



동전들이 붙어있는 상태로 검출됩니다.





오프닝을 적용하여 노이즈를 제거하고 팽창(dilate)연산을 적용하여 동전 영역내에 있을수 있는 공백을 메웁니다.

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)



동전이 검출될 수 있는 배경 영역(sure_bg)입니다.





동전 중앙 부분을 검출하기 위해서 distance transform를 적용한 후 이진화를 합니다. 오른쪽 이미지가 동전 영역이 확실한 동전  중앙 영역(sure_fg)입니다.


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)



       Distance Transform 결과                              이진화 적용 후 결과

   




배경 영역(sure_bg)에서 동전 중앙 영역(sure_fg)을 뺍니다.


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


아래 이미지처럼 동전인지 배경인지 아직 알 수 없는 영역(unknown)이 존재하게 됩니다.




동전 중앙 영역 이미지를 라벨링한 후, 아직 동전인지 배경인지 알 수 없는 영역의 라벨값을 0으로 지정합니다.  배경이 확실한 영역의 값은 1이됩니다.


# Marker labelling
ret, markers = cv.connectedComponents(sure_fg)

# Add one to all labels so that sure background is not 0, but 1
markers = markers+1

# Now, mark the region of unknown with zero
markers[unknown==255] = 0




watershed 알고리즘을 적용하면 동전 외곽은 라벨값으로 -1을 가지며 배경은 라벨값으로 1을 갖게됩니다.


markers = cv.watershed(img, markers)

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




실행 결과입니다. 동전의 접촉면이 분리되었지만 정확하지는 않습니다.





테스트에 사용한 전체 코드입니다.


import numpy as np
import cv2 as cv



img = cv.imread('coins.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)


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


# sure background area
sure_bg = cv.dilate(opening,kernel,iterations=3)


# Finding sure foreground area
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)


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


# Marker labelling
ret, markers = cv.connectedComponents(sure_fg)

# Add one to all labels so that sure background is not 0, but 1
markers = markers+1

# Now, mark the region of unknown with zero
markers[unknown==255] = 0


markers = cv.watershed(img, markers)

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

cv.imshow("dist_transform", result_dist_transform)
cv.imshow("unknown", unknown)
cv.imshow("sure_fg", sure_fg)
cv.imshow("sure_bg", sure_bg)
cv.imshow("result", img)
cv.waitKey(0)




최초 작성 2019. 1. 8



반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 이재호 2020.03.11 23:38

    정말 보석같은 강의들이 많네요 감사합니다.

  2. 2020.08.28 16:29

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.08.28 20:00 신고

      알고리즘이 모든 입력 사진에 대해 똑같이 동작하지 않습니다. 알고리즘마다 잘 동작하는 입력 사진이 있습니다.

      아래 링크에서 설명하는 grab_cut을 해보세요

      https://webnautes.tistory.com/1379

+ Recent posts