스레시홀딩(Thresholding)
스레시홀딩은 바이너리 이미지를 만드는 가장 대표적인 방법으로, 이미지 처리에서 핵심적인 기술입니다. 이 문서에서는 OpenCV를 사용한 다양한 스레시홀딩 기법들을 상세히 다룹니다.
스레시홀딩 개요
스레시홀딩(Thresholding)은 여러 값을 어떤 임계점을 기준으로 두 가지 부류로 나누는 방법입니다. 바이너리 이미지(Binary Image)란 검은색(0)과 흰색(255)만으로 표현한 이미지를 의미합니다.
전역 스레시홀딩(Global Thresholding)
전역 스레시홀딩은 하나의 임계값을 정한 뒤 픽셀 값이 임계값을 넘으면 255, 넘지 않으면 0으로 지정하는 방식입니다.
NumPy를 이용한 기본 구현
import cv2
import numpy as np
import matplotlib.pylab as plt
# 이미지를 그레이 스케일로 읽기
img = cv2.imread('../img/gray_gradient.jpg', cv2.IMREAD_GRAYSCALE)
# NumPy API로 바이너리 이미지 만들기
thresh_np = np.zeros_like(img) # 원본과 동일한 크기의 0으로 채워진 이미지
thresh_np[img > 127] = 255 # 127보다 큰 값만 255로 변경
cv2.threshold() 함수 사용법
OpenCV에서 제공하는 cv2.threshold() 함수를 사용하면 더 편리하게 스레시홀딩을 수행할 수 있습니다.
# OpenCV API로 바이너리 이미지 만들기
ret, thresh_cv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
print(ret) # 127.0, 바이너리 이미지에 사용된 임계값 반환
함수 구문:
ret, out = cv2.threshold(img, threshold, value, type_flag)
매개변수 설명:
-
img: 변환할 이미지 -
threshold: 스레시홀딩 임계값 -
value: 임계값 기준에 만족하는 픽셀에 적용할 값 -
type_flag: 스레시홀딩 적용 방법
반환값:
-
ret: 스레시홀딩에 사용한 임계값 -
out: 스레시홀딩이 적용된 바이너리 이미지
스레시홀딩 타입 플래그
OpenCV는 다양한 스레시홀딩 방식을 지원합니다:
-
cv2.THRESH_BINARY: 픽셀 값이 임계값을 넘으면 value로 지정, 넘지 못하면 0으로 지정 -
cv2.THRESH_BINARY_INV: cv2.THRESH_BINARY의 반대 -
cv2.THRESH_TRUNC: 픽셀 값이 임계값을 넘으면 value로 지정, 넘지 못하면 원래 값 유지 -
cv2.THRESH_TOZERO: 픽셀 값이 임계값을 넘으면 원래 값 유지, 넘지 못하면 0으로 지정 -
cv2.THRESH_TOZERO_INV: cv2.THRESH_TOZERO의 반대
# 다양한 스레시홀딩 타입 예제
import cv2
import numpy as np
import matplotlib.pylab as plt
img = cv2.imread('../img/gray_gradient.jpg', cv2.IMREAD_GRAYSCALE)
_, t_bin = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
_, t_bininv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
_, t_truc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
_, t_2zr = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
_, t_2zrinv = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
# 결과 시각화
imgs = {'Original': img, 'BINARY': t_bin, 'BINARY_INV': t_bininv,
'TRUNC': t_truc, 'TOZERO': t_2zr, 'TOZERO_INV': t_2zrinv}
for i, (key, value) in enumerate(imgs.items()):
plt.subplot(2, 3, i+1)
plt.title(key)
plt.imshow(value, cmap='gray')
plt.xticks([])
plt.yticks([])
plt.show()
오츠의 이진화 알고리즘(Otsu's Method)
1979년 오츠 노부유키가 개발한 알고리즘으로, 반복적인 시도 없이 한 번에 최적의 임계값을 찾을 수 있는 방법입니다.
알고리즘 원리
오츠의 알고리즘은 다음과 같이 작동합니다:
-
임계값을 임의로 정해 픽셀을 두 부류로 나눔
-
두 부류의 명암 분포를 계산
-
모든 경우의 수 중에서 두 부류의 명암 분포가 가장 균일할 때의 임계값을 선택
오츠 알고리즘 구현
import cv2
import numpy as np
import matplotlib.pylab as plt
# 이미지를 그레이 스케일로 읽기
img = cv2.imread('../img/scaned_paper.jpg', cv2.IMREAD_GRAYSCALE)
# 수동으로 임계값을 130으로 지정
_, t_130 = cv2.threshold(img, 130, 255, cv2.THRESH_BINARY)
# 오츔 알고리즘을 사용한 자동 임계값 설정
t, t_otsu = cv2.threshold(img, -1, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print('오츠 알고리즘 임계값:', t) # 자동으로 선택된 최적 임계값 출력
# 결과 비교
imgs = {'Original': img, 't:130': t_130, 'otsu:%d' % t: t_otsu}
for i, (key, value) in enumerate(imgs.items()):
plt.subplot(1, 3, i+1)
plt.title(key)
plt.imshow(value, cmap='gray')
plt.xticks([])
plt.yticks([])
plt.show()
주요 특징:
-
장점: 최적의 임계값을 자동으로 찾아줌
-
단점: 모든 경우의 수를 조사해야 하므로 처리 속도가 느림
-
활용: 스캔된 문서, 텍스트 이미지 등에서 효과적
적응형 스레시홀딩(Adaptive Thresholding)
전역 스레시홀딩은 조명이 일정하지 않거나 배경색이 여러 개인 경우에는 좋지 않은 결과를 낼 수 있습니다. 적응형 스레시홀딩은 이미지를 여러 영역으로 나눈 뒤, 각 영역의 주변 픽셀 값만 활용하여 임계값을 구하는 방법입니다.
cv2.adaptiveThreshold() 함수
cv2.adaptiveThreshold(img, value, method, type_flag, block_size, C)
매개변수 설명:
-
img: 입력 영상 -
value: 임계값을 만족하는 픽셀에 적용할 값 -
method: 임계값 결정 방법 -
type_flag: 스레시홀딩 적용 방법 (cv2.threshold()와 동일) -
block_size: 영역으로 나눌 이웃의 크기(n×n), 반드시 홀수 -
C: 계산된 임계값 결과에서 가감할 상수(음수 가능)
임계값 결정 방법
-
cv2.ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 평균으로 결정 -
cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 가우시안 분포에 따른 가중치의 합으로 결정
적응형 스레시홀딩 구현 예제
import cv2
import numpy as np
import matplotlib.pyplot as plt
blk_size = 9 # 블록 사이즈 (홀수여야 함)
C = 5 # 차감 상수
# 그레이 스케일로 이미지 읽기
img = cv2.imread('../img/sudoku.png', cv2.IMREAD_GRAYSCALE)
# 오츠의 알고리즘으로 전역 스레시홀딩
ret, th1 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 적응형 스레시홀딩 - 평균 방법
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, blk_size, C)
# 적응형 스레시홀딩 - 가우시안 방법
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, blk_size, C)
# 결과 비교
imgs = {'Original': img, 'Global-Otsu:%d' % ret: th1,
'Adapted-Mean': th2, 'Adapted-Gaussian': th3}
for i, (k, v) in enumerate(imgs.items()):
plt.subplot(2, 2, i+1)
plt.title(k)
plt.imshow(v, 'gray')
plt.xticks([])
plt.yticks([])
plt.show()
적응형 스레시홀딩의 특징
평균 방법 (ADAPTIVE_THRESH_MEAN_C):
-
더 선명한 결과
-
약간의 잡음 발생 가능
가우시안 방법 (ADAPTIVE_THRESH_GAUSSIAN_C):
-
평균 방법 대비 선명도는 낮음
-
잡음이 적은 부드러운 결과
사용 권장 상황:
-
조명이 일정하지 않은 이미지
-
그림자가 있는 이미지
-
배경색이 여러 개인 복잡한 이미지
스레시홀딩 방법 선택 가이드
전역 스레시홀딩 사용 시기
-
조명이 균일한 이미지
-
배경과 객체의 대비가 명확한 경우
-
간단하고 빠른 처리가 필요한 경우
오츠 알고리즘 사용 시기
-
최적의 임계값을 자동으로 찾고 싶을 때
-
스캔된 문서나 텍스트 이미지 처리
-
이미지의 히스토그램이 이중 모드인 경우
적응형 스레시홀딩 사용 시기
-
조명 조건이 일정하지 않은 이미지
-
그림자나 반사가 있는 이미지
-
복잡한 배경을 가진 이미지
-
대부분의 실제 이미지 처리 상황
실전 팁과 주의사항
매개변수 튜닝 가이드
block_size 선택:
-
작은 값 (3, 5, 7): 세밀한 디테일 보존, 잡음 증가
-
큰 값 (11, 15, 21): 부드러운 결과, 디테일 손실
C 값 조정:
-
양수: 더 많은 픽셀이 검은색으로 변환
-
음수: 더 많은 픽셀이 흰색으로 변환
-
0: 순수한 평균값 또는 가우시안 가중치 사용
성능 최적화
# 이미지 크기가 클 때는 전처리로 크기 조정
def resize_image(img, max_width=800):
if img.shape[1] > max_width:
ratio = max_width / img.shape[1]
new_height = int(img.shape[0] * ratio)
return cv2.resize(img, (max_width, new_height))
return img
# 가우시안 블러로 노이즈 제거 후 스레시홀딩
def preprocess_and_threshold(img):
# 노이즈 제거
blurred = cv2.GaussianBlur(img, (5, 5), 0)
# 적응형 스레시홀딩 적용
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
return thresh