6장. 평균 필터와 Gaussian 필터
얼마나 좋아졌나 재기
정수기 필터를 바꾼 뒤 물이 얼마나 깨끗해졌는지는 맛만 보고 말하기 어렵습니다. 수질 검사지로 숫자를 재야 "전보다 좋아졌다"를 남에게 보여 줄 수 있습니다. 필터를 건 사진도 눈으로만 보면 사람마다 판단이 다릅니다. 깨끗한 원본이 있으면, 결과가 원본과 얼마나 닮았는지 숫자로 잴 수 있습니다.
그림의 세 상자가 이 장에서 쓰는 재는 법입니다. 왼쪽은 사람이 보고, 가운데와 오른쪽은 깨끗한 원본과 견주어 숫자를 냅니다. 여기서 수질 검사지가 평균 절대 차이와 PSNR 이고, 깨끗한 물의 기준이 원본 그림입니다.
두 가지 숫자
| 숫자 | 구하는 법 | 좋은 쪽 | 읽는 법 |
|---|---|---|---|
| 평균 절대 차이(MAE) | cv2.absdiff(결과, 원본).mean() | 작을수록 | "칸마다 평균 몇만큼 다른가". 밝기 단위 그대로라 읽기 쉽다 |
| PSNR | cv2.PSNR(원본, 결과) | 클수록 | 원본과 닮은 정도를 데시벨로. 차이를 제곱해 평균 낸 값이 절반이 되면 약 3dB 오른다 |
PSNR 은 차이를 제곱해서 쓰므로 크게 틀린 칸을 더 무겁게 셉니다. 사진 분야에서 흔히 쓰는 점수라 논문과 자료에서 자주 봅니다. 노이즈 낀 그림과 필터 결과의 PSNR 을 견주어, 필터를 건 뒤 점수가 오르면 원본에 가까워진 것입니다.
손으로 평균 절대 차이 구하기
| 칸 | 원본 | 결과 | 차이 | 차이의 크기 |
|---|---|---|---|---|
| 1 | 100 | 104 | -4 | 4 |
| 2 | 100 | 97 | 3 | 3 |
| 3 | 60 | 66 | -6 | 6 |
| 4 | 200 | 199 | 1 | 1 |
| 평균 | (4 + 3 + 6 + 1) ÷ 4 = 3.5 |
차이를 그냥 더하면 빼기와 더하기가 지워지므로, 크기(절댓값)로 바꿔 더합니다. 4장에서 표준편차를 구할 때 제곱한 것과 같은 까닭입니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch06_board.png", cv2.IMREAD_GRAYSCALE)
rng = np.random.default_rng(600)
noisy = np.clip(img + rng.normal(0, 20, img.shape), 0, 255).astype(np.uint8)
blur3 = cv2.blur(noisy, (3, 3))
for name, out in [("노이즈 낀 그림", noisy), ("평균 필터 3", blur3)]:
# absdiff: 칸마다 |a - b|. uint8 끼리 빼도 음수가 0 으로 잘리지 않는다
mae = float(cv2.absdiff(out, img).mean())
# PSNR(원본, 견줄 그림): 데시벨 점수
psnr = cv2.PSNR(img, out)
print(f"{name}: 평균 차이 {mae:.2f}, PSNR {psnr:.2f} dB")이 코드를 돌리면 노이즈 낀 그림은 평균 차이 15.81, PSNR 22.23dB 이고, 크기 3 평균 필터를 건 뒤에는 9.38, 24.11dB 로 둘 다 좋아집니다.
주의할 점
out - img처럼uint8끼리 빼면 음수가 255 근처로 돌아갑니다.cv2.absdiff를 쓰거나astype(np.int16)으로 바꿔 뺍니다.- 두 숫자 모두 깨끗한 원본이 있어야 잴 수 있습니다. 실제 현장의 사진은 원본이 없으므로, 이 장처럼 깨끗한 그림에 노이즈를 더해 "어떤 설정이 좋은지" 미리 견주어 본 뒤 씁니다.
- 똑같은 두 그림의 PSNR 은 끝없이 커야 하지만, OpenCV 는 361.2 처럼 아주 큰 수를 돌려줍니다.