귀퉁이 서재

OpenCV - 19. 모폴로지(Morphology) 연산 (침식, 팽창, 열림, 닫힘, 그레디언트, 탑햇, 블랙햇) 본문

OpenCV

OpenCV - 19. 모폴로지(Morphology) 연산 (침식, 팽창, 열림, 닫힘, 그레디언트, 탑햇, 블랙햇)

Baek Kyun Shin 2020. 10. 11. 20:36

이번 포스팅에서는 모폴로지라는 개념에 대해 알아보겠습니다. 이번 포스팅 역시 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리한 것임을 밝힙니다.

코드: github.com/BaekKyunShin/OpenCV_Project_Python/tree/master/06.filter

모폴로지(morphology)란 '형태학'이라는 뜻입니다. 이는 영상 분야에서 노이즈 제거, 구멍 채우기, 끊어진 선 이어 붙이기 등에 쓰이는 형태학적 연산을 말합니다. 모폴로지 연산은 검은색과 흰색으로만 구성되어 있는 바이너리(binary) 이미지에 적용할 수 있습니다. 모폴로지 연산으로는 침식, 팽창, 열림, 닫힘이 있는데 이에 대해 차례대로 배워보겠습니다.

침식 연산

침식(erosion)이란 말 그대로 형태를 깎아 내는 것입니다. 따라서 침식 연산은 이미지를 깎아 내는 연산을 뜻합니다. 침식 연산을 위해서는 구조화 요소 커널(structuring element kernel)이라는 0과 1로 구성된 커널이 필요합니다. 구조화 요소 커널은 1이 채워진 모양에 따라 사각형, 타원형, 십자형 등으로 사용할 수 있습니다.

침식 연산은 구조화 요소 커널을 입력 이미지에 적용해서 1로 채워진 영역을 온전히 올려 놓을 수 없으면 해당 픽셀을 0으로 변경합니다. 아래는 십자형 구조화 요소 커널로 침식 연산을 하는 과정을 보여줍니다. 

출처: http://blog.daum.net/shksjy/314

A 이미지에서 흰색 배경은 0이고, 하늘색 전경은 1이라고 합시다. B는 십자형 구조화 요소 커널입니다. 가운데를 포함해서 회색 부분이 다 1로 구성되어 있다고 보면 됩니다. 이때 십자형 구조화 요소 커널의 중심부(빨간 점 부분)는 A 이미지의 파란색 부분을 쭉 훑습니다. 한 칸 한 칸 훑으면서 구조화 요소 커널이 A 이미지의 하늘색 부분과 완전히 겹치지 않을 때는 0으로 변경합니다. 완전히 겹치면 1로 그대로 둡니다. 오른쪽은 침식 연산의 결과입니다. 원래 하늘색 부분이 모두 1이었는데, 침식 연산 결과 빨간 1이 적혀 있는 부분만 1로 남아 있고 나머지 부분은 모두 0으로 변경됩니다. 원본 이미지보다 조금 깎인 것을 볼 수 있습니다.

구조화 요소 커널 생성을 위한 함수는 다음과 같습니다.

  • cv2.getStructuringElement(shape, ksize, anchor)
    shape: 구조화 요소 커널 모양 (cv2.MORPH_RECT: 사각형, cv2.MORPH_EPLIPSE: 타원형, cv2.MORPH_CROSS: 십자형)
    ksize: 커널 크기
    anchor(optional): 구조화 요소의 기준점, cv2.MORPH_CROSS에만 의미 있으며 기본 값은 중심점 (-1, -1)

위 함수로 생성한 구조화 요소 커널로 침식 연산을 수행하는 함수는 다음과 같습니다.

  • dst = cv2.erode(src, kernel, anchor, iterations, borderType, borderValue)
    src: 입력 영상, 바이너리
    kernel: 구조화 요소 커널
    anchor(optional): cv2.getStructuringElement()와 동일
    iterations(optional): 침식 연산 적용 반복 횟수
    boderType(optional): 외곽 영역 보정 방법 
    boderValue(optional): 외곽 영역 보정 값

