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

8장. Threshold, Adaptive Threshold, Otsu

Adaptive Threshold

학교가 반마다 커트라인을 따로 정한다고 해 봅시다. 시험이 어려웠던 반은 커트라인을 낮추고, 쉬웠던 반은 높입니다. 그러면 반마다 "그 반에서 잘한 학생"을 고를 수 있습니다. Adaptive Threshold 는 칸마다 주변 동네(블록)의 평균 밝기를 보고 커트라인을 따로 정합니다. 그늘진 동네는 그늘진 동네끼리 견주는 셈입니다.

칸 하나, 주변 블록, 기준 정하기, 견주기의 네 단계 흐름도

그림의 네 단계를 칸 하나하나에 되풀이합니다. 지금 정할 칸을 고르고, 그 칸을 가운데에 둔 blockSize × blockSize 네모의 평균을 내고, 평균에서 C 를 빼 기준을 만들고, 칸의 밝기가 기준보다 크면 흰색입니다. 여기서 반 하나가 블록이고, 반마다의 커트라인이 "주변 평균 − C"입니다.

그늘진 안내문의 원본, 전역 임계값 127 결과, Adaptive Threshold 결과를 나란히 놓은 그림

가운데의 전역 임계값은 오른쪽 그늘을 검게 칠했지만, 오른쪽 Adaptive 결과(blockSize 15, C 10)는 그늘 속 글자까지 또렷합니다. 칸마다 기준이 주변 종이의 밝기를 따라 내려가기 때문입니다.

인자 여섯 개

순서이름보통 값뜻
1srcgray흑백 그림(uint8)
2maxValue255기준을 넘은 칸에 넣을 값
3adaptiveMethodcv2.ADAPTIVE_THRESH_GAUSSIAN_C주변 평균을 내는 법(MEAN_C 또는 GAUSSIAN_C)
4thresholdTypecv2.THRESH_BINARYBINARY 나 BINARY_INV 둘 가운데 하나
5blockSize15둘러볼 네모의 한 변. 1 보다 큰 홀수
6C10주변 평균에서 뺄 값

cv2.threshold 와 달리 결과 그림 하나만 돌려줍니다. 칸마다 기준이 다르니 "쓴 임계값" 하나를 돌려줄 수 없기 때문입니다.

손으로 계산해 보기

blockSize 3, C 10, ADAPTIVE_THRESH_MEAN_C 로 가운데 칸 하나를 정해 봅니다. 밝은 곳의 글자 칸, 그늘 속 글자 칸, 그늘 속 종이 칸 세 경우입니다.

경우주변 3×3 의 밝기평균기준(평균 − 10)가운데 칸결과
밝은 곳의 글자180, 190, 200, 185, 120, 195, 190, 200, 1801640 ÷ 9 = 182.2172.2120기준보다 작다 → 0
그늘 속 글자60, 70, 65, 62, 30, 68, 64, 66, 70555 ÷ 9 = 61.751.730기준보다 작다 → 0
그늘 속 종이60, 70, 65, 62, 66, 68, 64, 66, 70591 ÷ 9 = 65.755.766기준보다 크다 → 255

그늘 속 종이(66)는 전역 임계값 127 이면 검정이 되지만, 주변 평균에 맞춘 기준(55.7)보다는 커서 흰색이 됩니다.

짧은 예시

Python
import cv2
import numpy as np

# 위 표의 둘째, 셋째 경우를 3×3 그림으로 만든다
text = np.array([[60, 70, 65], [62, 30, 68], [64, 66, 70]], dtype=np.uint8)
paper = np.array([[60, 70, 65], [62, 66, 68], [64, 66, 70]], dtype=np.uint8)
for name, block in [("그늘 속 글자", text), ("그늘 속 종이", paper)]:
    out = cv2.adaptiveThreshold(block,                        # src
                                255,                          # maxValue
                                cv2.ADAPTIVE_THRESH_MEAN_C,   # 주변을 똑같이 더해 나눈 평균
                                cv2.THRESH_BINARY,            # 기준보다 크면 255
                                3,                            # blockSize: 3×3 네모
                                10)                           # C: 평균에서 10 을 뺀다
    print(name, "가운데 칸:", int(out[1, 1]))
# 결과: 그늘 속 글자 0, 그늘 속 종이 255

가장자리 칸은 그림 밖의 이웃이 없어서 OpenCV 가 안쪽 칸을 거울처럼 비춰 채운 뒤 평균을 냅니다. 그래서 여기서는 이웃 여덟 칸이 모두 있는 가운데 칸만 읽습니다.

MEAN_C 와 GAUSSIAN_C

MEAN_C 는 똑같이 더한 평균, GAUSSIAN_C 는 가운데에 무게를 준 평균임을 견준 비교 그림

왼쪽 초록 상자의 MEAN_C 는 블록 안의 칸을 모두 똑같이 더해 나눕니다. 오른쪽 다홍 상자의 GAUSSIAN_C 는 가운데에 가까운 칸일수록 무게를 더 줍니다. 반 평균을 낼 때 모든 학생을 똑같이 세는 것이 MEAN_C, 내 짝꿍과 앞뒤 자리 학생을 더 크게 세는 것이 GAUSSIAN_C 입니다.

견줄 것ADAPTIVE_THRESH_MEAN_CADAPTIVE_THRESH_GAUSSIAN_C
평균 내는 법블록 안의 칸을 똑같이가운데에 가까울수록 무게를 더
결과블록 경계에서 조금 거칠 수 있다조금 더 매끈하다
속도조금 빠르다조금 느리다
고르는 법먼저 둘 다 해 보고 결과가 좋은 쪽을 쓴다같다

주의할 점

  • thresholdType 에는 THRESH_BINARY 와 THRESH_BINARY_INV 만 줄 수 있습니다. TRUNC 나 TOZERO 를 주면 오류가 납니다.
  • 그늘이 없는 고른 그림에서도 Adaptive 를 쓸 수 있지만, 넓은 빈 바탕에 자잘한 점이 생길 수 있습니다. 다음 절의 C 로 줄입니다.
← 이전 절8장 · 조명이 고르지 않을 때다음 절 →8장 · blockSize 와 C 의 효과