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

8장. Threshold, Adaptive Threshold, Otsu

실습 4 — blockSize 와 C 바꿔 보기

그늘진 안내문에 Adaptive Threshold 를 blockSize 세 가지, C 두 가지로 해 보고, 정답과 같은 칸의 비율을 표로 견줍니다. blockSize 를 짝수로 주면 어떤 오류가 나는지도 봅니다.

1단계. 그림과 정답 준비하기

Python
import cv2
import numpy as np
import pandas as pd

gray = cv2.imread("ch08_shadow_doc.png", cv2.IMREAD_GRAYSCALE)
clean = cv2.imread("ch08_clean_doc.png", cv2.IMREAD_GRAYSCALE)
# 그늘 없는 판을 127 로 나눈 것을 정답으로 삼는다
_, answer = cv2.threshold(clean, 127, 255, cv2.THRESH_BINARY)

def agree(out):
    """정답과 같은 칸의 비율."""
    return round(float((out == answer).mean()), 3)

print("정답의 검은 칸 비율:", round(float((answer == 0).mean()), 3))
출력
정답의 검은 칸 비율: 0.059

2단계. blockSize 와 C 의 조합 여섯 가지

Python
blocks = [5, 15, 51]
cs = [2, 10]
rows, outs = [], {}
for block in blocks:
    for c in cs:
        out = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                    cv2.THRESH_BINARY, block, c)
        outs[(block, c)] = out
        rows.append({"blockSize": block, "C": c,
                     "정답과 같은 칸": agree(out),
                     "검은 칸 비율": round(float((out == 0).mean()), 3)})

table = pd.DataFrame(rows)
print(table.to_string(index=False))
# idxmax: 가장 큰 값이 있는 줄 번호
best = table.loc[table["정답과 같은 칸"].idxmax()]
print("가장 좋은 조합: blockSize", int(best["blockSize"]), "/ C", int(best["C"]))
출력
 blockSize  C  정답과 같은 칸  검은 칸 비율
         5  2     0.989    0.068
         5 10     0.992    0.062
        15  2     0.983    0.075
        15 10     0.987    0.071
        51  2     0.965    0.094
        51 10     0.977    0.082
가장 좋은 조합: blockSize 5 / C 10

3단계. 격자로 견주기

Python
import matplotlib.pyplot as plt

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

# 줄은 C, 칸은 blockSize
fig, axes = plt.subplots(len(cs), len(blocks), figsize=(11, 5.2))
for i, c in enumerate(cs):
    for j, block in enumerate(blocks):
        out = outs[(block, c)]
        axes[i, j].imshow(out, cmap="gray", vmin=0, vmax=255)
        axes[i, j].set_title(f"blockSize {block}, C {c} → {agree(out)}")
        axes[i, j].axis("off")
plt.tight_layout()
plt.show()
blockSize 5·15·51 과 C 2·10 의 여섯 조합으로 이진화한 안내문을 놓은 2행 3열 결과

4단계. blockSize 를 짝수로 주면

Python
for block in [4, 1, 7]:
    try:
        cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, block, 10)
        print(f"blockSize {block}: 된다")
    except cv2.error as e:
        # 오류 글의 마지막 줄에서 약속(조건) 부분만 골라 찍는다
        rule = str(e).split("Assertion failed)")[1].split(" in function")[0].strip()
        print(f"blockSize {block}: 오류 → {rule}")
출력
blockSize 4: 오류 → blockSize % 2 == 1 && blockSize > 1
blockSize 1: 오류 → blockSize % 2 == 1 && blockSize > 1
blockSize 7: 된다

코드 설명

  • 여섯 조합 모두 정답과 같은 칸이 0.965 이상으로, 전역 임계값(0.619)보다 훨씬 좋습니다. 가장 좋은 조합은 blockSize 5, C 10 의 0.992 입니다.
  • 같은 blockSize 에서는 C 10 이 C 2 보다 늘 낫습니다(5: 0.989 → 0.992, 15: 0.983 → 0.987, 51: 0.965 → 0.977). C 를 키우면 기준이 낮아져 글자 둘레의 종이가 검게 붙는 칸이 줄기 때문입니다.
  • blockSize 가 커질수록 검은 칸 비율이 0.062 에서 0.082(C 10 기준)로 늘어 글자가 굵게 번집니다. blockSize 51, C 2 에서는 오른쪽 가장자리에 검은 세로줄도 생겼습니다. 그늘이 가장 짙은 가장자리에서 넓은 동네의 평균이 따라가지 못한 것입니다.
  • blockSize 4 와 1 은 blockSize % 2 == 1 && blockSize > 1 이라는 약속을 어겨 cv2.error 가 납니다. try / except cv2.error 로 오류를 받아 프로그램이 멈추지 않게 했습니다.
← 이전 절8장 · 실습 3 — 그늘진 안내문에서 전역과 Adaptive 견주기다음 절 →8장 · 실습 5 — Otsu 로 자동 임계값 정하기