OpenCV 비전 실무7장 · Median 과 Bilateral 필터, 필터 비교16 / 21← 이전목차다음 →MetaContents

7장. Median 과 Bilateral 필터, 필터 비교

실습 3 — Median 커널 크기별 결과와 원본과의 차이

점 비율 10% 와 20% 의 표지판에 Median 필터의 ksize 를 3, 5, 7, 9 로 바꿔 걸고, 원본과의 차이를 표와 그래프로 견줍니다. 부품 그림(ch07_parts.png)으로 Median 필터가 가는 선을 지우는 것도 확인합니다.

1단계. ksize 와 점 비율별 차이 표

Python
import cv2
import numpy as np
import pandas as pd

gray = cv2.imread("ch07_sign.png", cv2.IMREAD_GRAYSCALE)

def add_salt_pepper(img, amount, seed):
    rng = np.random.default_rng(seed)
    r = rng.random(img.shape)
    noisy = img.copy()
    noisy[r < amount / 2] = 0
    noisy[r > 1 - amount / 2] = 255
    return noisy

rows = []
for amount in [0.10, 0.20]:
    noisy = add_salt_pepper(gray, amount, 711)
    for k in [3, 5, 7, 9]:
        out = cv2.medianBlur(noisy, k)
        rows.append({
            "점 비율": f"{amount:.0%}",
            "ksize": k,
            "평균 차이": round(float(cv2.absdiff(gray, out).mean()), 2),
            "PSNR": round(cv2.PSNR(gray, out), 2),        # 클수록 원본에 가깝다
            "남은 점": int((out == 0).sum() + (out == 255).sum()),
        })
table = pd.DataFrame(rows)
print(table.to_string(index=False))
출력
점 비율  ksize  평균 차이  PSNR  남은 점
 10%      3   0.80 28.67    10
 10%      5   1.17 27.07     0
 10%      7   1.73 25.14     0
 10%      9   2.64 23.15     0
 20%      3   1.98 24.15   163
 20%      5   2.12 24.05     2
 20%      7   2.64 23.06     0
 20%      9   3.75 21.42     0

2단계. ksize 별 그림 보기

Python
import matplotlib.pyplot as plt

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

noisy20 = add_salt_pepper(gray, 0.20, 711)
fig, axes = plt.subplots(1, 5, figsize=(14, 2.4))
panels = [("점 20%", noisy20)] + [(f"ksize {k}", cv2.medianBlur(noisy20, k)) for k in [3, 5, 7, 9]]
for ax, (title, img) in zip(axes, panels):
    ax.imshow(img, cmap="gray", vmin=0, vmax=255)
    ax.set_title(title)
    ax.axis("off")
plt.tight_layout()
plt.show()
점 20% 표지판과 Median 필터 ksize 3, 5, 7, 9 결과를 나란히 놓은 결과

3단계. ksize 와 PSNR 의 관계 그래프

Python
fig, ax = plt.subplots(figsize=(7, 3.6))
# 점 비율마다 선 하나: 가로는 ksize, 세로는 PSNR
for amount, part in table.groupby("점 비율"):
    ax.plot(part["ksize"], part["PSNR"], marker="o", label=f"점 {amount}")
ax.set_xticks([3, 5, 7, 9])
ax.set_xlabel("ksize (커널 한 변의 칸 수)")
ax.set_ylabel("PSNR (dB, 클수록 원본에 가깝다)")
ax.legend(loc="upper right")
ax.set_title("Median 필터의 커널 크기와 PSNR")
plt.tight_layout()
plt.show()
점 비율 10%, 20% 에서 Median 필터의 ksize 에 따른 PSNR 을 선으로 그린 그래프

4단계. 가는 선도 지워지는지 보기

Python
# 부품 그림: 긴 판(밝기 230) 위에 굵기 1칸의 얇은 금(밝기 120)이 세로로 그어져 있다(가로 110)
parts = cv2.imread("ch07_parts.png", cv2.IMREAD_GRAYSCALE)
med3 = cv2.medianBlur(parts, 3)

print("금 위의 한 칸(세로 160, 가로 110) - 원본:", int(parts[160, 110]), "/ Median 3:", int(med3[160, 110]))
# 판 안(세로 152~198)에서 금의 밝기(120)로 남은 칸 수를 센다
line_before = int((parts[152:199, 110] == 120).sum())
line_after = int((med3[152:199, 110] == 120).sum())
print(f"금으로 남은 칸: 원본 {line_before}칸 → Median 3 {line_after}칸")
출력
금 위의 한 칸(세로 160, 가로 110) - 원본: 120 / Median 3: 230
금으로 남은 칸: 원본 47칸 → Median 3 0칸

코드 설명

  • 점 10% 에서는 ksize 3 이 PSNR 28.67 로 가장 높고, 커널을 키울수록 27.07, 25.14, 23.15 로 낮아집니다. 점은 3 에서 이미 거의 다 지워졌으므로(남은 점 10칸), 더 키우면 글자 모서리만 둥글게 만듭니다.
  • 점 20% 에서는 ksize 3 이면 점이 163칸 남고, 5 면 2칸만 남습니다. PSNR 은 24.15 와 24.05 로 거의 같아서, 점이 많을 때는 눈으로 보기에 더 깨끗한 5 를 고를 만합니다. 9 까지 키우면 21.42 로 떨어집니다. 3단계 그래프에서 두 선이 모두 오른쪽으로 내려가는 것이 이 뜻입니다.
  • cv2.medianBlur(noisy, k) 의 k 는 홀수 정수 하나입니다. 3, 5, 7, 9 처럼 키우면 커널이 3×3, 5×5, 7×7, 9×9 가 됩니다.
  • 4단계에서 부품 위의 얇은 금(굵기 1칸, 판 안에서 47칸)은 Median 3 한 번에 47칸이 모두 사라졌습니다. 금 위의 칸은 120 에서 판의 밝기 230 이 되었습니다. 3×3 아홉 칸 가운데 금은 세 칸뿐이라 늘 소수파이기 때문입니다. 흠집을 찾는 데이터라면 Median 필터가 흠집까지 지울 수 있습니다.
← 이전 절7장 · 실습 2 — 평균 필터와 Median 필터: 점 노이즈에서 견주기다음 절 →7장 · 실습 4 — Bilateral 인자 바꿔 보기