😏스레시홀딩

스레시홀딩(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년 오츠 노부유키가 개발한 알고리즘으로, 반복적인 시도 없이 한 번에 최적의 임계값을 찾을 수 있는 방법입니다.

알고리즘 원리

오츠의 알고리즘은 다음과 같이 작동합니다:

  1. 임계값을 임의로 정해 픽셀을 두 부류로 나눔

  2. 두 부류의 명암 분포를 계산

  3. 모든 경우의 수 중에서 두 부류의 명암 분포가 가장 균일할 때의 임계값을 선택

오츠 알고리즘 구현

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

참고 자료