7장. Median 과 Bilateral 필터, 필터 비교
실습 5 — 네 필터와 두 노이즈 비교표
표지판에 가우시안 노이즈와 Salt & Pepper 노이즈를 각각 더하고, 평균, Gaussian, Median, Bilateral 네 필터를 모두 걸어 평균 차이, PSNR, 걸린 시간을 한 표로 만듭니다. 걸린 시간은 컴퓨터마다 다르므로 "몇 배" 로 읽습니다.
1단계. 두 노이즈와 네 필터 준비
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.192단계. 차이, PSNR, 시간 재기
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.2523단계. 결과를 격자로 보기
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()
4단계. 노이즈별 1등과 시간 비교
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 이 가장 느리다는 점은 어느 컴퓨터에서나 같습니다.