7장. Median 과 Bilateral 필터, 필터 비교
필터 고르는 순서
병원에서는 증상을 먼저 묻고 약을 고릅니다. 열이 나는지, 기침을 하는지에 따라 약이 다르고, 같은 약이라도 운전을 해야 하면 졸리지 않는 약으로 바꿉니다. 필터도 노이즈의 생김새를 먼저 보고, 경계가 얼마나 중요한지, 얼마나 빨라야 하는지에 따라 고릅니다.
흐름도를 왼쪽부터 따라갑니다. 먼저 그림을 크게 확대해 노이즈가 점인지 자글자글한지 봅니다. 점이면 Median 을 ksize 3 이나 5 로 겁니다. 자글자글한데 글자나 테두리가 중요하면 Bilateral 을, 사진이 아주 많아 빨라야 하면 작은 커널의 Gaussian 을 씁니다. 마지막에는 늘 PSNR 과 시간을 표로 재어 확인합니다. 여기서 증상이 노이즈의 생김새, 약이 필터, 운전 여부가 속도 조건입니다.
상황별 첫 선택
| 상황 | 첫 선택 | 까닭 |
|---|---|---|
| 하얀 점과 검은 점이 흩뿌려졌다 | Median, ksize 3~5 | 튀는 값을 중앙값으로 버린다 |
| 자글자글하고 경계가 중요하다(글자, 부품) | Bilateral, d 5~9, sigma 50~100 | 경계 건너편을 섞지 않는다 |
| 자글자글하고 사진이 아주 많다 | Gaussian, 커널 3~5 | 빠르고 고르게 줄인다 |
| 점과 자글자글함이 섞였다 | Median 먼저. 다른 필터를 이어 걸지는 숫자로 재 보고 정한다 | 점이 남아 있으면 다른 필터가 제 몫을 못 하고, 이어 걸면 경계가 더 무뎌질 수 있다 |
| 흐리게 하는 것 자체가 목적이다(배경 흐림) | 평균이나 Gaussian, 큰 커널 | 경계를 지킬 까닭이 없다 |
짧은 예시
점과 자글자글함이 섞인 그림에서, Median 하나만 거는 것과 Median 뒤에 Bilateral 을 이어 거는 것을 견줍니다.
import cv2
import numpy as np
gray = cv2.imread("ch07_sign.png", cv2.IMREAD_GRAYSCALE)
# 가우시안 노이즈(표준편차 15)를 더한 뒤 점 5% 를 찍어 두 노이즈를 섞는다
mixed = np.clip(gray.astype(np.float64) + np.random.default_rng(713).normal(0, 15, gray.shape), 0, 255).astype(np.uint8)
r = np.random.default_rng(714).random(gray.shape)
mixed[r < 0.025] = 0
mixed[r > 0.975] = 255
med = cv2.medianBlur(mixed, 5) # 점을 먼저 지운다
both = cv2.bilateralFilter(med, 9, 75, 75) # 남은 자글자글함을 경계를 지키며 줄인다
for name, out in [("Median 만", med), ("Median → Bilateral", both)]:
print(f"{name:18} PSNR {cv2.PSNR(gray, out):.2f}")교안 PC 에서 돌리면 Median 만 건 것이 PSNR 27.26, Bilateral 을 이어 건 것이 25.11 로, 이어 걸었더니 오히려 낮아졌습니다. Median 5 가 점과 함께 자글자글함도 꽤 줄여 놓았는데, Bilateral(sigmaColor 75)이 그 위에서 글자 테두리까지 조금 더 섞었기 때문입니다. 필터를 더 건다고 늘 좋아지지는 않습니다. 그래서 흐름도의 마지막 걸음이 "숫자로 확인"입니다.
주의할 점
- 필터를 여러 번 이어 걸수록 경계와 가는 선이 조금씩 더 무뎌집니다. 꼭 필요한 만큼만 겁니다.
- 학습 데이터 전체에 같은 필터를 걸었다면, 모델을 실제로 쓸 때 들어오는 사진에도 같은 필터를 걸어야 합니다. 학습 때와 쓸 때의 사진이 달라지면 모델이 헷갈립니다.