모폴로지 연산
모폴로지 연산¶
개요¶
모폴로지 연산(Morphological Operations)은 이진 이미지나 그레이스케일 이미지의 형태를 기반으로 하는 연산입니다. 주로 노이즈 제거, 객체 분리, 홀 채우기 등에 사용됩니다. 이 문서에서는 구조 요소의 개념부터 다양한 모폴로지 연산의 활용까지 학습합니다.
난이도: ⭐⭐ (초급-중급)
학습 목표: - 구조 요소(Structuring Element) 이해 - 침식(Erosion)과 팽창(Dilation) 연산 - 열기(Opening)와 닫기(Closing) 연산 - 그래디언트, 탑햇, 블랙햇 연산 - 노이즈 제거 및 객체 분리 응용
목차¶
- 모폴로지 연산 개요
- 구조 요소 - getStructuringElement()
- 침식 - erode()
- 팽창 - dilate()
- 열기와 닫기 - morphologyEx()
- 그래디언트, 탑햇, 블랙햇
- 실전 응용
- 연습 문제
- 다음 단계
- 참고 자료
1. 모폴로지 연산 개요¶
모폴로지란?¶
┌─────────────────────────────────────────────────────────────────┐
│ 모폴로지 연산 개요 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 모폴로지(Morphology) = 형태학 │
│ 이미지의 형태(shape)를 기반으로 하는 연산 │
│ │
│ 주요 용도: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 노이즈 제거 - 작은 점 노이즈 제거 │ │
│ │ 2. 홀 채우기 - 객체 내부의 구멍 메우기 │ │
│ │ 3. 객체 분리 - 붙어있는 객체들 분리 │ │
│ │ 4. 객체 연결 - 떨어진 부분들 연결 │ │
│ │ 5. 엣지 검출 - 모폴로지 그래디언트 │ │
│ │ 6. 스켈레톤화 - 객체의 뼈대 추출 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 기본 연산: │
│ - 침식 (Erosion): 객체 축소 │
│ - 팽창 (Dilation): 객체 확장 │
│ │
│ 조합 연산: │
│ - 열기 (Opening) = 침식 → 팽창 │
│ - 닫기 (Closing) = 팽창 → 침식 │
│ │
└─────────────────────────────────────────────────────────────────┘
작동 원리¶
모폴로지 연산은 구조 요소(Structuring Element)라는 작은 마스크를 이미지 위로 이동시키며 픽셀 값을 결정합니다.
2. 구조 요소 - getStructuringElement()¶
구조 요소란?¶
┌─────────────────────────────────────────────────────────────────┐
│ 구조 요소 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 구조 요소 = 커널처럼 연산에 사용되는 작은 이진 행렬 │
│ │
│ 주요 형태: │
│ │
│ MORPH_RECT (직사각형) MORPH_CROSS (십자) MORPH_ELLIPSE │
│ ┌───┬───┬───┐ ┌───┬───┬───┐ ┌───┬───┬───┐ │
│ │ 1 │ 1 │ 1 │ │ 0 │ 1 │ 0 │ │ 0 │ 1 │ 0 │ │
│ ├───┼───┼───┤ ├───┼───┼───┤ ├───┼───┼───┤ │
│ │ 1 │ 1 │ 1 │ │ 1 │ 1 │ 1 │ │ 1 │ 1 │ 1 │ │
│ ├───┼───┼───┤ ├───┼───┼───┤ ├───┼───┼───┤ │
│ │ 1 │ 1 │ 1 │ │ 0 │ 1 │ 0 │ │ 0 │ 1 │ 0 │ │
│ └───┴───┴───┘ └───┴───┴───┘ └───┴───┴───┘ │
│ 모든 방향 영향 수직/수평만 영향 타원형 영향 │
│ │
│ 크기에 따른 효과: │
│ - 작은 크기 (3x3): 세밀한 처리 │
│ - 큰 크기 (7x7, 9x9): 강한 효과 │
│ │
└─────────────────────────────────────────────────────────────────┘
구조 요소 생성¶
import cv2
import numpy as np
# getStructuringElement(shape, ksize, anchor=(-1,-1))
# shape: 구조 요소 형태
# ksize: (width, height) 크기
# anchor: 기준점 (기본값: 중심)
# 직사각형
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
print("RECT (5x5):\n", rect_kernel)
# 십자형
cross_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
print("\nCROSS (5x5):\n", cross_kernel)
# 타원형
ellipse_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
print("\nELLIPSE (5x5):\n", ellipse_kernel)
# 커스텀 구조 요소
custom_kernel = np.array([
[0, 1, 0],
[1, 1, 1],
[0, 1, 0]
], dtype=np.uint8)
구조 요소 시각화¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
shapes = [
('RECT', cv2.MORPH_RECT),
('CROSS', cv2.MORPH_CROSS),
('ELLIPSE', cv2.MORPH_ELLIPSE)
]
sizes = [(5, 5), (7, 7), (11, 11)]
fig, axes = plt.subplots(len(shapes), len(sizes), figsize=(12, 10))
for i, (name, shape) in enumerate(shapes):
for j, size in enumerate(sizes):
kernel = cv2.getStructuringElement(shape, size)
axes[i, j].imshow(kernel, cmap='gray')
axes[i, j].set_title(f'{name} {size}')
axes[i, j].axis('off')
plt.tight_layout()
plt.show()
3. 침식 - erode()¶
침식 연산 원리¶
┌─────────────────────────────────────────────────────────────────┐
│ 침식 (Erosion) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 원리: │
│ - 구조 요소를 이미지 위로 이동 │
│ - 구조 요소 아래의 모든 픽셀이 1일 때만 중심 픽셀을 1로 │
│ - 하나라도 0이면 중심 픽셀은 0 │
│ │
│ 효과: │
│ - 전경(흰색) 영역 축소 │
│ - 작은 노이즈 제거 │
│ - 연결된 객체 분리 │
│ - 경계 부드럽게 │
│ │
│ 예시: │
│ 원본: 침식 후 (3x3): │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ████████████│ │ ████████ │ │
│ │ ████████████│ ──▶ │ ████████ │ │
│ │ ████████████│ │ ████████ │ │
│ │ ████████████│ │ │ │
│ └─────────────┘ └─────────────┘ │
│ 테두리 1픽셀씩 축소 │
│ │
│ 노이즈 제거: │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ██ ■ ████ │ │ ██ ███ │ │
│ │ ████ ████ │ ──▶ │ ██ ██ │ 작은 점(■) 제거 │
│ │ ■ ████ │ │ ███ │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
침식 사용법¶
import cv2
import numpy as np
# 이진 이미지 준비
img = cv2.imread('binary_image.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 구조 요소 생성
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# erode(src, kernel, iterations=1)
# iterations: 반복 횟수 (기본값 1)
eroded_1 = cv2.erode(binary, kernel, iterations=1)
eroded_2 = cv2.erode(binary, kernel, iterations=2)
eroded_3 = cv2.erode(binary, kernel, iterations=3)
cv2.imshow('Original', binary)
cv2.imshow('Eroded 1x', eroded_1)
cv2.imshow('Eroded 2x', eroded_2)
cv2.imshow('Eroded 3x', eroded_3)
cv2.waitKey(0)
cv2.destroyAllWindows()
침식 테스트 이미지 생성¶
import cv2
import numpy as np
# 테스트 이미지 생성
img = np.zeros((300, 400), dtype=np.uint8)
# 큰 사각형
cv2.rectangle(img, (50, 50), (150, 150), 255, -1)
# 작은 노이즈 점들
for _ in range(50):
x, y = np.random.randint(200, 350), np.random.randint(50, 250)
cv2.circle(img, (x, y), 2, 255, -1)
# 연결된 원
cv2.circle(img, (280, 150), 40, 255, -1)
cv2.circle(img, (320, 150), 40, 255, -1)
# 침식 적용
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
eroded = cv2.erode(img, kernel, iterations=1)
cv2.imshow('Original', img)
cv2.imshow('Eroded', eroded)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 팽창 - dilate()¶
팽창 연산 원리¶
┌─────────────────────────────────────────────────────────────────┐
│ 팽창 (Dilation) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 원리: │
│ - 구조 요소를 이미지 위로 이동 │
│ - 구조 요소 아래의 픽셀 중 하나라도 1이면 중심 픽셀을 1로 │
│ - 침식의 반대 연산 │
│ │
│ 효과: │
│ - 전경(흰색) 영역 확장 │
│ - 홀(구멍) 채우기 │
│ - 끊어진 부분 연결 │
│ - 객체 강조 │
│ │
│ 예시: │
│ 원본: 팽창 후 (3x3): │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ██████ │ │ ████████████│ │
│ │ ██████ │ ──▶ │ ████████████│ │
│ │ ██████ │ │ ████████████│ │
│ └─────────────┘ └─────────────┘ │
│ 테두리 1픽셀씩 확장 │
│ │
│ 끊어진 부분 연결: │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ ██ ██ │ │ ████ ████│ │
│ │ ██ .. ██ │ ──▶ │ ██████████ │ 점선이 연결됨 │
│ │ ██ ██ │ │ ████ ████│ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
팽창 사용법¶
import cv2
import numpy as np
# 이진 이미지 준비
img = cv2.imread('binary_image.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# dilate(src, kernel, iterations=1)
dilated_1 = cv2.dilate(binary, kernel, iterations=1)
dilated_2 = cv2.dilate(binary, kernel, iterations=2)
dilated_3 = cv2.dilate(binary, kernel, iterations=3)
cv2.imshow('Original', binary)
cv2.imshow('Dilated 1x', dilated_1)
cv2.imshow('Dilated 2x', dilated_2)
cv2.imshow('Dilated 3x', dilated_3)
cv2.waitKey(0)
cv2.destroyAllWindows()
침식과 팽창 비교¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 테스트 이미지
img = np.zeros((200, 200), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (150, 150), 255, -1)
cv2.circle(img, (100, 100), 20, 0, -1) # 내부 구멍
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
eroded = cv2.erode(img, kernel, iterations=1)
dilated = cv2.dilate(img, kernel, iterations=1)
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
axes[0].imshow(img, cmap='gray')
axes[0].set_title('Original')
axes[1].imshow(eroded, cmap='gray')
axes[1].set_title('Eroded (Shrink)')
axes[2].imshow(dilated, cmap='gray')
axes[2].set_title('Dilated (Expand)')
for ax in axes:
ax.axis('off')
plt.tight_layout()
plt.show()
5. 열기와 닫기 - morphologyEx()¶
열기 (Opening)¶
┌─────────────────────────────────────────────────────────────────┐
│ 열기 (Opening) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 열기 = 침식 → 팽창 │
│ │
│ 효과: │
│ - 작은 노이즈(점) 제거 │
│ - 객체의 전체 크기는 대략 유지 │
│ - 가느다란 연결부 끊기 │
│ │
│ 원본 침식 팽창 (열기 결과) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │██ ■ █│ │█ │ │██ █│ │
│ │██████│ ─▶ │ ████ │ ─▶ │██████│ │
│ │ ■ ██│ │ █ │ │ ██│ │
│ └──────┘ └──────┘ └──────┘ │
│ 작은 점(■) 제거됨 │
│ │
└─────────────────────────────────────────────────────────────────┘
닫기 (Closing)¶
┌─────────────────────────────────────────────────────────────────┐
│ 닫기 (Closing) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 닫기 = 팽창 → 침식 │
│ │
│ 효과: │
│ - 작은 구멍(홀) 채우기 │
│ - 객체의 전체 크기는 대략 유지 │
│ - 끊어진 부분 연결 │
│ │
│ 원본 팽창 침식 (닫기 결과) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │██████│ │██████│ │██████│ │
│ │██○ ██│ ─▶ │██████│ ─▶ │██████│ │
│ │██████│ │██████│ │██████│ │
│ └──────┘ └──────┘ └──────┘ │
│ 내부 구멍(○) 채워짐 │
│ │
└─────────────────────────────────────────────────────────────────┘
morphologyEx() 사용법¶
import cv2
import numpy as np
img = cv2.imread('binary_image.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# morphologyEx(src, op, kernel, iterations=1)
# op: 연산 종류
# 열기 (Opening): 노이즈 제거
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 닫기 (Closing): 홀 채우기
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 열기 후 닫기 (노이즈 제거 + 홀 채우기)
clean = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
clean = cv2.morphologyEx(clean, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Original', binary)
cv2.imshow('Opening', opening)
cv2.imshow('Closing', closing)
cv2.imshow('Open + Close', clean)
cv2.waitKey(0)
cv2.destroyAllWindows()
열기와 닫기 비교 테스트¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 테스트 이미지: 노이즈 + 구멍이 있는 사각형
img = np.zeros((200, 200), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (150, 150), 255, -1)
# 노이즈 추가 (작은 점들)
noise = img.copy()
for _ in range(30):
x, y = np.random.randint(10, 45), np.random.randint(10, 190)
cv2.circle(noise, (x, y), 2, 255, -1)
for _ in range(30):
x, y = np.random.randint(155, 190), np.random.randint(10, 190)
cv2.circle(noise, (x, y), 2, 255, -1)
# 구멍 추가 (객체 내부)
holes = noise.copy()
for _ in range(10):
x, y = np.random.randint(60, 140), np.random.randint(60, 140)
cv2.circle(holes, (x, y), 3, 0, -1)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
opening = cv2.morphologyEx(holes, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(holes, cv2.MORPH_CLOSE, kernel)
both = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
axes[0, 0].imshow(holes, cmap='gray')
axes[0, 0].set_title('Original (Noise + Holes)')
axes[0, 1].imshow(opening, cmap='gray')
axes[0, 1].set_title('Opening (Noise Removed)')
axes[1, 0].imshow(closing, cmap='gray')
axes[1, 0].set_title('Closing (Holes Filled)')
axes[1, 1].imshow(both, cmap='gray')
axes[1, 1].set_title('Open + Close')
for ax in axes.flatten():
ax.axis('off')
plt.tight_layout()
plt.show()
6. 그래디언트, 탑햇, 블랙햇¶
모폴로지 그래디언트¶
┌─────────────────────────────────────────────────────────────────┐
│ 모폴로지 그래디언트 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 그래디언트 = 팽창 - 침식 │
│ │
│ 효과: 객체의 윤곽선(경계) 추출 │
│ │
│ 원본 팽창 침식 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ ████ │ │██████│ │ ██ │ │
│ │ ████ │ - │██████│ = │ ██ │ │
│ │ ████ │ │██████│ │ ██ │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ 그래디언트 결과: │
│ ┌──────┐ │
│ │ ████ │ → 외곽선만 남음 │
│ │ █ █ │ │
│ │ ████ │ │
│ └──────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
탑햇과 블랙햇¶
┌─────────────────────────────────────────────────────────────────┐
│ 탑햇 / 블랙햇 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 탑햇 (Top Hat) = 원본 - 열기 │
│ - 밝은 영역에서 작은 밝은 부분 추출 │
│ - 배경보다 밝은 작은 객체 검출 │
│ │
│ 블랙햇 (Black Hat) = 닫기 - 원본 │
│ - 어두운 영역에서 작은 어두운 부분 추출 │
│ - 배경보다 어두운 작은 구멍/객체 검출 │
│ │
│ 활용: │
│ - 조명이 불균일한 이미지 보정 │
│ - 문서 이미지의 그림자 제거 │
│ - 작은 결함 검출 │
│ │
└─────────────────────────────────────────────────────────────────┘
구현 및 사용¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))
# 모폴로지 그래디언트
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
# 탑햇
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
# 블랙햇
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
# 수동 계산 (확인용)
dilated = cv2.dilate(img, kernel)
eroded = cv2.erode(img, kernel)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
gradient_manual = dilated - eroded
tophat_manual = img - opening
blackhat_manual = closing - img
# 시각화
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes[0, 0].imshow(img, cmap='gray')
axes[0, 0].set_title('Original')
axes[0, 1].imshow(gradient, cmap='gray')
axes[0, 1].set_title('Gradient (Edge)')
axes[0, 2].imshow(tophat, cmap='gray')
axes[0, 2].set_title('Top Hat (Bright spots)')
axes[1, 0].imshow(blackhat, cmap='gray')
axes[1, 0].set_title('Black Hat (Dark spots)')
# 탑햇 + 블랙햇으로 대비 향상
enhanced = cv2.add(img, tophat)
enhanced = cv2.subtract(enhanced, blackhat)
axes[1, 1].imshow(enhanced, cmap='gray')
axes[1, 1].set_title('Enhanced (Top+Black Hat)')
for ax in axes.flatten():
ax.axis('off')
axes[1, 2].axis('off')
plt.tight_layout()
plt.show()
모든 모폴로지 연산 정리¶
import cv2
# morphologyEx()에서 사용 가능한 연산 목록
operations = {
cv2.MORPH_ERODE: "침식 (Erode)",
cv2.MORPH_DILATE: "팽창 (Dilate)",
cv2.MORPH_OPEN: "열기 (Open = Erode + Dilate)",
cv2.MORPH_CLOSE: "닫기 (Close = Dilate + Erode)",
cv2.MORPH_GRADIENT: "그래디언트 (Dilate - Erode)",
cv2.MORPH_TOPHAT: "탑햇 (Src - Open)",
cv2.MORPH_BLACKHAT: "블랙햇 (Close - Src)",
cv2.MORPH_HITMISS: "히트미스 (패턴 매칭)"
}
for op, name in operations.items():
print(f"{op}: {name}")
7. 실전 응용¶
노이즈 제거 파이프라인¶
import cv2
import numpy as np
def remove_noise_morphology(binary_img, noise_size=3):
"""
모폴로지 연산으로 노이즈 제거
Parameters:
- binary_img: 이진 이미지
- noise_size: 제거할 노이즈의 최대 크기
"""
# 커널 크기 = 노이즈 크기 * 2 + 1
kernel_size = noise_size * 2 + 1
kernel = cv2.getStructuringElement(
cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)
)
# 열기로 작은 점 노이즈 제거
cleaned = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
# 닫기로 작은 구멍 채우기
cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel)
return cleaned
# 사용 예
img = cv2.imread('noisy_document.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
cleaned = remove_noise_morphology(binary, noise_size=2)
객체 분리¶
import cv2
import numpy as np
def separate_objects(binary_img, erosion_iterations=3):
"""
붙어있는 객체들을 분리
"""
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# 침식으로 객체 축소 (연결부 끊기)
eroded = cv2.erode(binary_img, kernel, iterations=erosion_iterations)
# 거리 변환으로 중심점 찾기 (선택적)
dist_transform = cv2.distanceTransform(eroded, cv2.DIST_L2, 5)
_, sure_fg = cv2.threshold(
dist_transform, 0.5 * dist_transform.max(), 255, 0
)
sure_fg = np.uint8(sure_fg)
return eroded, sure_fg
# 사용 예
img = cv2.imread('connected_circles.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
separated, centers = separate_objects(binary)
문서 이미지 전처리¶
import cv2
import numpy as np
def preprocess_document(img):
"""
문서 이미지 전처리 (그림자 제거 + 이진화)
"""
# 그레이스케일 변환
if len(img.shape) == 3:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
gray = img
# 탑햇으로 밝은 배경 추출
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
# 블랙햇으로 그림자/어두운 부분 보정
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel)
# 원본에서 블랙햇 빼기 (그림자 제거 효과)
no_shadow = cv2.add(gray, blackhat)
# 적응형 이진화
binary = cv2.adaptiveThreshold(
no_shadow, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 21, 15
)
# 노이즈 제거
kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_small)
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel_small)
return binary
# 사용 예
img = cv2.imread('document_with_shadow.jpg')
result = preprocess_document(img)
스켈레톤화 (Skeletonization)¶
import cv2
import numpy as np
def skeletonize(img):
"""
모폴로지 연산으로 스켈레톤(뼈대) 추출
"""
skeleton = np.zeros_like(img)
temp = img.copy()
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
while True:
# 열기 연산
opened = cv2.morphologyEx(temp, cv2.MORPH_OPEN, kernel)
# 차이 계산
diff = cv2.subtract(temp, opened)
# 침식
temp = cv2.erode(temp, kernel)
# 스켈레톤에 추가
skeleton = cv2.bitwise_or(skeleton, diff)
# 더 이상 흰색 픽셀이 없으면 종료
if cv2.countNonZero(temp) == 0:
break
return skeleton
# 사용 예
img = cv2.imread('character.png', cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
skeleton = skeletonize(binary)
8. 연습 문제¶
연습 1: 구조 요소 효과 비교¶
동일한 이진 이미지에 대해 세 가지 구조 요소(RECT, CROSS, ELLIPSE)를 사용하여 침식과 팽창을 적용하고, 결과의 차이를 분석하세요.
연습 2: 문자 두께 조절¶
손글씨 이미지에서 문자의 두께를 조절하는 함수를 작성하세요: - 양수 값: 팽창으로 두껍게 - 음수 값: 침식으로 얇게
def adjust_stroke_width(img, amount):
"""
amount > 0: 두껍게
amount < 0: 얇게
"""
pass
연습 3: 경계 추출 비교¶
다음 세 가지 방법으로 객체의 경계를 추출하고 비교하세요: 1. 모폴로지 그래디언트 2. Canny 엣지 검출 3. findContours
연습 4: 점자 인식 전처리¶
점자 이미지에서 각 점을 개별적으로 검출하기 위한 전처리 파이프라인을 설계하세요. (힌트: 침식으로 점들을 분리)
연습 5: 세포 분리 (Watershed 전처리)¶
현미경 세포 이미지에서 붙어있는 세포들을 분리하기 위한 전처리를 구현하세요: 1. 이진화 2. 노이즈 제거 (열기/닫기) 3. 확실한 배경 영역 찾기 (팽창) 4. 확실한 전경 영역 찾기 (거리 변환 + 임계값)
9. 다음 단계¶
07_Thresholding.md에서 다양한 이진화 방법과 임계처리 기법을 학습합니다!
다음에 배울 내용:
- 전역 임계처리 (cv2.threshold)
- OTSU 자동 임계값
- 적응형 임계처리
- HSV 기반 임계처리
10. 참고 자료¶
공식 문서¶
관련 학습 자료¶
| 폴더 | 관련 내용 |
|---|---|
| 05_Image_Filtering.md | 필터링 기초 |
| 09_Contours.md | 전처리 후 윤곽선 검출 |