8장. Threshold, Adaptive Threshold, Otsu
실습 4 — blockSize 와 C 바꿔 보기
그늘진 안내문에 Adaptive Threshold 를 blockSize 세 가지, C 두 가지로 해 보고, 정답과 같은 칸의 비율을 표로 견줍니다. blockSize 를 짝수로 주면 어떤 오류가 나는지도 봅니다.
1단계. 그림과 정답 준비하기
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.0592단계. blockSize 와 C 의 조합 여섯 가지
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 103단계. 격자로 견주기
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()
4단계. blockSize 를 짝수로 주면
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로 오류를 받아 프로그램이 멈추지 않게 했습니다.