OpenCV 비전 실무14장 · 영상 기하학 변환과 Affine, Perspective14 / 21← 이전목차다음 →MetaContents

14장. 영상 기하학 변환과 Affine, Perspective

실습 1 — resize 와 보간법 비교

모눈 그림의 크기를 세 가지 방법으로 바꾸고, 작은 아이콘을 키울 때와 촘촘한 무늬를 줄일 때 보간법마다 결과가 어떻게 다른지 봅니다. 마지막에 줄였다 키운 그림을 원본과 숫자로 견줍니다.

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("ch14_grid.png")
h, w = img.shape[:2]
print("원본 (가로, 세로):", (w, h))

# 방법 1: dsize 로 (가로, 세로) 를 직접 준다
a = cv2.resize(img, (160, 120))
# 방법 2: dsize 자리에 None, 배율 fx(가로)·fy(세로)를 준다
b = cv2.resize(img, None, fx=0.5, fy=0.5)
# 방법 3: 가로만 정하고 세로는 비율로 계산한다(round: 반올림)
new_w = 200
new_h = round(h * new_w / w)
c = cv2.resize(img, (new_w, new_h))

# shape 는 (세로, 가로, 채널) 순서로 찍힌다
for name, arr in [("dsize=(160, 120)", a), ("fx=fy=0.5", b), (f"가로 {new_w}, 비율 지킴", c)]:
    print(f"{name:16} → shape {arr.shape}, 가로÷세로 = {arr.shape[1] / arr.shape[0]:.3f}")
출력
원본 (가로, 세로): (320, 240)
dsize=(160, 120) → shape (120, 160, 3), 가로÷세로 = 1.333
fx=fy=0.5        → shape (120, 160, 3), 가로÷세로 = 1.333
가로 200, 비율 지킴    → shape (150, 200, 3), 가로÷세로 = 1.333

2단계. 작은 아이콘을 8배로 키우기

Python
# 32×24 아주 작은 아이콘
icon = cv2.imread("ch14_icon.png")
print("아이콘 shape:", icon.shape)

methods = {"NEAREST": cv2.INTER_NEAREST, "LINEAR": cv2.INTER_LINEAR, "CUBIC": cv2.INTER_CUBIC}
bigs = {}
for name, flag in methods.items():
    # interpolation: 새로 생긴 칸을 채우는 방법
    bigs[name] = cv2.resize(icon, None, fx=8, fy=8, interpolation=flag)
    # 서로 다른 색이 몇 가지인지 센다. 칸 하나의 B, G, R 세 숫자를 한 묶음으로 본다
    colors = len(np.unique(bigs[name].reshape(-1, 3), axis=0))
    print(f"{name:8} shape {bigs[name].shape}, 서로 다른 색 {colors}가지")

fig, axes = plt.subplots(1, 4, figsize=(12, 2.8))
axes[0].imshow(cv2.cvtColor(icon, cv2.COLOR_BGR2RGB))
axes[0].set_title("원본 32×24")
for ax, (name, big) in zip(axes[1:], bigs.items()):
    ax.imshow(cv2.cvtColor(big, cv2.COLOR_BGR2RGB))
    ax.set_title(f"{name} 8배")
for ax in axes:
    ax.axis("off")
plt.tight_layout()
plt.show()
출력
아이콘 shape: (24, 32, 3)
NEAREST  shape (192, 256, 3), 서로 다른 색 5가지
LINEAR   shape (192, 256, 3), 서로 다른 색 508가지
CUBIC    shape (192, 256, 3), 서로 다른 색 3260가지
32×24 아이콘의 원본과 NEAREST, LINEAR, CUBIC 으로 8배 키운 결과

3단계. 촘촘한 무늬를 4분의 1로 줄이기

Python
# 굵기 2칸의 검고 흰 고리가 촘촘한 동심원 무늬(흑백)
rings = cv2.imread("ch14_rings.png", cv2.IMREAD_GRAYSCALE)

smalls = {}
for name, flag in [("NEAREST", cv2.INTER_NEAREST), ("LINEAR", cv2.INTER_LINEAR), ("AREA", cv2.INTER_AREA)]:
    smalls[name] = cv2.resize(rings, (80, 60), interpolation=flag)
    # 표준편차: 밝기가 들쭉날쭉한 정도. 고리가 고르게 섞였으면 작다
    print(f"{name:8} 평균 {round(float(smalls[name].mean()), 1):6} / 표준편차 {round(float(smalls[name].std()), 1)}")