침식 연산은 큰 물체의 주변을 깎는 기능을 합니다. 더불어 작은 물체는 아예 없애버리므로 노이즈 제거 효과도 있고, 원래는 떨어져 있는 물체인데 겹쳐 있는 것을 서로 떼어내는 데도 효과적입니다.

# 침식 연산 (morph_erode.py)

import cv2
import numpy as np

img = cv2.imread('../img/morph_dot.png')

# 구조화 요소 커널, 사각형 (3x3) 생성 ---①
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
# 침식 연산 적용 ---②
erosion = cv2.erode(img, k)

# 결과 출력
merged = np.hstack((img, erosion))
cv2.imshow('Erode', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()

침식 연산을 위한 구조화 요소 커널은 3 x 3의 사각형 모양으로 생성했습니다. 결과는 보시는 바와 같이 노이즈가 제거되었고, 글씨가 전반적으로 가늘어졌습니다.

팽창 연산

팽창(dilatation)은 침식과 반대로 물체의 주변을 확장하는 연산입니다. 연산 방법도 반대입니다. 침식 연산은 구조화 요소 커널이 입력 영상에서 1로 채워진 영역과 완전히 겹치지 않으면 0으로 변경했습니다. 그러나 팽창 연산은 이와 반대로 완전히 겹치지 않으면 1로 변경합니다. 아래 예시에서 구조화 요소 커널은 십자형이 아님에 유의하기 바랍니다.

팽창을 위한 함수는 아래와 같습니다.

  • dst = cv2.dilate(src, kernel, dst, anchor, iterations, bordeType, borderValue)
    모든 파라미터는 cv2.erode()와 동일합니다.
# 팽창 연산 (morph_dilate.py)

import cv2
import numpy as np

img = cv2.imread('../img/morph_hole.png')

# 구조화 요소 커널, 사각형 (3x3) 생성 ---①
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
# 팽창 연산 적용 ---②
dst = cv2.dilate(img, k)

# 결과 출력
merged = np.hstack((img, dst))
cv2.imshow('Dilation', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()

실행 결과 글씨가 더 팽창되긴 했지만 글씨 안에 있던 검정색 노이즈가 제거된 것을 볼 수 있습니다. 흰색 글씨가 바깥으로도 안으로도 팽창이 되어 노이즈는 제거되고 더 뚱뚱해진 거라고 생각하시면 됩니다.

열림, 닫힘, 그레디언트, 탑햇, 블랙햇 연산

침식은 어두운 부분의 노이즈를 제거하는 효과가 있고 팽창은 밝은 부분의 노이즈를 제거하는 효과가 있다는 것을 살펴봤습니다. 노이즈 제거 효과는 좋으나 원래 모양이 홀쭉해지거나 뚱뚱해지는 변형이 일어납니다. 하지만 침식과 팽창의 연산을 조합하면 원래의 모양을 유지하면서 노이즈를 제거하는 효과를 거둘 수 있습니다.

침식 연산 후 팽창 연산을 적용하는 것을 열림(opening) 연산이라고 하고, 팽창 연산 후 침식 연산을 적용하는 것을 닫힘(closing) 연산이라고 합니다. 열림 연산은 주변보다 밝은 노이즈를 제거하는데 효과적입니다. 또한 맞닿아 있는 것처럼 보이는 독립된 개체를 분리하거나 돌출된 모양을 제거하는 데 효과적입니다. 반면, 닫힘 연산은 주변보다 어두운 노이즈를 제거하는데 효과적이면서 끊어져 보이는 개체를 연결하거나 구멍을 메우는 데 효과적입니다. 

열림 = 침식 + 팽창
닫힘 = 팽창 + 침식

팽창 연산을 적용한 이미지에서 침식 연산을 적용한 이미지를 빼면 경계 픽셀만 얻게 되는데, 이는 앞서 살펴본 경계 검출과 비슷합니다. 이런 연산을 그레디언트(gradient) 연산이라고 합니다.

그레디언트 = 팽창 - 침식

또한, 원본에서 열림 연산 적용 결과를 빼면 값이 크게 튀는 밝은 영역을 강조할 수 있고, 닫힘 연산 적용 결과에서 원본을 빼면 어두운 부분을 강조할 수 있습니다. 이것을 각각 탑햇(top hat)과 블랙햇(black hat) 연산이라고 합니다.

탑햇 = 원본 - 열림
블랙햇 = 닫힘 - 원본

OpenCV는 열림, 닫힘, 그레디언트, 탑햇, 블랙햇 연산을 위해서 아래의 함수를 제공합니다.

  • dst = cv2.morphologyEx(src, op, kernel, dst, anchor, iteration, borderType, borderValue)
    src: 입력 영상
    op: 모폴로지 연산 종류 (cv2.MORPH_OPEN: 열림 연산, cv2.MORPH_COLSE: 닫힘 연산, cv2.MORPH_GRADIENT: 그레디언트 연산, cv2.MORPH_TOPHAT: 탑햇 연산, cv2.MORPH_BLACKHAT: 블랙햇 연산)
    kernel: 구조화 요소 커널
    dst(optional): 결과 영상
    anchor(optional): 커널의 기준점
    iteration(optional): 연산 반복 횟수
    borderType(optional): 외곽 영역 보정 방법
    borderValue(optional): 외곽 영역 보정 값
# 열림과 닫힘 연산으로 노이즈 제거 (morph_open_close.py)

import cv2
import numpy as np

img1 = cv2.imread('../img/morph_dot.png', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('../img/morph_hole.png', cv2.IMREAD_GRAYSCALE)    

# 구조화 요소 커널, 사각형 (5x5) 생성 ---①
k = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
# 열림 연산 적용 ---②
opening = cv2.morphologyEx(img1, cv2.MORPH_OPEN, k)
# 닫힘 연산 적용 ---③
closing = cv2.morphologyEx(img2, cv2.MORPH_CLOSE, k)

# 결과 출력
merged1 = np.hstack((img1, opening))
merged2 = np.hstack((img2, closing))
merged3 = np.vstack((merged1, merged2))
cv2.imshow('opening, closing', merged3)
cv2.waitKey(0)
cv2.destroyAllWindows()

앞서 살펴본 팽창 및 침식과 다르게 글씨의 굵기에는 변함이 없으면서 노이즈는 효과적으로 제거했습니다.

아래 코드는 팽창 연산에서 침식 연산을 뺀 그레디언트 연산을 적용한 예제입니다.

# 모폴로지 그레이언트 (morph_gradient.py)

import cv2
import numpy as np

img = cv2.imread('../img/morphological.png')

# 구조화 요소 커널, 사각형 (3x3) 생성 ---①
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
# 열림 연산 적용 ---②
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, k)

# 결과 출력
merged = np.hstack((img, gradient))
cv2.imshow('gradient', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()

보시는 바와 같이 경계를 검출하는 효과가 있습니다.

아래는 달 이미지에 탑햇, 블랙햇 연산을 적용한 것입니다.

# 모폴로지 탑햇, 블랙햇 연산 (morph_hat.py)

import cv2
import numpy as np

img = cv2.imread('../img/moon_gray.jpg')

# 구조화 요소 커널, 사각형 (5x5) 생성 ---①
k = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
# 탑햇 연산 적용 ---②
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, k)
# 블랫햇 연산 적용 ---③
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, k)

# 결과 출력
merged = np.hstack((img, tophat, blackhat))
cv2.imshow('tophat blackhat', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()

탑햇은 달의 밝은 부분을 강조하고, 블랙햇은 달의 어두운 부분을 강조합니다.

Comments