7장. Median 과 Bilateral 필터, 필터 비교
실습 4 — Bilateral 인자 바꿔 보기
가우시안 노이즈(표준편차 20)를 더한 컬러 표지판에 Gaussian 필터와 Bilateral 필터를 걸어, 노이즈를 얼마나 줄이고 경계를 얼마나 지키는지 견줍니다. 한 줄 밝기 그래프로 경계를 보고, sigmaColor 를 바꿔 가며 숫자가 어떻게 변하는지 표로 봅니다.
1단계. Gaussian 과 Bilateral 견주기
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
img = cv2.imread("ch07_sign.png") # 이번에는 컬러로 읽는다
def add_gaussian(src, std, seed):
"""평균 0, 표준편차 std 의 가우시안 노이즈를 칸마다 더하고 0~255 로 자른다."""
rng = np.random.default_rng(seed)
# normal(평균, 표준편차, 모양): 종 모양으로 퍼진 난수
noise = rng.normal(0, std, src.shape)
# 6차시와 같은 꼴: 소수로 바꿔 더하고, clip 으로 0~255 에 잘라 넣은 뒤 uint8 로 바꾼다
return np.clip(src.astype(np.float64) + noise, 0, 255).astype(np.uint8)
noisy = add_gaussian(img, 20, 712)
gauss = cv2.GaussianBlur(noisy, (5, 5), 0) # 6차시의 Gaussian 필터
bilat = cv2.bilateralFilter(noisy, # src: 컬러 그림
9, # d: 지름 9칸
75, # sigmaColor
75) # sigmaSpace
for name, out in [("노이즈 그대로", noisy), ("Gaussian 5×5", gauss), ("Bilateral 9·75·75", bilat)]:
print(f"{name:18} PSNR {cv2.PSNR(img, out):.2f}")
crop = (slice(50, 112), slice(44, 160)) # 글자 "GAT" 둘레
fig, axes = plt.subplots(1, 4, figsize=(12, 2.4))
for ax, (title, im) in zip(axes, [("원본", img), ("노이즈", noisy), ("Gaussian", gauss), ("Bilateral", bilat)]):
ax.imshow(cv2.cvtColor(im[crop], cv2.COLOR_BGR2RGB)) # 컬러는 RGB 로 바꿔 띄운다
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()노이즈 그대로 PSNR 22.42
Gaussian 5×5 PSNR 22.98
Bilateral 9·75·75 PSNR 33.89
2단계. 한 줄 밝기 그래프로 경계 보기
# 흑백으로 바꿔 세로 80 줄(글자 G·A·T 를 가로지르는 줄)의 밝기를 꺼낸다
def row_of(im):
return cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)[80, 40:170]
x = np.arange(40, 170)
fig, ax = plt.subplots(figsize=(10, 3.6))
ax.plot(x, row_of(img), label="원본", linewidth=2)
ax.plot(x, row_of(gauss), label="Gaussian 5×5")
ax.plot(x, row_of(bilat), label="Bilateral 9·75·75")
ax.set_ylim(0, 320) # 위쪽에 범례 자리를 둔다
ax.set_xlabel("가로 칸 번호 (세로 80 줄)")
ax.set_ylabel("밝기")
ax.legend(loc="upper center", ncol=3)
plt.tight_layout()
plt.show()
# 글자 G 의 왼쪽 테두리 둘레(가로 54~66) 값을 두 칸 걸러 찍는다
for name, im in [("원본", img), ("Gaussian", gauss), ("Bilateral", bilat)]:
print(f"{name:9}", [int(v) for v in cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)[80, 54:67:2]])원본 [90, 90, 219, 250, 250, 250, 90]
Gaussian [93, 100, 186, 244, 243, 214, 113]
Bilateral [92, 92, 218, 241, 244, 246, 90]
3단계. sigmaColor 를 바꿔 가며 재기
import pandas as pd
def mid_ratio(im):
"""표지판 안쪽에서 초록(90 안팎)도 흰 글자(250 안팎)도 아닌 중간 밝기(121~219) 칸의 비율.
경계가 번질수록 중간 밝기 칸이 늘어난다."""
g = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)[45:158, 45:230]
return round(float(((g > 120) & (g < 220)).mean()), 3)
rows = [{"설정": "원본", "PSNR": "-", "중간 밝기 비율": mid_ratio(img)},
{"설정": "Gaussian 5×5", "PSNR": round(cv2.PSNR(img, gauss), 2), "중간 밝기 비율": mid_ratio(gauss)}]
for sc in [10, 40, 75, 150]:
out = cv2.bilateralFilter(noisy, 9, sc, 75) # sigmaColor 만 바꾼다
rows.append({"설정": f"Bilateral sigmaColor {sc}",
"PSNR": round(cv2.PSNR(img, out), 2),
"중간 밝기 비율": mid_ratio(out)})
print(pd.DataFrame(rows).to_string(index=False)) 설정 PSNR 중간 밝기 비율
원본 - 0.037
Gaussian 5×5 22.98 0.106
Bilateral sigmaColor 10 22.6 0.042
Bilateral sigmaColor 40 29.43 0.027
Bilateral sigmaColor 75 33.89 0.014
Bilateral sigmaColor 150 32.79 0.0144단계. sigmaColor 별 그림 보기
fig, axes = plt.subplots(1, 4, figsize=(12, 2.4))
for ax, sc in zip(axes, [10, 40, 75, 150]):
out = cv2.bilateralFilter(noisy, 9, sc, 75)
ax.imshow(cv2.cvtColor(out[crop], cv2.COLOR_BGR2RGB))
ax.set_title(f"sigmaColor {sc}")
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
add_gaussian은rng.normal(0, 20, 모양)으로 칸마다 플러스·마이너스 난수를 더하고np.clip으로 0~255 를 넘는 값을 잘라 냅니다. 컬러 그림이면 B, G, R 채널에 따로 난수가 더해집니다.- 1단계에서 PSNR 은 노이즈 그대로 22.42, Gaussian 5×5 는 22.98 로 조금밖에 오르지 않았고, Bilateral 은 33.89 로 11 넘게 올랐습니다. 이 그림은 글자와 테두리가 많아서, Gaussian 필터가 노이즈를 줄여 얻는 만큼을 경계를 흐려 거의 다 잃습니다.
- 2단계에서 글자 G 의 왼쪽 테두리를 보면 원본은 90, 90, 219, 250 으로 곧장 오릅니다. Gaussian 은 93, 100, 186, 244 로 비탈이 생기고, 오른쪽 테두리도 214, 113 으로 번졌습니다. Bilateral 은 92, 92, 218, 241 이고 오른쪽 끝도 90 으로, 원본의 계단에 가깝습니다.
- 3단계에서 sigmaColor 10 은 PSNR 22.60 으로 노이즈 그대로(22.42)와 거의 같습니다. 노이즈의 흔들림(표준편차 20)보다 작아 이웃을 거의 섞지 않기 때문입니다. 40 에서 29.43, 75 에서 33.89 로 가장 높고, 150 은 32.79 로 조금 내려갑니다. 중간 밝기 비율은 Gaussian 이 0.106 으로 원본(0.037)의 세 배 가까이 늘어 경계가 번졌음을 보여 줍니다. Bilateral 은 40 이상에서 0.027~0.014 로 원본보다도 작은데, 원본의 테두리를 부드럽게 그린 중간 칸들이 양쪽 가운데 더 비슷한 쪽 밝기로 끌려갔기 때문입니다.