8장. Threshold, Adaptive Threshold, Otsu
Otsu 알고리즘
반 학생들을 "잘하는 무리"와 "더 노력할 무리"로 나누는 선생님을 떠올려 봅시다. 커트라인을 50점에도 두어 보고, 60점에도, 70점에도 두어 봅니다. 그때마다 두 무리의 평균 점수를 냅니다. 두 무리의 평균이 가장 멀리 떨어지는 커트라인을 고르면, 무리마다 비슷한 학생끼리 모이고 두 무리는 또렷이 갈립니다. 1979년 일본의 오츠 노부유키(大津展之)가 발표한 Otsu 알고리즘이 바로 이 일을 밝기에 합니다.
그림의 다섯 단계를 0~255 의 후보마다 되풀이하고, 점수가 가장 높은 후보를 임계값으로 고릅니다. 점수는 "왼쪽 무리의 비율 × 오른쪽 무리의 비율 × (두 평균의 차)²" 입니다. 여기서 선생님이 시험 삼아 두는 커트라인이 후보, 두 무리의 평균이 멀리 떨어질수록 높아지는 숫자가 점수(무리 사이 분산)입니다.
손으로 계산해 보기
픽셀 8개의 밝기 30, 40, 50, 90, 120, 160, 170, 180 에서 후보 세 개를 견줍니다. 후보 이하가 왼쪽 무리, 후보보다 큰 칸이 오른쪽 무리입니다.
| 후보 | 왼쪽 무리 | 오른쪽 무리 | 두 비율 | 두 평균 | 점수 = 비율 × 비율 × 차이² |
|---|---|---|---|---|---|
| 60 | 30, 40, 50 | 90, 120, 160, 170, 180 | 3/8, 5/8 | 40, 144 | 0.375 × 0.625 × 104² = 2535 |
| 100 | 30, 40, 50, 90 | 120, 160, 170, 180 | 4/8, 4/8 | 52.5, 157.5 | 0.5 × 0.5 × 105² = 2756.25 |
| 140 | 30, 40, 50, 90, 120 | 160, 170, 180 | 5/8, 3/8 | 66, 170 | 0.625 × 0.375 × 104² = 2535 |
후보 100 의 점수가 가장 큽니다. 90 과 120 사이(90~119)의 어느 후보든 무리가 똑같이 나뉘므로 점수도 같습니다. OpenCV 는 이런 동점 가운데 가장 작은 90 을 돌려줍니다.
비율을 곱하는 까닭은 한쪽 무리가 아주 작을 때 점수를 낮추려는 것입니다. 픽셀 하나만 떼어 낸 무리는 평균이 멀리 떨어져 보여도 쓸모 있는 나눔이 아니기 때문입니다.
부품 그림에서 후보 0~255 의 점수를 모두 계산해 다홍 선으로 그렸습니다(청록 막대는 히스토그램). 점수는 부품 봉우리를 지나며 오르고, 골짜기에서 거의 평평하다가, 작업대 봉우리에서 뚝 떨어집니다. 가장 높은 곳은 136 으로, 앞 절에서 눈으로 고른 골짜기와 같습니다.
OpenCV 로 Otsu 쓰기
| 할 일 | 쓰는 법 |
|---|---|
| Otsu 켜기 | type 에 cv2.THRESH_BINARY + cv2.THRESH_OTSU 처럼 더해 준다 |
| thresh 인자 | 무시되므로 보통 0 을 준다 |
| 고른 값 읽기 | 돌려주는 첫 값 ret 이 Otsu 가 고른 임계값이다 |
| 글자·물체를 흰색으로 | cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU |
짧은 예시
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 로도 풀리지 않습니다.