Morphology
이번 장 에서는 Morphology Operation[2] 에 대하여 알아보겠습니다.
Morphology Operation이란, Image의 형태를 조작하는 연산의 종류를 통칭합니다.
적용 대상이 어떤 Image냐에 따라 Binary, Grayscale로 나눌 수 있고, 연산 방식에 따라 Dilation(확산)과 Erosion(침식)으로 나눌 수 있습니다.
두 연산 모두 공통적으로 Source Image Kernel을 통해 Target Image를 생성하는 과정을 갖습니다.
Morphology Operation을 응용하면 Edge Detection이나 Image Denoising 등의 작업을 할 수 있습니다.
목차는 아래와 같습니다.
Binary Morphology
Grayscale Morphology
Composited Morphological Operations
Import Libraries
import os
import sys
import math
from platform import python_version
import cv2
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
print("Python version : ", python_version())
print("Opencv version : ", cv2.__version__)
Python version : 3.6.9
Opencv version : 4.1.2
Data load
sample_image_path = '../image/'
sample_image = 'kitten.jpg'
img = cv2.imread(os.path.join(sample_image_path, sample_image), cv2.IMREAD_GRAYSCALE)
h, w = [int(x) for x in img.shape]
matplotlib.rcParams['figure.figsize'] = (8.0, 8.0)
Binary Morphology
전체 Image가 0 또는 1로 이루어진 Image를 Binary Image라고 합니다. 아래 설명은 모두 Binary Image를 기준으로 하겠습니다.
이러한 Image는 보통 Image 자체로 이용되기보다 RoI(Region of Interest)를 선택하기 위한 Mask로서 이용됩니다.
Binary Image에 Morphology를 적용하면 자잘한 Noise를 제거하는데 도움이 됩니다.
Erosion 연산은 침식이라는 말 뜻이 의미하듯이 1에 해당하는 밝은 영역이 조금씩 줄어드는 모습을 보입니다.
RoI 바깥에 1의 값을 갖는 Noise가 발생하는 경우에 Erosion 연산을 통해 Noise를 보정할 수 있습니다.
Dilation 연산은 팽창이라는 뜻인데, 흰색 영역이 조금 커지는 모습을 보입니다.
RoI 중간에 0의 값을 갖는 Noise가 발생하는 경우에 Dilation 연산으로 보정할 수 있습니다.
한 번의 호출로 몇 번씩 연산을 반복할지를 인자 ‘iterations’로 조절할 수 있습니다.
ret, th = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
kernel = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.uint8)
erosion = cv2.erode(th, kernel, iterations=1)
dilation = cv2.dilate(th, kernel, iterations=1)
th = cv2.cvtColor(th, cv2.COLOR_GRAY2RGB)
erosion = cv2.cvtColor(erosion, cv2.COLOR_GRAY2RGB)
dilation = cv2.cvtColor(dilation, cv2.COLOR_GRAY2RGB)
th = cv2.rectangle(th, (200, 140), (400, 340), (255, 0, 0), 3)
erosion = cv2.rectangle(erosion, (200, 140), (400, 340), (255, 0, 0), 3)
dilation = cv2.rectangle(dilation, (200, 140), (400, 340), (255, 0, 0), 3)
plt.subplot(221)
plt.imshow(th, cmap='gray')
plt.title('Binary Kitten')
plt.subplot(223)
plt.imshow(erosion, cmap='gray')
plt.title('Erosed Kitten')
plt.subplot(224)
plt.imshow(dilation, cmap='gray')
plt.title('Dilated Kitten')
plt.suptitle('Binary Kitten with morphology', size=15)
plt.show()
Grayscale Morphology
Morphology Operation을 Grayscale Image에 적용하게 되면 기본 원리는 동일하지만 결과가 아주 달라집니다.
1, 0의 값을 사용하는 것이 아니라 Dilation의 경우 Max값을, Erosion의 경우 Min 값을 사용한다는 점이 다른 점 입니다.
자세한 것은 아래 수식을 통해 확인할 수 있습니다.
grayscale dilation : $ (I\oplus k)(j,i)=max_{(y,x)\subseteq k} (I(j-y,i-x)+k(y,x))$
grayscale erosion : $ (I\ominus k)(j,i)=min_{(y,x)\subseteq k} (I(j+y,i+x)-k(y,x))$
수식에서 알 수 있듯 Min, Max연산이 적용됩니다. Dilation의 경우 큰 값을 더 크게, 작은 값을 더 작게 하여 픽셀값간의 차이를 극대화하는 연산이고, Erosion의 경우 큰 값을 작게, 작은 값을 크게 하여 전체적으로 평평하게 만드는 연산입니다.
kernel = np.array([[1, 2, 1]])
gray_kernel = np.dot(kernel.T, kernel)
gray_erosion = cv2.erode(img, gray_kernel, iterations=1)
gray_dilation = cv2.dilate(img, gray_kernel, iterations=1)
plt.subplot(221)
plt.imshow(img, cmap='gray')
plt.title('Gray Kitten')
plt.subplot(223)
plt.imshow(gray_erosion, cmap='gray')
plt.title('Gray Erosed Kitten')
plt.subplot(224)
plt.imshow(gray_dilation, cmap='gray')
plt.title('Gray Dilated Kitten')
plt.suptitle('Gray Kitten with morphology', size=15)
plt.show()
어떤 Image에 커널을 적용하느냐가 Morphology Operation 의 핵심입니다. 물론 어떤 커널을 설계하느냐도 중요하지만, 어떤 연산을 어떤 순서로 몇 번 씩 적용할 것인가 또한 결과물에 많은 영향을 끼칩니다.
위 결과물을 얻는데 사용한 kernel은 가운데쪽에 더 많은 가중치를 주게 되어 주변과의 명암 차이를 더 크게 하는 효과가 있습니다.
Morphology연산은 Heuristic한 특성이 크게 드러나는 연산입니다.
다양한 특성이 존재하는 Image 전체에 일괄적으로 동일한 Morphology Operation을 적용하는 것은 대부분 의미가 없고, 동일한 특성을 가진 부분에 특정한 목적 위해 적용하는 편이 좋습니다.
이러한 작업을 잘 하기 위해서는 결국 다양한 Image에 다양한 방법으로 다양한 커널을 적용해보면서 감을 잡아야 합니다.
Composited Morphological Operations
앞서 소개한 두 Morphology Operation을 번갈아 한 번 씩 적용하는 작업 또한 상당히 빈번하게 사용됩니다.
한 번 변형한 Mask를 원래 크기로 되돌리기 위해서인데, 이러한 작업을 통해 원하는 노이즈만 제거된 결과를 얻을 수 있습니다.
작업을 진행하는 순서에 따라 아래 두 가지로 나눌 수 있습니다.
Erosion→Dilation의 경우 Open(열기)
Dilation의→Erosion 경우 Close(닫기)
Open은 1 값을 갖는 노이즈를 줄이고, Close는 0값을 갖는 노이즈를 줄이는데 사용됩니다.
ret, th = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
erosion = cv2.erode(th, kernel, iterations=1)
dilation = cv2.dilate(th, kernel, iterations=1)
opened = cv2.dilate(erosion, kernel, iterations=1)
closed = cv2.erode(dilation, kernel, iterations=1)
plt.subplot(221)
plt.imshow(th, cmap='gray')
plt.title('Binary Kitten')
plt.subplot(223)
plt.imshow(opened, cmap='gray')
plt.title('Opened Kitten')
plt.subplot(224)
plt.imshow(closed, cmap='gray')
plt.title('Closed Kitten')
plt.suptitle('Binary Kitten with double morphology', size=15)
plt.show()
Gray Scale Image에도 동일한 작업을 진행할 수 있습니다.
gray_open = cv2.dilate(gray_erosion, kernel, iterations=2)
gray_close = cv2.erode(gray_dilation, kernel, iterations=2)
plt.subplot(221)
plt.imshow(img, cmap='gray')
plt.title('Gray Kitten')
plt.subplot(223)
plt.imshow(gray_open, cmap='gray')
plt.title('Gray opened Kitten')
plt.subplot(224)
plt.imshow(gray_close, cmap='gray')
plt.title('Gray closed Kitten')
plt.suptitle('Gray Kitten with double morphology', size=15)
plt.show()
Conclusion
Morphology Operation은 주로 Binary Image로 생성한 RoI Mask를 다루는데 많이 쓰입니다.
개인적으로 RoI Mask의 Denoising에 매우 유용하게 사용하고 있고, Edge Detection 등의 연산과 연계하는 등 활용 방안이 아주 많으니 꼭 익혀두시는 것을 추천드립니다.
Reference