6장. 평균 필터와 Gaussian 필터
실습 2 — 3×3 평균 필터를 손으로 계산하고 cv2.blur 와 견주기
5×5 크기의 아주 작은 그림을 숫자로 만들고, 가운데 칸의 평균 필터 값을 손으로 계산한 뒤 cv2.blur, cv2.boxFilter 의 결과와 맞춰 봅니다. 가장자리 칸이 테두리 처리에 따라 어떻게 달라지는지도 봅니다.
1단계. 작은 그림을 만들고 손으로 계산하기
import cv2
import numpy as np
# 고른 면(100) 가운데에 노이즈로 튄 칸(145)이 있는 5×5 그림
a = np.full((5, 5), 100, dtype=np.uint8)
a[1:4, 1:4] = [[98, 103, 101],
[97, 145, 99],
[102, 100, 96]]
print(a)
# 가운데 칸(2, 2)의 3×3 이웃을 잘라 손계산을 그대로 따라 한다
patch = a[1:4, 1:4].astype(np.float64)
total = patch.sum()
print("이웃 아홉 칸의 합:", int(total))
print("합 ÷ 9 =", round(float(total / 9), 2), "→ 반올림", round(float(total / 9)))[[100 100 100 100 100]
[100 98 103 101 100]
[100 97 145 99 100]
[100 102 100 96 100]
[100 100 100 100 100]]
이웃 아홉 칸의 합: 941
합 ÷ 9 = 104.56 → 반올림 1052단계. cv2.blur 로 계산하기
# blur(그림, (가로, 세로)): 칸마다 이웃 3×3 칸의 평균을 새 값으로 쓴다
blurred = cv2.blur(a, (3, 3))
print(blurred)
print("가운데 칸:", int(a[2, 2]), "→", int(blurred[2, 2]))
# 튄 칸의 몫이 둘레로 퍼졌는지 본다: 가운데 3×3 의 결과
print("가운데 3×3 의 결과:", blurred[1:4, 1:4].tolist())[[ 99 100 100 101 100]
[ 99 105 105 105 100]
[ 99 105 105 105 99]
[100 105 104 104 99]
[101 100 100 99 98]]
가운데 칸: 145 → 105
가운데 3×3 의 결과: [[105, 105, 105], [105, 105, 105], [105, 104, 104]]3단계. cv2.boxFilter 로 평균과 합 구하기
# boxFilter(그림, 결과 자료형, 크기): -1 은 원본과 같은 자료형. normalize 를 주지 않으면 평균을 낸다
box_mean = cv2.boxFilter(a, -1, (3, 3))
print("blur 와 같은가:", np.array_equal(box_mean, blurred))
# normalize=False: 칸 수로 나누지 않고 합만 구한다. 합은 255 를 넘으므로 소수(CV_32F)로 받는다
box_sum = cv2.boxFilter(a, cv2.CV_32F, (3, 3), normalize=False)
print("가운데 칸의 합:", float(box_sum[2, 2]))
print("합 ÷ 9 =", round(float(box_sum[2, 2]) / 9, 2))blur 와 같은가: True
가운데 칸의 합: 941.0
합 ÷ 9 = 104.564단계. 가장자리 칸과 테두리 처리
# 왼쪽 위 모서리(0, 0)는 바깥에 이웃이 없다. 기본값은 끝 칸을 빼고 거울에 비춰 채운다(REFLECT_101)
padded = cv2.copyMakeBorder(a, 1, 1, 1, 1, cv2.BORDER_REFLECT_101)
print("기본 방법으로 덧댄 모서리 3×3:", padded[0:3, 0:3].tolist())
print("기본 방법의 모서리 결과:", int(cv2.blur(a, (3, 3))[0, 0]))
# BORDER_CONSTANT: 바깥을 0 으로 채운다. 모서리는 아홉 칸 가운데 다섯 칸이 0 이 된다
zero_pad = cv2.copyMakeBorder(a, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0)
print("0 으로 덧댄 모서리 3×3:", zero_pad[0:3, 0:3].tolist())
print("0 으로 채운 모서리 결과:", int(cv2.blur(a, (3, 3), borderType=cv2.BORDER_CONSTANT)[0, 0]))기본 방법으로 덧댄 모서리 3×3: [[98, 100, 98], [100, 100, 100], [98, 100, 98]]
기본 방법의 모서리 결과: 99
0 으로 덧댄 모서리 3×3: [[0, 0, 0], [0, 100, 100], [0, 100, 98]]
0 으로 채운 모서리 결과: 44코드 설명
a[1:4, 1:4]의 아홉 칸을 더하면 941, 9 로 나누면 104.56 이고 반올림하면 105 입니다.cv2.blur(a, (3, 3))[2, 2]도 105 로 손계산과 같습니다.- 평균 필터는 튄 칸을 없애지 않고 나눠 퍼뜨립니다. 가운데 3×3 의 결과가 104~105 로, 튄 칸(145)만이 아니라 둘레 여덟 칸도 모두 100 보다 조금 커졌습니다.
cv2.boxFilter(a, -1, (3, 3))는cv2.blur와 같고,normalize=False를 주면 평균 대신 합(941.0)을 돌려줍니다. 합은 255 를 넘을 수 있어cv2.CV_32F(32비트 소수)로 받았습니다.cv2.copyMakeBorder는 바깥에 칸을 덧대 보여 줍니다. 모서리 칸은 기본 방법(REFLECT_101)이면 둘레의 98·100 을 빌려 99, 바깥을 0 으로 채우면 아홉 칸 가운데 다섯 칸이 0 이라 44 가 되어, 0 으로 채운 쪽은 가장자리가 크게 어두워집니다.