fig, axes = plt.subplots(1, 4, figsize=(12, 2.8))
axes[0].imshow(rings, cmap="gray", vmin=0, vmax=255)
axes[0].set_title("원본 320×240")
for ax, (name, sm) in zip(axes[1:], smalls.items()):
    # interpolation="nearest": 화면에 띄울 때 칸을 부드럽게 섞지 않고 그대로 크게 보여 준다(2차시)
    ax.imshow(sm, cmap="gray", vmin=0, vmax=255, interpolation="nearest")
    ax.set_title(f"{name} 80×60")
for ax in axes:
    ax.axis("off")
plt.tight_layout()
plt.show()
출력
NEAREST  평균   58.2 / 표준편차 107.0
LINEAR   평균   69.4 / 표준편차 64.1
AREA     평균   65.4 / 표준편차 17.1
동심원 무늬의 원본과 NEAREST, LINEAR, AREA 로 4분의 1 줄인 결과

4단계. 줄였다 키워 원본과 견주기

Python
import pandas as pd

rows = []
for name, flag in [("NEAREST", cv2.INTER_NEAREST), ("LINEAR", cv2.INTER_LINEAR),
                   ("CUBIC", cv2.INTER_CUBIC), ("AREA", cv2.INTER_AREA)]:
    # 줄일 때만 방법을 바꾸고, 키울 때는 모두 LINEAR 로 같게 한다
    small = cv2.resize(img, (80, 60), interpolation=flag)
    back = cv2.resize(small, (w, h), interpolation=cv2.INTER_LINEAR)
    # 평균 절대 차이(MAE): 작을수록 원본에 가깝다(6차시)
    rows.append({"줄일 때 방법": name, "MAE": round(float(cv2.absdiff(img, back).mean()), 2)})

print(pd.DataFrame(rows).to_string(index=False))
출력
줄일 때 방법   MAE
NEAREST 13.01
 LINEAR  8.13
  CUBIC  8.64
   AREA  9.48

코드 설명

  • cv2.resize(img, (160, 120)) 와 cv2.resize(img, None, fx=0.5, fy=0.5) 는 같은 (120, 160, 3) 을 만듭니다. dsize 는 (가로, 세로), shape 는 (세로, 가로) 순서입니다. 가로를 200 으로 정하고 세로를 round(240 × 200 ÷ 320) = 150 으로 계산하면 세 결과 모두 가로÷세로가 1.333(4:3)으로 원본과 같습니다.
  • 아이콘을 8배로 키우면 INTER_NEAREST 는 서로 다른 색이 5가지로 원본의 색만 그대로 씁니다. INTER_LINEAR 는 508가지, INTER_CUBIC 은 3,260가지로, 이웃 색을 섞은 새 색이 생겨 경계가 부드러워집니다. 값이 바뀌면 안 되는 마스크에 INTER_NEAREST 를 쓰는 까닭이 이것입니다.
  • 동심원을 4분의 1로 줄이면 INTER_NEAREST 의 표준편차가 107.0 으로 검고 흰 칸이 그대로 남아 굵은 물결무늬(계단 현상)가 생깁니다. INTER_AREA 는 17.1 로 고리를 평균 내어 고른 회색이 됩니다. INTER_LINEAR 는 64.1 로 그 사이인데, 줄일 때도 둘레 4칸만 보므로 넓은 범위를 평균 내지 못하기 때문입니다.
  • 줄였다 키운 모눈 그림의 MAE 는 NEAREST 13.01 이 가장 컸고, LINEAR 8.13, CUBIC 8.64, AREA 9.48 이었습니다. 이 모눈 그림은 넓은 빈 면에 가는 선이 몇 줄 있을 뿐이라, AREA 가 가는 선을 둘레와 평균 내어 옅게 만든 차이가 MAE 에 드러났습니다. 촘촘한 무늬의 계단 현상은 3단계처럼 눈과 표준편차로 봐야 하고, MAE 숫자 하나로 보간법을 고르지 않습니다.
← 이전 절14장 · 실습 지도다음 절 →14장 · 실습 2 — 가운데 자르기와 레터박스로 128×128 맞추기