OpenCV 비전 실무6장 · 평균 필터와 Gaussian 필터18 / 21← 이전목차다음 →MetaContents

6장. 평균 필터와 Gaussian 필터

실습 5 — 직접 만든 커널과 경계 견주기

cv2.filter2D 에 평균 커널을 직접 만들어 넣고 cv2.blur 와 같은지 확인합니다. 커널 숫자의 합이 1 이 아니면 밝기가 어떻게 바뀌는지 보고, 마지막으로 평균 필터와 Gaussian 필터가 경계 한 줄을 어떻게 바꾸는지 선 그래프로 견줍니다.

1단계. 평균 커널을 직접 만들어 걸기

Python
import cv2
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

img = cv2.imread("ch06_board.png", cv2.IMREAD_GRAYSCALE)

# 아홉 칸 모두 1/9 인 평균 커널. filter2D 는 소수(float32) 커널을 받는다
mean_kernel = np.ones((3, 3), dtype=np.float32) / 9
print("커널 숫자의 합:", round(float(mean_kernel.sum()), 3))

# filter2D(그림, 결과 자료형, 커널): -1 은 원본과 같은 자료형(uint8)
mine = cv2.filter2D(img, -1, mean_kernel)
print("cv2.blur 와 똑같은가:", np.array_equal(mine, cv2.blur(img, (3, 3))))

# 크기 5 도 같은지 본다
mean5 = np.ones((5, 5), dtype=np.float32) / 25
print("크기 5 도 똑같은가:", np.array_equal(cv2.filter2D(img, -1, mean5), cv2.blur(img, (5, 5))))
출력
커널 숫자의 합: 1.0
cv2.blur 와 똑같은가: True
크기 5 도 똑같은가: True

2단계. 커널 숫자의 합과 밝기

Python
# 합이 2, 1, 0.5 인 커널 세 개를 견준다
kernels = {
    "합 2 (모두 1/4.5)": np.ones((3, 3), dtype=np.float32) / 4.5,
    "합 1 (모두 1/9)": np.ones((3, 3), dtype=np.float32) / 9,
    "합 0.5 (모두 1/18)": np.ones((3, 3), dtype=np.float32) / 18,
}
print("원본의 평균 밝기:", round(float(img.mean()), 1))
for name, k in kernels.items():
    out = cv2.filter2D(img, -1, k)
    # 255 로 잘린 칸의 비율: 합이 1 보다 크면 밝은 칸이 넘쳐 잘린다
    print(f"{name}: 평균 밝기 {float(out.mean()):.1f}, 255 인 칸 비율 {float((out == 255).mean()):.3f}")

# 가운데를 강조한 정수 무게 1-2-1 커널. 합 16 으로 나눠 합을 1 로 맞춘다
center = np.array([[1, 2, 1],
                   [2, 4, 2],
                   [1, 2, 1]], dtype=np.float32) / 16
by_hand = cv2.filter2D(img, -1, center)
gauss3 = cv2.GaussianBlur(img, (3, 3), 0)
diff = cv2.absdiff(by_hand, gauss3)
print("GaussianBlur(3) 와 가장 크게 다른 값:", int(diff.max()), "/ 다른 칸 수:", int((diff > 0).sum()))
출력
원본의 평균 밝기: 123.5
합 2 (모두 1/4.5): 평균 밝기 190.1, 255 인 칸 비율 0.541
합 1 (모두 1/9): 평균 밝기 123.5, 255 인 칸 비율 0.000
합 0.5 (모두 1/18): 평균 밝기 61.8, 255 인 칸 비율 0.000
GaussianBlur(3) 와 가장 크게 다른 값: 1 / 다른 칸 수: 1427

3단계. 경계 한 줄의 밝기 견주기

Python
row = 165   # 어두운 바닥(60)과 밝은 바닥(200)이 만나는 가로줄
xs = np.arange(130, 190)   # 경계(가로 160) 둘레의 60칸

mean9 = cv2.blur(img, (9, 9))
gauss9 = cv2.GaussianBlur(img, (9, 9), 0)

# 경계가 번진 폭: 밝기가 70~190 인 칸 수
for name, arr in [("원본", img), ("평균 9×9", mean9), ("Gaussian 9×9", gauss9)]:
    line = arr[row, 130:190]
    print(f"{name}: 번진 폭 {int(((line > 70) & (line < 190)).sum())}칸")

fig, ax = plt.subplots(figsize=(8, 3.6))
ax.plot(xs, img[row, 130:190], label="원본")
ax.plot(xs, mean9[row, 130:190], label="평균 필터 9×9")
ax.plot(xs, gauss9[row, 130:190], label="Gaussian 9×9")
ax.set_xlabel("가로 칸 번호")
ax.set_ylabel("밝기")
ax.set_ylim(40, 240)
ax.legend(loc="upper left")
ax.set_title("경계 한 줄의 밝기 (가로줄 165)")
plt.tight_layout()
plt.show()
출력
원본: 번진 폭 0칸
평균 9×9: 번진 폭 8칸
Gaussian 9×9: 번진 폭 4칸
경계 한 줄의 밝기를 원본, 크기 9 평균 필터, 크기 9 Gaussian 필터로 겹쳐 그린 선 그래프

코드 설명

  • cv2.filter2D(img, -1, mean_kernel) 은 직접 만든 1/9 커널로 필터를 겁니다. 크기 3 과 5 모두 cv2.blur 와 똑같습니다(True). cv2.blur 가 하는 일이 바로 "모든 칸이 같은 커널로 곱해 더하기"라는 뜻입니다.
  • 커널 숫자의 합이 2 면 평균 밝기가 123.5 에서 190.1 로 오르고 0.541(절반 넘는 칸)이 255 로 잘립니다. 합이 0.5 면 61.8 로 절반쯤 어두워집니다. 노이즈만 줄이려면 합을 1 로 맞춥니다.
  • 1-2-1 정수 무게를 16 으로 나눈 커널은 cv2.GaussianBlur(img, (3, 3), 0) 과 거의 같고, 반올림 방식 차이로 끝자리가 1 다른 칸이 1,427개 있습니다.
  • 경계 한 줄을 보면 번진 폭이 평균 필터 8칸, Gaussian 4칸입니다. 그래프에서 평균 필터(주황)는 가로 155~164 에 곧은 비탈로 넓게 번졌고, Gaussian(초록)은 경계 가까이에서 가파르게 올라 원본(파랑)의 계단에 더 가깝습니다. 그래프의 선 색은 Matplotlib 의 기본 색입니다.
← 이전 절6장 · 실습 4 — Gaussian 필터와 sigma다음 절 →6장 · 프로젝트 1 — 야간 주차장 사진의 노이즈 줄이기: 가장 좋은 설정 찾기