OpenCV 비전 실무20장 · 교육 콘텐츠 설계와 종합 실습17 / 21← 이전목차다음 →MetaContents

20장. 교육 콘텐츠 설계와 종합 실습

실습 4 — 전후 비교 격자와 한 장 요약 만들기

6장의 주차장 그림(ch06_parking.png)에 6장에서 쓴 것과 같은 노이즈(표준편차 20, 씨앗 600)를 더하고 필터 세 가지를 견줍니다. 수업 슬라이드에 붙일 전후 비교 격자 out_ch20_compare.png 와, 흐름·그림·숫자를 한 장에 모은 요약 out_ch20_onepage.png 를 코드로 만듭니다.

1단계. 노이즈를 더하고 필터 셋 걸기

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

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

clean = cv2.imread("ch06_parking.png")
rng = np.random.default_rng(600)
# 6차시와 같은 노이즈 함수: 소수로 바꿔 더하고 0~255 로 자른 뒤 uint8 로
noisy = np.clip(clean.astype(np.float64) + rng.normal(0, 20, clean.shape), 0, 255).astype(np.uint8)

results = {
    "노이즈": noisy,
    "Gaussian 5×5": cv2.GaussianBlur(noisy, (5, 5), 0),
    "Median 5": cv2.medianBlur(noisy, 5),
    # bilateralFilter(그림, 이웃 지름, 색 차이 시그마, 거리 시그마)
    "Bilateral": cv2.bilateralFilter(noisy, 9, 75, 75),
}
# PSNR: 클수록 원본에 가깝다(6차시)
psnr = {name: round(float(cv2.PSNR(clean, im)), 2) for name, im in results.items()}
for name, value in psnr.items():
    print(f"{name:13} PSNR {value} dB")
출력
노이즈           PSNR 22.29 dB
Gaussian 5×5  PSNR 26.41 dB
Median 5      PSNR 27.52 dB
Bilateral     PSNR 33.12 dB

2단계. 전후 비교 격자 저장하기

Python
def compare_grid(images, titles, path, cols=3):
    """그림 목록을 같은 크기의 격자로 놓고 제목을 달아 저장한다. 슬라이드에 그대로 붙인다."""
    rows = (len(images) + cols - 1) // cols          # 그림 수를 칸 수로 나눠 올림한 줄 수
    fig, axes = plt.subplots(rows, cols, figsize=(4 * cols, 3.3 * rows))
    for ax in axes.flat:
        ax.axis("off")                               # 빈칸이 남아도 테두리가 보이지 않게
    for ax, im, title in zip(axes.flat, images, titles):
        ax.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
        ax.set_title(title)
    plt.tight_layout()
    fig.savefig(path, dpi=100)                       # dpi: 1인치에 넣을 점 수. 100 이면 가로 1200칸
    return fig

# 가장 좋은 필터를 PSNR 로 고른다(노이즈 자신은 빼고)
best = max((n for n in psnr if n != "노이즈"), key=psnr.get)
order = ["노이즈", best]
fig = compare_grid([clean] + [results[n] for n in order],
                   ["원본"] + [f"{n} ({psnr[n]} dB)" for n in order],
                   "out_ch20_compare.png")
print("가장 좋은 필터:", best)
print("저장한 격자:", os.path.getsize("out_ch20_compare.png") // 1024, "KB")
plt.show()
출력
가장 좋은 필터: Bilateral
저장한 격자: 537 KB
원본, 노이즈, 가장 좋은 필터의 결과를 PSNR 과 함께 나란히 놓은 전후 비교 격자

3단계. 흐름, 그림, 숫자를 한 장에 모으기

Python
fig = plt.figure(figsize=(12, 6.5))
# GridSpec: 그림 판을 3줄 4칸으로 나눈다. height_ratios 로 줄 높이 비율을 정한다
grid = fig.add_gridspec(3, 4, height_ratios=[0.8, 3, 1.6])

# 맨 위 줄: 처리 흐름을 상자와 화살표로
flow = fig.add_subplot(grid[0, :])
flow.axis("off")
steps = ["원본 읽기", "노이즈 더하기", "필터 걸기", "PSNR 재기"]
for k, step in enumerate(steps):
    x = 0.12 + k * 0.25
    flow.text(x, 0.5, step, ha="center", va="center", fontsize=13,
              bbox=dict(boxstyle="round", facecolor="white"))
    if k < len(steps) - 1:
        # annotate 의 화살표: xytext(꼬리)에서 xy(머리)로
        flow.annotate("", xy=(x + 0.17, 0.5), xytext=(x + 0.08, 0.5), arrowprops=dict(arrowstyle="->"))

# 가운데 줄: 그림 네 장
for k, (name, im) in enumerate([("원본", clean)] + [(n, results[n]) for n in ["노이즈", "Median 5", "Bilateral"]]):
    ax = fig.add_subplot(grid[1, k])
    ax.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
    ax.set_title(name)
    ax.axis("off")

# 아래 줄: 숫자 막대
bar = fig.add_subplot(grid[2, :])
bar.barh(list(psnr.keys()), list(psnr.values()))
bar.set_xlabel("PSNR (dB, 클수록 원본에 가깝다)")
bar.invert_yaxis()
fig.suptitle(f"노이즈 줄이기 한 장 요약: 가장 좋은 필터는 {best}", fontsize=15)
fig.tight_layout()
fig.savefig("out_ch20_onepage.png", dpi=100)
plt.show()
처리 흐름, 그림 네 장, PSNR 막대를 한 장에 모은 요약 그림

4단계. 슬라이드에 맞는지 크기 확인하기

Python
# 저장한 그림을 다시 읽어 가로세로 비율을 본다. 슬라이드는 보통 16:9(1.78)다
for path in ["out_ch20_compare.png", "out_ch20_onepage.png"]:
    im = cv2.imread(path)
    h, w = im.shape[:2]
    print(f"{path}: {w}×{h}, 비율 {w / h:.2f}, {os.path.getsize(path) // 1024} KB")
출력
out_ch20_compare.png: 1200×330, 비율 3.64, 537 KB
out_ch20_onepage.png: 1200×650, 비율 1.85, 381 KB

코드 설명

  • 노이즈 함수와 씨앗(600), 표준편차(20)를 6장에서 쓴 그대로 썼습니다. 같은 씨앗이라 6장의 그림과 칸마다 같은 노이즈입니다.
  • compare_grid 는 그림 목록과 제목 목록을 받아 격자로 놓고 저장합니다. 한 번 만들어 두면 수업마다 그림만 바꿔 씁니다.
  • fig.add_gridspec(3, 4, height_ratios=[...]) 는 판을 칸으로 나누고, grid[0, :] 처럼 여러 칸을 묶어 하나의 그림 자리로 씁니다.
  • fig.savefig(path, dpi=100) 는 그림 크기(인치) × dpi 만큼의 칸으로 저장합니다. 가로 12인치 × 100 이면 가로 1200칸입니다.
  • 교안 PC 에서 PSNR 은 노이즈 22.29dB, Gaussian 26.41dB, Median 27.52dB, Bilateral 33.12dB 로 Bilateral 이 가장 높았습니다. 비교 격자는 1200×330(비율 3.64)으로 가로로 길어 슬라이드의 띠 하나에, 한 장 요약은 1200×650(비율 1.85)으로 16:9 슬라이드 한 장에 알맞습니다.
← 이전 절20장 · 실습 3 — 자동 채점기 만들기다음 절 →20장 · 실습 5 — 수업 자료 묶음 만들기