14장. 영상 기하학 변환과 Affine, Perspective
자르기와 크기 맞추기
액자 가게에서 가로로 긴 사진을 정사각형 액자에 넣는다고 해 봅시다. 방법은 세 가지입니다. 사진을 억지로 눌러 넣거나(찌그러짐), 양옆을 잘라 내거나(가장자리를 잃음), 사진을 줄여 넣고 남는 곳에 흰 종이를 덧댑니다(여백이 생김). 딥러닝 모델은 보통 128×128 처럼 정해진 크기의 사진만 받으므로, 크기가 제각각인 사진을 이 세 가지 가운데 하나로 맞춥니다.
그림의 세 상자가 세 가지 방법입니다. 여기서 눌러 넣기가 비율을 무시한 cv2.resize, 양옆 잘라 내기가 가운데 자르기, 흰 종이 덧대기가 레터박스입니다. 어느 방법이든 무엇인가를 잃습니다. 모양, 가장자리, 칸 수 가운데 무엇을 잃어도 되는지에 따라 고릅니다.
320×180 시계 사진을 세 방법으로 128×128 에 맞췄습니다. 늘리기는 동그란 시계가 위아래로 길쭉해졌습니다. 가운데 자르기는 시계 모양은 그대로지만 양 끝의 빨강·파랑 네모가 잘려 나갔습니다. 레터박스는 모든 것이 남았지만 위아래에 회색 띠가 생겼습니다.
자르기는 슬라이싱으로
2장에서 본 것처럼 잘라 내기는 img[y 시작:y 끝, x 시작:x 끝] 한 줄입니다. 13장에서는 관심 영역(ROI)을 골라 잘라 냅니다. 이 장에서는 크기를 맞추려고 가운데를 자릅니다.
| 단계 | 320×180 사진의 계산 |
|---|---|
| ① 짧은 변의 길이 | min(320, 180) = 180 |
| ② 가로에서 버릴 칸 | 320 − 180 = 140, 양쪽에 70칸씩 |
| ③ 자르기 | img[0:180, 70:250] → 180×180 |
| ④ 크기 바꾸기 | 180×180 → 128×128, 줄이므로 INTER_AREA |
레터박스 계산
| 단계 | 320×180 사진의 계산 |
|---|---|
| ① 배율 | 긴 변에 맞춘다: 128 ÷ 320 = 0.4 |
| ② 줄인 크기 | 320 × 0.4 = 128, 180 × 0.4 = 72 → 128×72 |
| ③ 모자라는 칸 | 세로 128 − 72 = 56, 위 28칸, 아래 28칸 |
| ④ 덧대기 | cv2.copyMakeBorder(작은 그림, 28, 28, 0, 0, ...) |
cv2.copyMakeBorder 는 copy and make border(복사하며 테두리 만들기)라는 뜻입니다. 인자는 그림, 위, 아래, 왼쪽, 오른쪽 칸 수, 채우는 방식, 색 순서입니다. 6장에서 본 BORDER_REPLICATE, BORDER_REFLECT 도 여기서 쓸 수 있습니다.
짧은 예시
import cv2
img = cv2.imread("ch14_photos/wide.png")
h, w = img.shape[:2]
print(w, h) # 결과: 320 180
# 가운데 자르기: 짧은 변(180)에 맞춰 양쪽을 똑같이 잘라 낸다
side = min(h, w)
x0, y0 = (w - side) // 2, (h - side) // 2 # 버릴 칸의 절반: x0 = 70, y0 = 0
crop = img[y0:y0 + side, x0:x0 + side]
print(crop.shape) # 결과: (180, 180, 3)
# 레터박스: 긴 변을 128 에 맞추고, 모자란 세로를 위아래로 나눠 덧댄다
scale = 128 / max(h, w)
small = cv2.resize(img, (round(w * scale), round(h * scale)), interpolation=cv2.INTER_AREA)
top = (128 - small.shape[0]) // 2
bottom = 128 - small.shape[0] - top
box = cv2.copyMakeBorder(small, top, bottom, 0, 0, # 위, 아래, 왼쪽, 오른쪽 칸 수
cv2.BORDER_CONSTANT, # 정한 색 하나로 채운다
value=(128, 128, 128)) # 채울 색: 회색
print(small.shape, box.shape) # 결과: (72, 128, 3) (128, 128, 3)// 는 나눈 몫만 남기는 나눗셈입니다. 모자란 칸이 홀수면 위와 아래가 한 칸 다르므로, 아래는 128 - 높이 - top 으로 남은 칸을 모두 채웁니다.
주의할 점
- 가운데 자르기는 물건이 가운데에 있을 때만 알맞습니다. 물건이 가장자리에 있으면 물건이 잘려 나갑니다.
- 레터박스의 여백 색은 학습 데이터 전체에서 같게 씁니다. 사진마다 다르면 모델이 여백 색까지 배울 수 있습니다.
- 물건 탐지(17장부터 다루는 YOLO)처럼 상자 좌표가 있는 데이터는 자르거나 여백을 붙인 만큼 상자 좌표도 함께 옮겨야 합니다.