6장. 평균 필터와 Gaussian 필터
필터와 커널
신문의 작은 글씨를 돋보기로 한 군데씩 옮겨 가며 읽는 장면을 떠올려 봅시다. 돋보기는 늘 둘레 몇 글자를 함께 보여 줍니다. 필터도 사진 위에 작은 창을 올려놓고, 창 안의 이웃 칸들을 보고 가운데 칸의 새 값을 정합니다. 그리고 한 칸 옆으로 옮겨 같은 일을 되풀이합니다. 이 창에 적힌 작은 숫자 표가 커널입니다.
그림의 네 단계가 칸 하나의 새 값을 정하는 순서입니다. 이 네 단계를 모든 칸에서 되풀이하면 새 사진 한 장이 됩니다. 여기서 돋보기가 커널이고, 돋보기에 비친 글자들이 이웃 칸이고, 돋보기를 옮기며 읽는 일이 필터를 거는 일입니다. 비유와 다른 점이 하나 있습니다. 돋보기는 보기만 하지만, 커널은 칸마다 무게(곱할 숫자)를 가지고 있어 계산을 합니다.
3×3 커널로 손계산하기
노이즈 때문에 가운데 칸만 145 로 튀어 오른 곳이 있다고 해 봅시다. 평균 필터의 커널은 아홉 칸이 모두 1/9 입니다.
왼쪽이 이웃 아홉 칸의 밝기, 가운데가 커널, 오른쪽이 결과입니다. 칸마다 같은 자리의 숫자끼리 곱해 모두 더하면 가운데 칸의 새 값이 됩니다.
| 단계 | 계산 |
|---|---|
| ① 이웃 아홉 칸 | 98, 103, 101, 97, 145, 99, 102, 100, 96 |
| ② 칸마다 1/9 을 곱해 더하기 | (98 + 103 + 101 + 97 + 145 + 99 + 102 + 100 + 96) × 1/9 |
| ③ 더한 값 | 941 × 1/9 = 104.56 |
| ④ 정수로 반올림 | 105 |
145 로 튀었던 칸이 105 가 되었습니다. 둘레가 100 근처이므로 튄 값이 이웃에 섞여 크게 누그러진 것입니다.
짧은 예시
import numpy as np
patch = np.array([[98, 103, 101],
[97, 145, 99],
[102, 100, 96]], dtype=np.float64)
# 평균 필터의 커널: 아홉 칸 모두 1/9
kernel = np.full((3, 3), 1 / 9)
# 같은 자리끼리 곱한 뒤(*) 모두 더한다(sum)
value = (patch * kernel).sum()
print(round(float(value), 2), round(float(value))) # 결과: 104.56 105np.full((3, 3), 1 / 9) 은 3×3 칸을 모두 1/9 로 채운 표입니다. full 은 "가득 채운"이라는 뜻입니다.
주의할 점
- 새 값은 원래 그림의 이웃으로 계산합니다. 앞 칸에서 이미 바꾼 값을 다음 칸의 계산에 쓰지 않습니다. OpenCV 는 결과를 새 그림에 따로 담으므로 걱정하지 않아도 됩니다.
- 커널을 밀며 곱해 더하는 계산을 넓게 합성곱이라고 부릅니다. 딥러닝의 CNN 은 이 커널의 숫자를 사람이 정하지 않고 데이터로 배웁니다.