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

6장. 평균 필터와 Gaussian 필터

프로젝트 1 — 야간 주차장 사진의 노이즈 줄이기: 가장 좋은 설정 찾기

주차장 관리 업체의 교육을 맡은 강사가, 밤에 찍은 감시 카메라(CCTV, Closed-Circuit Television, 폐쇄 회로 텔레비전) 화면처럼 지글거리는 사진으로 노이즈 제거를 가르치려 합니다. 어떤 필터를 어떤 크기로 걸어야 가장 좋은지 감으로 말하지 않고, 숫자로 견주어 고르는 도구를 만들어 보세요.

사진은 밤의 야외 주차장을 도형으로 그린 가상의 그림입니다. 사람은 없고, 표지판과 자동차는 단순한 도형입니다. 실제 감시 카메라 화면이 아니므로, 깨끗한 그림에 노이즈를 직접 더해 "야간 CCTV 풍" 사진을 만듭니다.

파일내용
ch06_parking.png밤의 야외 주차장 (320×240, 컬러). 주차장 표지판(P), 불 켜진 건물, 흰 주차선, 자동차 두 대

노이즈는 아래처럼 만듭니다.

항목값
종류가우시안 노이즈 (B, G, R 채널마다 따로)
평균, 표준편차0, 25
씨앗600

요구사항

  1. ch06_parking.png 를 컬러로 읽고, 평균 0, 표준편차 25, 씨앗 600 의 가우시안 노이즈를 더해 노이즈 낀 그림을 만듭니다. 노이즈 낀 그림과 원본의 평균 절대 차이와 PSNR 을 찍습니다.
  2. 평균 필터와 Gaussian 필터(시그마 0)를 각각 크기 3, 5, 7, 9 로 걸어, 여덟 가지 설정의 평균 절대 차이와 PSNR 을 표로 찍습니다. 숫자는 소수 둘째 자리까지 적고, PSNR 이 큰 순서로 줄을 세웁니다.
  3. PSNR 이 가장 큰 설정을 고르고, 노이즈 낀 그림보다 PSNR 이 몇 dB 올랐는지 찍습니다. 고른 설정의 결과를 out_ch06_best.png 로 저장합니다.
  4. 원본, 노이즈 낀 그림, 가장 좋은 설정, 가장 나쁜 설정을 2×2 격자로 놓은 비교 그림을 out_ch06_compare.jpg 로 저장하고 화면에 띄웁니다.
  5. 표지판(P) 둘레를 잘라 크게 보고, 평균 절대 차이로 고른 설정이 PSNR 로 고른 설정과 같은지 찍습니다.

힌트

  1. 컬러 그림에 노이즈를 더할 때도 rng.normal(0, 25, img.shape) 처럼 그림의 모양을 그대로 주면 채널마다 따로 만들어집니다.
  2. 설정은 ("평균", k) 와 ("Gaussian", k) 의 목록으로 만들고, 반복문 안에서 cv2.blur 와 cv2.GaussianBlur 를 골라 부르는 함수를 하나 둡니다.
  3. 표는 사전 목록을 pd.DataFrame 으로 바꾸고 sort_values("PSNR", ascending=False) 로 줄을 세웁니다. ascending 은 "오름차순"이라는 뜻입니다.
  4. 컬러 그림을 Matplotlib 으로 띄울 때는 cv2.COLOR_BGR2RGB 로 바꾸고, plt.savefig 를 plt.show() 보다 먼저 부릅니다.
  5. 표지판은 세로 15~80, 가로 25~95 둘레에 있습니다. img[15:80, 25:95] 처럼 잘라 봅니다.

채점 기준

항목기준점수
노이즈 만들기씨앗을 고정해 정해진 세기로 더하고, 0~255 로 잘라 uint8 로 만든다15
견주기 표여덟 가지 설정의 평균 절대 차이와 PSNR 을 PSNR 순서로 찍는다30
고르기와 저장가장 좋은 설정과 오른 dB 를 찍고 out_ch06_best.png 로 저장한다20
비교 그림네 장을 2×2 로 놓아 저장하고 띄운다20
확대와 확인표지판을 잘라 보고, 두 기준으로 고른 설정을 견준다15

코드의 흐름

노이즈 더하기, 후보 만들기, 점수 재기, 가장 좋은 설정 고르기, 비교 그림 저장의 다섯 단계를 이은 흐름도

그림의 다섯 단계를 예시 풀이의 단계와 같은 순서로 짰습니다. 셋째 단계에서 설정마다 같은 원본, 같은 노이즈로 점수를 재야 공정하게 견줄 수 있으므로, 노이즈는 첫 단계에서 한 번만 만들어 모든 설정에 함께 씁니다.

