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

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

실습 5 — 네 필터와 두 노이즈 비교표

표지판에 가우시안 노이즈와 Salt & Pepper 노이즈를 각각 더하고, 평균, Gaussian, Median, Bilateral 네 필터를 모두 걸어 평균 차이, PSNR, 걸린 시간을 한 표로 만듭니다. 걸린 시간은 컴퓨터마다 다르므로 "몇 배" 로 읽습니다.

1단계. 두 노이즈와 네 필터 준비

Python
import time
import cv2
import numpy as np
import pandas as pd

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

def add_gaussian(src, std, seed):
    rng = np.random.default_rng(seed)
    return np.clip(src.astype(np.float64) + rng.normal(0, std, src.shape), 0, 255).astype(np.uint8)

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

noises = {
    "가우시안": add_gaussian(gray, 20, 712),        # 표준편차 20
    "점": add_salt_pepper(gray, 0.10, 711),         # 점 비율 10%
}
# 필터 이름 → 그림을 받아 결과를 돌려주는 함수. 커널 크기는 5 안팎으로 맞춘다
filters = {
    "평균": lambda im: cv2.blur(im, (5, 5)),
    "Gaussian": lambda im: cv2.GaussianBlur(im, (5, 5), 0),
    "Median": lambda im: cv2.medianBlur(im, 5),
    "Bilateral": lambda im: cv2.bilateralFilter(im, 9, 75, 75),
}
for name, im in noises.items():
    print(f"{name} 노이즈 그대로: PSNR {cv2.PSNR(gray, im):.2f}")
출력
가우시안 노이즈 그대로: PSNR 22.35
점 노이즈 그대로: PSNR 15.19

2단계. 차이, PSNR, 시간 재기

Python
def time_ms(func, im, repeat=20):
    """func(im) 을 repeat 번 돌려 한 번에 걸린 평균 밀리초."""
    start = time.perf_counter()
    for _ in range(repeat):
        func(im)
    return (time.perf_counter() - start) / repeat * 1000

rows, outputs = [], {}
for nname, noisy in noises.items():
    for fname, func in filters.items():
        out = func(noisy)
        outputs[(nname, fname)] = out          # 3단계에서 그림으로 쓴다
        rows.append({
            "노이즈": nname,
            "필터": fname,
            "평균 차이": round(float(cv2.absdiff(gray, out).mean()), 2),
            "PSNR": round(cv2.PSNR(gray, out), 2),
            "시간(ms)": round(time_ms(func, noisy), 3),
        })
table = pd.DataFrame(rows)
print(table.to_string(index=False))
출력
 노이즈        필터  평균 차이  PSNR  시간(ms)
가우시안        평균  12.38 21.25   0.166
가우시안  Gaussian   9.81 23.65   0.070
가우시안    Median   6.65 26.71   0.348
가우시안 Bilateral   6.01 28.70   2.285
   점        평균  16.24 20.27   0.166
   점  Gaussian  14.03 21.55   0.056
   점    Median   1.17 27.07   0.331
   점 Bilateral   9.35 19.28   2.252

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

Python
import matplotlib.pyplot as plt

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

fig, axes = plt.subplots(2, 5, figsize=(15, 4.6))
for r, nname in enumerate(noises):
    axes[r, 0].imshow(noises[nname], cmap="gray", vmin=0, vmax=255)
    axes[r, 0].set_title(f"{nname} 노이즈")
    for c, fname in enumerate(filters, start=1):
        axes[r, c].imshow(outputs[(nname, fname)], cmap="gray", vmin=0, vmax=255)
        psnr = table.query("노이즈 == @nname and 필터 == @fname")["PSNR"].iloc[0]
        axes[r, c].set_title(f"{fname} ({psnr} dB)")
for ax in axes.flat:
    ax.axis("off")
plt.tight_layout()
plt.show()
가우시안 노이즈와 점 노이즈 표지판에 네 필터를 건 결과를 2행 5열로 놓고 PSNR 을 적은 결과

4단계. 노이즈별 1등과 시간 비교

Python
for nname, part in table.groupby("노이즈", sort=False):
    # idxmax: 가장 큰 값이 있는 줄의 번호
    best = part.loc[part["PSNR"].idxmax()]
    worst = part.loc[part["PSNR"].idxmin()]
    print(f"[{nname}] 1등 {best['필터']} {best['PSNR']} dB / 꼴찌 {worst['필터']} {worst['PSNR']} dB")

# 시간은 두 노이즈의 평균으로, 가장 빠른 필터를 1 로 놓고 몇 배인지 본다
speed = table.groupby("필터", sort=False)["시간(ms)"].mean()
fastest = speed.min()
for fname, ms in speed.items():
    print(f"{fname:10} {ms:.3f} ms → 가장 빠른 필터의 {ms / fastest:.1f}배")
출력
[가우시안] 1등 Bilateral 28.7 dB / 꼴찌 평균 21.25 dB
[점] 1등 Median 27.07 dB / 꼴찌 Bilateral 19.28 dB
평균         0.166 ms → 가장 빠른 필터의 2.6배
Gaussian   0.063 ms → 가장 빠른 필터의 1.0배
Median     0.340 ms → 가장 빠른 필터의 5.4배
Bilateral  2.268 ms → 가장 빠른 필터의 36.0배

코드 설명

  • filters 사전에 필터 이름과 lambda im: ... 함수를 짝지어 두면, 같은 반복문으로 네 필터를 차례로 돌리고 시간도 같은 방법으로 잴 수 있습니다.
  • 가우시안 노이즈에서는 Bilateral 이 PSNR 28.70 으로 1등, 평균 필터가 21.25 로 꼴찌입니다. 평균 필터는 노이즈 그대로(22.35)보다도 낮습니다. Median 도 26.71 로 꽤 높은데, 이 그림이 고르게 칠한 면이 넓은 도형 그림이라 그렇습니다.
  • 점 노이즈에서는 Median 이 27.07 로 1등이고 Bilateral 이 19.28 로 꼴찌입니다. 본문에서 본 대로 Bilateral 은 점을 경계 건너편으로 보고 남깁니다(평균 차이 9.35).
  • table.query("노이즈 == @nname and 필터 == @fname") 는 조건에 맞는 줄만 고릅니다. @ 를 붙이면 파이썬 변수의 값을 조건에 씁니다.
  • 시간은 교안 PC 에서 Gaussian 이 가장 빨랐고, 평균 필터가 그 두세 배, Median 이 다섯 배 남짓, Bilateral 이 서른 배 넘게 걸렸습니다. 같은 5×5 인데 Gaussian 이 평균 필터보다 빨랐던 것은 OpenCV 안에서 함수마다 빠르게 만든 방법이 달라서로 보이며, 컴퓨터에 따라 둘의 순서는 바뀔 수 있습니다. Bilateral 이 가장 느리다는 점은 어느 컴퓨터에서나 같습니다.
← 이전 절7장 · 실습 4 — Bilateral 인자 바꿔 보기다음 절 →7장 · 프로젝트 1 — 노이즈 종류에 맞는 필터 고르기 표