logo

이진 이미지와 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)

문턱값 190을 적용한 신문 이진 이미지

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_INV0최댓값
THRESH_TRUNC문턱값원래 값
THRESH_TOZERO원래 값0
THRESH_TOZERO_INV0원래 값

THRESH_BINARY_INV는 전경을 검정, 배경을 흰색으로 만들고 싶을 때 사용 가능함.

_, binary_inverse = cv.threshold(
    newspaper,
    190,
    255,
    cv.THRESH_BINARY_INV,
)
show(binary_inverse)

Thresholding 방식별 픽셀 변환

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 알고리즘이 자동으로 선택한 문턱값의 결과

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

Previous
마스크와 색상 기반 탐지