7장. Median 과 Bilateral 필터, 필터 비교
실습 2 — 평균 필터와 Median 필터: 점 노이즈에서 견주기
작은 배열로 평균과 중앙값을 손계산과 대조한 뒤, 점 비율 10% 의 표지판에 평균 필터와 Median 필터를 걸어 원본과의 차이를 견줍니다. 글자 부분을 확대해 얼룩과 깨끗함을 눈으로도 봅니다.
1단계. 아홉 칸으로 평균과 중앙값 대조하기
import cv2
import numpy as np
# 본문 손계산의 아홉 칸: 하얀 점(255) 하나가 섞였다
nine = np.array([90, 92, 93, 95, 255, 91, 94, 96, 93])
print("줄 세우기:", [int(v) for v in np.sort(nine)])
print("평균:", round(float(nine.mean()), 1), "/ 중앙값:", float(np.median(nine)))
# 하얀 점이 셋으로 늘면?
three = np.array([90, 255, 93, 95, 255, 91, 255, 96, 93])
print("점 셋 - 평균:", round(float(three.mean()), 1), "/ 중앙값:", float(np.median(three)))줄 세우기: [90, 91, 92, 93, 93, 94, 95, 96, 255]
평균: 111.0 / 중앙값: 93.0
점 셋 - 평균: 147.0 / 중앙값: 95.02단계. 5×5 그림에 두 필터 걸기
# 밝기 100 의 고른 판에 하얀 점과 검은 점을 하나씩
tiny = np.full((5, 5), 100, dtype=np.uint8)
tiny[2, 2] = 255
tiny[0, 4] = 0
# blur = 평균 필터(6차시), (3, 3) 은 커널의 (가로, 세로)
print("평균 필터 3×3:")
print(cv2.blur(tiny, (3, 3)))
# medianBlur 의 둘째 인자는 홀수 하나
print("Median 필터 3:")
print(cv2.medianBlur(tiny, 3))평균 필터 3×3:
[[100 100 100 89 89]
[100 117 117 106 89]
[100 117 117 117 100]
[100 117 117 117 100]
[100 100 100 100 100]]
Median 필터 3:
[[100 100 100 100 100]
[100 100 100 100 100]
[100 100 100 100 100]
[100 100 100 100 100]
[100 100 100 100 100]]3단계. 표지판에서 원본과의 차이 재기
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
noisy = add_salt_pepper(gray, 0.10, 711)
def mae(a, b):
"""평균 절대 차이: absdiff 로 칸마다 |a - b| 를 구해 평균한다."""
return round(float(cv2.absdiff(a, b).mean()), 2)
results = {
"노이즈 그대로": noisy,
"평균 3×3": cv2.blur(noisy, (3, 3)),
"평균 5×5": cv2.blur(noisy, (5, 5)),
"Median 3": cv2.medianBlur(noisy, 3),
"Median 5": cv2.medianBlur(noisy, 5),
}
for name, out in results.items():
# 남은 점: 결과에 0 이나 255 로 남은 칸
left = int((out == 0).sum() + (out == 255).sum())
print(f"{name:10} 평균 차이 {mae(gray, out):6.2f} 남은 점 {left:5d}")노이즈 그대로 평균 차이 12.50 남은 점 7557
평균 3×3 평균 차이 14.61 남은 점 0
평균 5×5 평균 차이 16.24 남은 점 0
Median 3 평균 차이 0.80 남은 점 10
Median 5 평균 차이 1.17 남은 점 04단계. 글자 부분 확대해 보기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
# 글자 "GATE" 둘레만 잘라 크게 본다: [세로 시작:끝, 가로 시작:끝]
crop = (slice(50, 110), slice(40, 170))
panels = [("원본", gray), ("점 10%", noisy),
("평균 5×5", results["평균 5×5"]), ("Median 5", results["Median 5"])]
fig, axes = plt.subplots(1, 4, figsize=(12, 2.6))
for ax, (title, img) in zip(axes, panels):
ax.imshow(img[crop], cmap="gray", vmin=0, vmax=255)
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
- 1단계는 본문의 손계산 그대로입니다. 점 하나면 평균이 111 로 튀지만 중앙값은 93 이고, 점이 셋이어도 중앙값은 95 에 머뭅니다.
- 2단계에서
cv2.blur는 두 점을 둘레 칸까지 117 과 89 로 번지게 하고,cv2.medianBlur는 25칸을 모두 100 으로 되돌립니다. - 3단계에서 평균 필터는 0 과 255 로 남은 점을 하나도 남기지 않았지만, 평균 차이는 노이즈 그대로(12.50)보다 오히려 커졌습니다(3×3 은 14.61, 5×5 는 16.24). 점을 지운 것이 아니라 둘레 칸에 회색 얼룩으로 펴 바르고 글자까지 흐렸기 때문입니다. Median 3 은 0.80, Median 5 는 1.17 로 크게 줄였습니다. Median 3 은 점이 10칸 남았는데도 차이가 더 작은데, 커널이 작아 글자를 덜 건드렸기 때문입니다.
cv2.absdiff(a, b).mean()이 평균 절대 차이입니다. uint8 끼리 그냥 빼면 음수가 255 쪽으로 돌아가므로absdiff를 씁니다.