실제 데이터로 해 보려면

실제 사진으로 해 보려면 같은 장면을 밝은 조건에서 찍은 깨끗한 사진이 있어야 점수를 잴 수 있습니다. 깨끗한 사진에 이 프로젝트처럼 노이즈를 더해 설정을 고른 뒤, 실제 어두운 사진에는 고른 설정을 그대로 걸어 눈으로 확인합니다. 공개 데이터로 연습하려면 AI Hub(aihub.or.kr)처럼 이미지 데이터를 공개한 곳에서 내려받습니다. 내려받기 전에 데이터마다 이용 약관(상업적 이용 가능 여부, 재배포 금지 등)을 확인하고, 감시 카메라 영상에는 사람의 얼굴이나 차량 번호가 찍히기 쉬우므로 개인정보 처리 기준을 먼저 확인합니다.

결과 보기 (예시 풀이)

1단계. 노이즈 낀 그림 만들기

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

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

# 요구사항 1: 깨끗한 그림을 컬러로 읽는다
clean = cv2.imread("ch06_parking.png")
print("모양:", clean.shape)

# 평균 0, 표준편차 25, 씨앗 600. 모양을 그대로 주면 B, G, R 채널마다 따로 노이즈가 생긴다
rng = np.random.default_rng(600)
noisy = np.clip(clean.astype(np.float64) + rng.normal(0, 25, clean.shape), 0, 255).astype(np.uint8)

def score(out):
    """원본과의 평균 절대 차이와 PSNR 을 돌려준다."""
    return round(float(cv2.absdiff(out, clean).mean()), 2), round(cv2.PSNR(clean, out), 2)

base_mae, base_psnr = score(noisy)
print(f"노이즈 낀 그림: 평균 차이 {base_mae}, PSNR {base_psnr} dB")
출력
모양: (240, 320, 3)
노이즈 낀 그림: 평균 차이 19.41, PSNR 20.46 dB

2단계. 여덟 가지 설정의 점수 재기

Python
def apply(kind, k, image):
    """설정 이름과 크기에 맞는 필터를 건다."""
    if kind == "평균":
        return cv2.blur(image, (k, k))
    return cv2.GaussianBlur(image, (k, k), 0)   # 시그마 0: 크기에 맞춰 스스로 정한다

# 요구사항 2: 두 필터 × 네 크기 = 여덟 가지
settings = [(kind, k) for kind in ("평균", "Gaussian") for k in (3, 5, 7, 9)]
rows, outputs = [], {}
for kind, k in settings:
    out = apply(kind, k, noisy)
    outputs[(kind, k)] = out
    mae, psnr = score(out)
    rows.append({"필터": kind, "크기": k, "평균차이": mae, "PSNR": psnr})

# PSNR 이 큰 순서로 줄을 세운다. reset_index(drop=True): 줄 번호를 0 부터 새로 매긴다
table = pd.DataFrame(rows).sort_values("PSNR", ascending=False).reset_index(drop=True)
print(table.to_string())
출력
         필터  크기   평균차이   PSNR
0  Gaussian   3   8.99  26.29
1  Gaussian   5   8.28  25.94
2        평균   3   9.05  25.45
3  Gaussian   7   8.58  24.80
4  Gaussian   9   9.22  23.91
5        평균   5   9.37  23.75
6        평균   7  11.09  22.07
7        평균   9  13.22  20.59

3단계. 가장 좋은 설정 고르고 저장하기

Python
# 요구사항 3: 표의 첫 줄이 PSNR 이 가장 큰 설정이다
best = table.iloc[0]
worst = table.iloc[-1]
best_key = (best["필터"], int(best["크기"]))
worst_key = (worst["필터"], int(worst["크기"]))
print(f"가장 좋은 설정: {best_key[0]} {best_key[1]}×{best_key[1]} (PSNR {best['PSNR']} dB)")
print(f"노이즈 낀 그림보다 {round(best['PSNR'] - base_psnr, 2)} dB 올랐습니다")
print(f"가장 나쁜 설정: {worst_key[0]} {worst_key[1]}×{worst_key[1]} (PSNR {worst['PSNR']} dB)")

print("저장:", cv2.imwrite("out_ch06_best.png", outputs[best_key]))
출력
가장 좋은 설정: Gaussian 3×3 (PSNR 26.29 dB)
노이즈 낀 그림보다 5.83 dB 올랐습니다
가장 나쁜 설정: 평균 9×9 (PSNR 20.59 dB)
저장: True

4단계. 2×2 비교 그림 저장하기

