OpenCV 비전 실무6장 · 평균 필터와 Gaussian 필터16 / 21← 이전목차다음 →MetaContents

6장. 평균 필터와 Gaussian 필터

실습 3 — 커널 크기별 평균 필터

노이즈 낀 안내판 그림에 커널 크기 3, 5, 9, 15 의 평균 필터를 걸고, 원본과의 평균 차이, 고른 면에 남은 흔들림, 경계가 번진 폭을 표로 견줍니다. 노이즈는 실습 1과 같은 방법(평균 0, 표준편차 20, 씨앗 600)으로 다시 만듭니다.

1단계. 노이즈 낀 그림 만들기

Python
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.81

2단계. 크기마다 걸고 표로 재기

Python
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   12

3단계. 결과를 격자로 보기

Python
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)
원본, 노이즈 낀 그림, 크기 3, 5, 9, 15 의 평균 필터 결과를 2행 3열로 놓은 결과

코드 설명

  • 노이즈 낀 그림은 원래 모두 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(가장 작은 값의 자리)을 줄인 이름입니다. 표에서 평균 차이가 가장 작은 줄을 찾습니다.
← 이전 절6장 · 실습 2 — 3×3 평균 필터를 손으로 계산하고 cv2.blur 와 견주기다음 절 →6장 · 실습 4 — Gaussian 필터와 sigma