OpenCV 비전 실무8장 · Threshold, Adaptive Threshold, Otsu10 / 21← 이전목차다음 →MetaContents

8장. Threshold, Adaptive Threshold, Otsu

Otsu 알고리즘

반 학생들을 "잘하는 무리"와 "더 노력할 무리"로 나누는 선생님을 떠올려 봅시다. 커트라인을 50점에도 두어 보고, 60점에도, 70점에도 두어 봅니다. 그때마다 두 무리의 평균 점수를 냅니다. 두 무리의 평균이 가장 멀리 떨어지는 커트라인을 고르면, 무리마다 비슷한 학생끼리 모이고 두 무리는 또렷이 갈립니다. 1979년 일본의 오츠 노부유키(大津展之)가 발표한 Otsu 알고리즘이 바로 이 일을 밝기에 합니다.

후보 하나, 두 무리, 두 평균, 점수, 최고 점수의 다섯 단계 흐름도

그림의 다섯 단계를 0~255 의 후보마다 되풀이하고, 점수가 가장 높은 후보를 임계값으로 고릅니다. 점수는 "왼쪽 무리의 비율 × 오른쪽 무리의 비율 × (두 평균의 차)²" 입니다. 여기서 선생님이 시험 삼아 두는 커트라인이 후보, 두 무리의 평균이 멀리 떨어질수록 높아지는 숫자가 점수(무리 사이 분산)입니다.

손으로 계산해 보기

픽셀 8개의 밝기 30, 40, 50, 90, 120, 160, 170, 180 에서 후보 세 개를 견줍니다. 후보 이하가 왼쪽 무리, 후보보다 큰 칸이 오른쪽 무리입니다.

후보왼쪽 무리오른쪽 무리두 비율두 평균점수 = 비율 × 비율 × 차이²
6030, 40, 5090, 120, 160, 170, 1803/8, 5/840, 1440.375 × 0.625 × 104² = 2535
10030, 40, 50, 90120, 160, 170, 1804/8, 4/852.5, 157.50.5 × 0.5 × 105² = 2756.25
14030, 40, 50, 90, 120160, 170, 1805/8, 3/866, 1700.625 × 0.375 × 104² = 2535

후보 100 의 점수가 가장 큽니다. 90 과 120 사이(90~119)의 어느 후보든 무리가 똑같이 나뉘므로 점수도 같습니다. OpenCV 는 이런 동점 가운데 가장 작은 90 을 돌려줍니다.

비율을 곱하는 까닭은 한쪽 무리가 아주 작을 때 점수를 낮추려는 것입니다. 픽셀 하나만 떼어 낸 무리는 평균이 멀리 떨어져 보여도 쓸모 있는 나눔이 아니기 때문입니다.

부품 그림의 히스토그램 위에 후보 임계값마다의 Otsu 점수 곡선을 겹쳐 그린 그래프

부품 그림에서 후보 0~255 의 점수를 모두 계산해 다홍 선으로 그렸습니다(청록 막대는 히스토그램). 점수는 부품 봉우리를 지나며 오르고, 골짜기에서 거의 평평하다가, 작업대 봉우리에서 뚝 떨어집니다. 가장 높은 곳은 136 으로, 앞 절에서 눈으로 고른 골짜기와 같습니다.

OpenCV 로 Otsu 쓰기

할 일쓰는 법
Otsu 켜기type 에 cv2.THRESH_BINARY + cv2.THRESH_OTSU 처럼 더해 준다
thresh 인자무시되므로 보통 0 을 준다
고른 값 읽기돌려주는 첫 값 ret 이 Otsu 가 고른 임계값이다
글자·물체를 흰색으로cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU

짧은 예시

Python
import cv2
import numpy as np

pixels = np.array([[30, 40, 50, 90, 120, 160, 170, 180]], dtype=np.uint8)
ret, out = cv2.threshold(pixels,
                         0,                                     # thresh: Otsu 가 고르므로 0
                         255,                                   # maxval
                         cv2.THRESH_BINARY + cv2.THRESH_OTSU)   # 방식 + Otsu 켜기
print(ret)              # 결과: 90.0 → 손계산의 동점 구간(90~119)에서 가장 작은 값
print(out.tolist())     # 결과: [[0, 0, 0, 0, 255, 255, 255, 255]]

주의할 점

  • Otsu 는 흑백(채널 하나) uint8 그림에만 됩니다. 컬러 그림을 넣으면 오류가 납니다.
  • Otsu 는 히스토그램만 봅니다. 그림의 어느 자리에 어떤 밝기가 있는지는 모르므로, 앞 절의 그늘 문제는 Otsu 로도 풀리지 않습니다.
← 이전 절8장 · blockSize 와 C 의 효과다음 절 →8장 · Otsu 가 잘 안 맞는 경우와 블러 먼저 하기