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

8장. Threshold, Adaptive Threshold, Otsu

실습 2 — 다섯 가지 임계값 방식 견주기

cv2.threshold 의 다섯 가지 방식을 칸 다섯 개짜리 숫자 줄, 0~255 로 오르는 한 줄, 부품 그림에 차례로 적용해 견줍니다.

1단계. 숫자 다섯 개로 표 만들기

Python
import cv2
import numpy as np
import pandas as pd

row = np.array([[30, 100, 127, 128, 200]], dtype=np.uint8)
# 방식 이름과 상수를 짝지은 사전
kinds = {"BINARY": cv2.THRESH_BINARY, "BINARY_INV": cv2.THRESH_BINARY_INV,
         "TRUNC": cv2.THRESH_TRUNC, "TOZERO": cv2.THRESH_TOZERO,
         "TOZERO_INV": cv2.THRESH_TOZERO_INV}

table = {"입력": row.ravel().tolist()}
for name, kind in kinds.items():
    _, out = cv2.threshold(row, 127, 255, kind)
    table[name] = out.ravel().tolist()

# 행과 열을 바꿔(T = transpose, 뒤바꾸기) 방식이 한 줄씩 오게 한다
print(pd.DataFrame(table).T.to_string(header=False))
출력
입력           30  100  127  128  200
BINARY        0    0    0  255  255
BINARY_INV  255  255  255    0    0
TRUNC        30  100  127  127  127
TOZERO        0    0    0  128  200
TOZERO_INV   30  100  127    0    0

2단계. 0~255 로 오르는 한 줄에 적용해 그래프 그리기

Python
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

# np.arange(256): 0, 1, 2, ..., 255. 세로 1 줄짜리 그림으로 바꾼다
ramp = np.arange(256, dtype=np.uint8).reshape(1, -1)

fig, axes = plt.subplots(1, 5, figsize=(13, 2.8), sharey=True)
for ax, (name, kind) in zip(axes, kinds.items()):
    _, out = cv2.threshold(ramp, 127, 255, kind)
    # 가로는 들어간 밝기, 세로는 나온 밝기
    ax.plot(ramp.ravel(), out.ravel())
    ax.axvline(127, color="gray", linestyle="--")
    ax.set_title(name)
    ax.set_xlabel("들어간 밝기")
axes[0].set_ylabel("나온 밝기")
plt.tight_layout()
plt.show()
0~255 의 한 줄에 다섯 방식을 적용해 들어간 밝기와 나온 밝기를 그린 다섯 그래프

3단계. 부품 그림에 적용해 견주기

Python
gray = cv2.imread("ch08_parts.png", cv2.IMREAD_GRAYSCALE)

fig, axes = plt.subplots(2, 3, figsize=(10, 5.6))
panels = [("원본", gray)]
for name, kind in kinds.items():
    _, out = cv2.threshold(gray, 127, 255, kind)
    panels.append((name, out))
    # 결과에 남은 서로 다른 값의 개수: 이진 그림이면 2
    print(f"{name:10}: 서로 다른 값 {len(np.unique(out))}개, 평균 밝기 {round(float(out.mean()), 1)}")

for ax, (title, im) in zip(axes.flat, panels):
    ax.imshow(im, cmap="gray", vmin=0, vmax=255)
    ax.set_title(title)
    ax.axis("off")
plt.tight_layout()
plt.show()
출력
BINARY    : 서로 다른 값 2개, 평균 밝기 210.4
BINARY_INV: 서로 다른 값 2개, 평균 밝기 44.6
TRUNC     : 서로 다른 값 58개, 평균 밝기 118.7
TOZERO    : 서로 다른 값 79개, 평균 밝기 160.2
TOZERO_INV: 서로 다른 값 59개, 평균 밝기 13.9
부품 그림의 원본과 다섯 방식의 결과를 놓은 2행 3열 결과

코드 설명

  • 표의 다섯 줄은 본문의 손계산과 같습니다. 127 은 "127 보다 크지 않으므로" BINARY 에서 0, TOZERO 에서 0, TOZERO_INV 와 TRUNC 에서 그대로 127 입니다.
  • ramp = np.arange(256).reshape(1, -1) 은 0 부터 255 까지 한 칸씩 오르는 세로 1 줄짜리 그림입니다. 들어간 밝기와 나온 밝기를 그래프로 그리면 방식마다 모양이 한눈에 보입니다.
  • 부품 그림에서 BINARY 와 BINARY_INV 는 서로 다른 값이 2개뿐인 이진 그림입니다. TRUNC 는 58개, TOZERO 는 79개, TOZERO_INV 는 59개의 값이 남아 이진 그림이 아닙니다.
  • TRUNC 는 127 보다 밝은 작업대를 127 로 눌러 평균 밝기가 118.7 로 내려갔고, TOZERO_INV 는 밝은 작업대를 0 으로 지워 평균이 13.9 입니다. 부품만 원래 밝기로 남기고 싶을 때 TOZERO_INV 를 씁니다.
← 이전 절8장 · 실습 1 — 전역 임계값으로 부품 이진화하기다음 절 →8장 · 실습 3 — 그늘진 안내문에서 전역과 Adaptive 견주기