이진 이미지와 Thresholding
실습 준비
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
newspaper = cv.imread("newspaper.png", cv.IMREAD_GRAYSCALE)
이미지 표시
show() 함수는 OpenCV와 이미지·영상 입출력에서 정의한 함수를 사용함.
newspaper.shape, newspaper.dtype
이진 이미지
이진 이미지(binary image)는 픽셀값이 두 종류뿐인 이미지임. OpenCV에서는 일반적으로 uint8 자료형의 0과 255를 사용함.
| 픽셀값 | 일반적인 의미 |
|---|---|
| 0 | 배경 또는 선택하지 않은 영역 |
| 255 | 전경 또는 선택한 영역 |
마스크도 이진 이미지임. 색상 범위 대신 그레이스케일의 밝기를 기준으로도 이진 이미지 생성 가능함.
밝기 분포와 문턱값
그레이스케일 히스토그램은 각 밝기 값에 해당하는 픽셀 수를 나타냄. 배경과 글자의 밝기 분포가 나뉘어 있다면 두 집단 사이의 값을 문턱값(threshold)으로 선택 가능함.
hist = cv.calcHist([newspaper], [0], None, [256], [0, 256])
plt.plot(hist)
plt.xlim([0, 256])
plt.xlabel("픽셀 값")
plt.ylabel("픽셀 수")
plt.show()


전역 Thresholding
전역 Thresholding은 이미지 전체에 하나의 문턱값을 적용함. THRESH_BINARY에서는 문턱값보다 큰 픽셀을 최댓값으로, 나머지를 0으로 변경함.
used_threshold, binary = cv.threshold(
newspaper,
190,
255,
cv.THRESH_BINARY,
)
used_threshold # 사용한 문턱값
np.unique(binary) # 이미지의 픽셀값 종류 확인
이미지 보기
show(binary)

cv.threshold()는 실제로 사용한 문턱값과 결과 이미지를 함께 반환함. 직접 지정한 전역 Thresholding에서는 첫 번째 반환값이 입력한 문턱값과 같음.
문턱값 비교
문턱값을 낮추면 더 많은 픽셀이 최댓값인 흰색이 되고, 높이면 더 많은 픽셀이 0인 검정이 됨.
threshold_values = [80, 120, 150, 190]
binary_images = [
cv.threshold(newspaper, value, 255, cv.THRESH_BINARY)[1]
for value in threshold_values
]
comparison = cv.hconcat(binary_images)
show(comparison)
왼쪽부터 문턱값 80, 120, 150, 190의 결과임. 너무 낮거나 높으면 글자와 배경의 세부 정보가 함께 사라질 수 있음.

Thresholding 방식
같은 문턱값도 처리 방식에 따라 결과가 달라짐.
| 방식 | 문턱값보다 큰 픽셀 | 나머지 픽셀 |
|---|---|---|
THRESH_BINARY | 최댓값 | 0 |
THRESH_BINARY_INV | 0 | 최댓값 |
THRESH_TRUNC | 문턱값 | 원래 값 |
THRESH_TOZERO | 원래 값 | 0 |
THRESH_TOZERO_INV | 0 | 원래 값 |
THRESH_BINARY_INV는 전경을 검정, 배경을 흰색으로 만들고 싶을 때 사용 가능함.
_, binary_inverse = cv.threshold(
newspaper,
190,
255,
cv.THRESH_BINARY_INV,
)
show(binary_inverse)

Otsu Thresholding
문턱값을 직접 정하기 어려우면 Otsu 알고리즘으로 자동 선택 가능함. Otsu 알고리즘은 픽셀을 두 집단으로 나눌 때 각 집단 내부의 밝기 분산이 작아지는 문턱값을 선택함.
THRESH_OTSU를 사용할 때 문턱값 인자는 0으로 두며 실제 선택된 값은 첫 번째 반환값으로 확인함.
otsu_threshold, binary_otsu = cv.threshold(
newspaper,
0,
255,
cv.THRESH_BINARY | cv.THRESH_OTSU,
)
otsu_threshold # 문턱값
이미지 보기
show(binary_otsu)

Otsu가 잘 동작하는 조건
전경과 배경의 밝기 분포가 비교적 분명하게 나뉜 이미지에 적합함. 조명이 고르지 않거나 밝기 분포가 많이 겹치면 하나의 전역 문턱값만으로 분리하기 어려움.
전역 문턱값의 한계
사진의 위치마다 조명이 다르면 같은 물체도 서로 다른 밝기 값을 가짐. 이미지 전체에 하나의 문턱값을 적용하면 밝은 영역이나 어두운 영역 중 한쪽이 사라질 수 있음.
sudoku = cv.imread("sudoku.png", cv.IMREAD_GRAYSCALE)
if sudoku is None:
raise FileNotFoundError("이미지를 읽을 수 없습니다")
_, global_binary = cv.threshold(
sudoku,
0,
255,
cv.THRESH_BINARY | cv.THRESH_OTSU,
)
show(global_binary)



적응형 Thresholding
적응형 Thresholding(adaptive thresholding)은 각 픽셀 주변 영역에서 지역 문턱값을 계산함. 조명이 고르지 않은 문서나 사진에 유용함.
block_size = 15
c = 5
adaptive_mean = cv.adaptiveThreshold(
sudoku,
255,
cv.ADAPTIVE_THRESH_MEAN_C,
cv.THRESH_BINARY,
block_size,
c,
)
adaptive_gaussian = cv.adaptiveThreshold(
sudoku,
255,
cv.ADAPTIVE_THRESH_GAUSSIAN_C,
cv.THRESH_BINARY,
block_size,
c,
)
block_size: 문턱값 계산에 참고할 이웃 영역의 크기. 1보다 큰 홀수c: 이웃 영역에서 계산한 값에서 뺄 상수ADAPTIVE_THRESH_MEAN_C: 이웃 픽셀의 산술 평균 사용ADAPTIVE_THRESH_GAUSSIAN_C: 가까운 픽셀에 더 큰 가중치를 둔 평균 사용
show(adaptive_mean)

show(adaptive_gaussian)

block_size가 너무 작으면 작은 밝기 변화와 잡음에 민감해지고, 너무 크면 지역 조명 차이를 충분히 반영하지 못할 수 있음. 이미지의 글자나 물체 크기에 맞춰 조정함.