Python
# 요구사항 4: 원본, 노이즈, 가장 좋은 설정, 가장 나쁜 설정
panels = [("원본", clean), (f"노이즈 (PSNR {base_psnr})", noisy),
          (f"가장 좋음: {best_key[0]} {best_key[1]} (PSNR {best['PSNR']})", outputs[best_key]),
          (f"가장 나쁨: {worst_key[0]} {worst_key[1]} (PSNR {worst['PSNR']})", outputs[worst_key])]

fig, axes = plt.subplots(2, 2, figsize=(9, 7))
for ax, (title, arr) in zip(axes.flat, panels):
    ax.imshow(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))   # 컬러는 RGB 로 바꿔 띄운다
    ax.set_title(title, fontsize=11)
    ax.axis("off")
plt.tight_layout()
# 저장을 먼저 하고 띄운다(띄운 뒤에는 그림이 닫힐 수 있다)
# 노이즈 낀 그림이 든 격자는 png 로 저장하면 수백 KB 가 되므로, 보기용 비교 그림은 jpg 로 작게 저장한다
plt.savefig("out_ch06_compare.jpg", dpi=100)
print("저장: out_ch06_compare.jpg")
plt.show()
출력
저장: out_ch06_compare.jpg
원본, 노이즈 낀 그림, 가장 좋은 설정, 가장 나쁜 설정의 결과를 2행 2열로 놓은 비교 그림

5단계. 표지판 확대와 두 기준 견주기

Python
# 요구사항 5: 표지판(P) 둘레를 잘라 크게 본다
crop = (slice(15, 80), slice(25, 95))
fig, axes = plt.subplots(1, 4, figsize=(11, 3))
for ax, (title, arr) in zip(axes, panels):
    ax.imshow(cv2.cvtColor(arr[crop], cv2.COLOR_BGR2RGB))
    ax.set_title(title.split(" (")[0], fontsize=10)
    ax.axis("off")
plt.tight_layout()
plt.show()

# 평균 차이가 가장 작은 설정도 같은지 본다
by_mae = table.loc[table["평균차이"].idxmin()]
print(f"평균 차이로 고른 설정: {by_mae['필터']} {int(by_mae['크기'])}×{int(by_mae['크기'])} (평균 차이 {by_mae['평균차이']})")
print("PSNR 로 고른 설정과 같은가:", (by_mae["필터"], int(by_mae["크기"])) == best_key)
출력
평균 차이로 고른 설정: Gaussian 5×5 (평균 차이 8.28)
PSNR 로 고른 설정과 같은가: False
원본, 노이즈, 가장 좋은 설정, 가장 나쁜 설정에서 표지판 둘레만 잘라 크게 본 네 그림

표를 보면 노이즈 낀 그림의 PSNR 은 20.46dB 이고, 여덟 가지 설정 가운데 Gaussian 3×3 이 26.29dB 로 가장 좋아 5.83dB 올랐습니다. 앞의 세 줄이 Gaussian 3, Gaussian 5, 평균 3 으로 모두 작은 커널입니다. 같은 크기끼리 견주면 네 크기 모두 Gaussian 이 평균 필터보다 PSNR 이 높았고, 크기가 커질수록 차이가 벌어져 크기 9 에서는 Gaussian 23.91dB, 평균 20.59dB 였습니다. 평균 9×9 는 가장 나쁜 설정으로, 노이즈 낀 그림(20.46dB)과 거의 같은 점수입니다. 비교 그림에서도 노이즈는 사라졌지만 창문과 주차선이 뿌옇게 번졌습니다. 표지판을 크게 보면 Gaussian 3×3 은 지글거림이 조금 남아도 P 의 테두리가 또렷하고, 평균 9×9 는 P 의 구멍까지 흐려졌습니다.

평균 절대 차이로 고르면 Gaussian 5×5(8.28)가 뽑혀, PSNR 로 고른 Gaussian 3×3(평균 차이 8.99)과 다릅니다. PSNR 은 차이를 제곱해 크게 틀린 칸을 무겁게 세므로, 경계가 덜 번진 크기 3 을 더 높게 쳤습니다. 평균 절대 차이는 고른 면의 작은 흔들림을 더 지운 크기 5 를 높게 쳤습니다. 이처럼 재는 기준에 따라 1등이 바뀔 수 있으므로, 숫자로 후보를 두세 개 추린 뒤 표지판처럼 중요한 곳을 눈으로 확인하고 고릅니다. 이 프로젝트에서는 두 기준 모두 Gaussian 의 작은 커널(3~5)을 가리켰습니다. 점이 드문드문 박힌 노이즈나, 경계를 더 잘 지켜야 하는 사진은 7장의 Median·Bilateral 필터와 견주어 봅니다.

← 이전 절6장 · 실습 5 — 직접 만든 커널과 경계 견주기다음 절 →6장 · 정리