6장. 평균 필터와 Gaussian 필터
직접 만든 커널
빵집 반죽 틀을 떠올려 봅시다. 가게에서 파는 동그란 틀(cv2.blur, cv2.GaussianBlur)을 써도 되지만, 원하는 모양의 틀을 직접 깎아 찍을 수도 있습니다. cv2.filter2D 는 우리가 숫자를 적어 만든 커널을 받아 그대로 필터를 겁니다. 평균 커널을 손으로 만들어 넣으면 cv2.blur 와 같은 결과가 나옵니다.
그림의 네 단계를 따라 커널을 직접 만들고 씁니다. 둘째 단계의 "합 확인"이 특히 중요합니다. 여기서 반죽 틀이 커널이고, 틀로 찍는 일이 cv2.filter2D 입니다.
커널의 합이 1 이어야 하는 까닭
고른 면(모든 칸 100)에 커널을 걸면 결과는 100 × (커널 숫자의 합) 입니다.
| 커널 | 숫자의 합 | 고른 면 100 의 결과 | 사진 전체 |
|---|---|---|---|
| 아홉 칸 모두 1/9 | 1 | 100 × 1 = 100 | 밝기 그대로 |
| 아홉 칸 모두 1/4.5 | 2 | 100 × 2 = 200 | 두 배로 밝아지고, 255 를 넘는 칸은 255 로 잘린다 |
| 아홉 칸 모두 1/18 | 0.5 | 100 × 0.5 = 50 | 절반으로 어두워진다 |
노이즈만 줄이고 밝기는 지키려면 커널 숫자의 합이 1 이어야 합니다. 그래서 정수로 적은 커널(1-2-1 무게)은 늘 합(16)으로 나눠 씁니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch06_board.png", cv2.IMREAD_GRAYSCALE)
# 평균 커널을 직접 만든다. float32 는 32비트 소수
kernel = np.ones((3, 3), dtype=np.float32) / 9
print(round(float(kernel.sum()), 3)) # 결과: 1.0 → 밝기가 지켜진다
# filter2D(그림, 결과 자료형, 커널): -1 은 원본과 같은 자료형(uint8)으로
mine = cv2.filter2D(img, -1, kernel)
print(np.array_equal(mine, cv2.blur(img, (3, 3)))) # 결과: True → blur 와 똑같다np.ones 는 모든 칸이 1 인 배열입니다. 1/9 을 곱해 아홉 칸이 모두 1/9 인 평균 커널이 됩니다.
주의할 점
- 커널 숫자의 합이 1 이 아니면 사진 전체가 밝아지거나 어두워집니다. 만든 뒤에는
kernel.sum()을 찍어 봅니다. cv2.filter2D는 커널을 뒤집지 않고 그대로 겹쳐 곱합니다. 이 장의 커널은 위아래·좌우가 대칭이라 상관없지만, 대칭이 아닌 커널은 다른 도구(SciPy 등)와 결과가 좌우로 뒤집혀 나올 수 있습니다.- 정수 무게 커널을
cv2.filter2D로 걸면cv2.GaussianBlur와 끝자리가 1 다른 칸이 조금 생깁니다. 두 함수가 소수를 반올림하는 방식이 달라서입니다.