14장. 영상 기하학 변환과 Affine, Perspective
실습 1 — resize 와 보간법 비교
모눈 그림의 크기를 세 가지 방법으로 바꾸고, 작은 아이콘을 키울 때와 촘촘한 무늬를 줄일 때 보간법마다 결과가 어떻게 다른지 봅니다. 마지막에 줄였다 키운 그림을 원본과 숫자로 견줍니다.
1단계. 크기를 정하는 세 가지 방법
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.3332단계. 작은 아이콘을 8배로 키우기
# 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가지
3단계. 촘촘한 무늬를 4분의 1로 줄이기
# 굵기 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
4단계. 줄였다 키워 원본과 견주기
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 숫자 하나로 보간법을 고르지 않습니다.