6장. 평균 필터와 Gaussian 필터
실습 3 — 커널 크기별 평균 필터
노이즈 낀 안내판 그림에 커널 크기 3, 5, 9, 15 의 평균 필터를 걸고, 원본과의 평균 차이, 고른 면에 남은 흔들림, 경계가 번진 폭을 표로 견줍니다. 노이즈는 실습 1과 같은 방법(평균 0, 표준편차 20, 씨앗 600)으로 다시 만듭니다.
1단계. 노이즈 낀 그림 만들기
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
img = cv2.imread("ch06_board.png", cv2.IMREAD_GRAYSCALE)
# 실습 1과 같은 노이즈: 평균 0, 표준편차 20, 씨앗 600
rng = np.random.default_rng(600)
noisy = np.clip(img.astype(np.float64) + rng.normal(0, 20, img.shape), 0, 255).astype(np.uint8)
# 고른 면: 왼쪽 아래 어두운 바닥(세로 165~214, 가로 30~59). 원래는 모두 60 이다
flat = (slice(165, 215), slice(30, 60))
print("고른 면의 원래 밝기 종류:", [int(v) for v in np.unique(img[flat])])
print("노이즈 낀 고른 면의 표준편차:", round(float(noisy[flat].std()), 2))
print("노이즈 낀 그림과 원본의 평균 차이:", round(float(cv2.absdiff(noisy, img).mean()), 2))고른 면의 원래 밝기 종류: [60]
노이즈 낀 고른 면의 표준편차: 19.21
노이즈 낀 그림과 원본의 평균 차이: 15.812단계. 크기마다 걸고 표로 재기
rows, results = [], {}
for k in (3, 5, 9, 15):
out = cv2.blur(noisy, (k, k))
results[k] = out
# 경계가 번진 폭: 노이즈 없는 원본에 같은 필터를 걸고, 가로줄 165 에서 밝기가 70~190 인 칸을 센다
edge_row = cv2.blur(img, (k, k))[165, 130:190]
rows.append({
"크기": k,
"섞는칸": k * k, # 커널 안의 칸 수
"평균차이": round(float(cv2.absdiff(out, img).mean()), 2), # 작을수록 원본에 가깝다
"흔들림": round(float(out[flat].std()), 2), # 고른 면의 표준편차: 작을수록 매끈하다
"경계폭": int(((edge_row > 70) & (edge_row < 190)).sum()), # 클수록 경계가 번졌다
})
table = pd.DataFrame(rows)
print(table.to_string(index=False)) 크기 섞는칸 평균차이 흔들림 경계폭
3 9 9.38 6.75 2
5 25 11.21 4.25 4
9 81 16.16 2.15 8
15 225 22.84 1.13 123단계. 결과를 격자로 보기
panels = [("원본", img), ("노이즈 (표준편차 20)", noisy)]
panels += [(f"평균 필터 {k}×{k}", results[k]) for k in (3, 5, 9, 15)]
fig, axes = plt.subplots(2, 3, figsize=(11, 5.6))
# axes.flat: 2×3 칸을 한 줄로 펴서 차례로 꺼낸다
for ax, (title, arr) in zip(axes.flat, panels):
ax.imshow(arr, cmap="gray", vmin=0, vmax=255)
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
# 평균 차이가 가장 작은 크기를 고른다
best = table.loc[table["평균차이"].idxmin()]
print(f"원본과 가장 닮은 크기: {int(best['크기'])} (평균 차이 {best['평균차이']})")원본과 가장 닮은 크기: 3 (평균 차이 9.38)
코드 설명
- 노이즈 낀 그림은 원래 모두 60 인 고른 면의 표준편차가 19.21 이고, 원본과의 평균 차이가 15.81 입니다. 이 두 숫자가 필터를 견주는 출발점입니다.
- 커널이 커질수록 고른 면의 흔들림은 6.75 에서 1.13 으로 줄어 매끈해지고, 경계가 번진 폭은 2칸에서 12칸으로 넓어집니다. 노이즈를 지우는 힘과 무늬를 지키는 힘이 반대로 움직입니다.
- 원본과의 평균 차이는 크기 3 에서 9.38 로 가장 작고, 15 에서는 22.84 로 노이즈 낀 그림(15.81)보다도 커집니다. 이 안내판에는 가는 줄무늬와 글자가 많아서, 크게 흐리면 노이즈를 지운 이득보다 무늬를 잃은 손해가 큽니다. 격자 그림에서도 15×15 는 줄무늬가 회색 덩어리가 되고 "EXIT" 가 읽기 어렵습니다.
idxmin()은 index of minimum(가장 작은 값의 자리)을 줄인 이름입니다. 표에서 평균 차이가 가장 작은 줄을 찾습니다.