14장. 영상 기하학 변환과 Affine, Perspective
옮기기와 아핀 변환의 짜임
놀이공원의 단체 사진 촬영을 떠올려 봅시다. 사진사가 "모두 오른쪽으로 세 걸음, 뒤로 한 걸음!" 하고 외치면 모든 사람이 같은 규칙으로 움직입니다. 규칙 하나로 모든 사람의 새 자리가 정해집니다. 아핀 변환도 같습니다. 점을 옮기는 규칙을 숫자 여섯 개로 적어 두면, 그림의 모든 칸이 그 규칙대로 옮겨 갑니다.
그림의 왼쪽이 2×3 변환 표, 가운데가 옮길 점, 오른쪽이 새 자리입니다. 표의 윗줄은 새 x 를, 아랫줄은 새 y 를 만듭니다. 줄마다 "첫째 숫자 × x + 둘째 숫자 × y + 셋째 숫자"를 계산합니다. 여기서 "오른쪽으로 세 걸음"이 표의 셋째 칸(옮길 거리)이고, 모든 사람이 같은 규칙을 따르는 것이 모든 칸에 같은 표를 쓰는 것입니다.
2×3 표를 손으로 계산하기
점 (40, 40) 을 오른쪽으로 60칸, 아래로 20칸 옮기는 표는 아래와 같습니다.
| 줄 | 첫째 칸 (x 에 곱함) | 둘째 칸 (y 에 곱함) | 셋째 칸 (더함) | 계산 | 새 값 |
|---|---|---|---|---|---|
| 윗줄 → 새 x | 1 | 0 | 60 | 1 × 40 + 0 × 40 + 60 | 100 |
| 아랫줄 → 새 y | 0 | 1 | 20 | 0 × 40 + 1 × 40 + 20 | 60 |
곱해서 더하는 계산 하나로 여러 변환을 적을 수 있습니다. 표의 숫자만 바꾸면 됩니다.
| 변환 | 2×3 표 | 점 (x, y) 가 가는 곳 | 점 (40, 40) 의 예 |
|---|---|---|---|
| 그대로 | [[1, 0, 0], [0, 1, 0]] | (x, y) | (40, 40) |
| 옮기기 | [[1, 0, 60], [0, 1, 20]] | (x + 60, y + 20) | (100, 60) |
| 절반으로 줄이기 | [[0.5, 0, 0], [0, 0.5, 0]] | (0.5x, 0.5y) | (20, 20) |
| 가로로 2배 | [[2, 0, 0], [0, 1, 0]] | (2x, y) | (80, 40) |
옮기기는 더하기, 키우기와 줄이기는 곱하기입니다. 첫째·둘째 칸이 곱할 수, 셋째 칸이 더할 수입니다.
그림의 세 단계가 아핀 변환을 거는 순서입니다. 표는 np.float32 로 만들고, cv2.warpAffine 에 그림, 표, 결과 크기를 줍니다. 원본이 옮겨 가고 비는 곳은 borderValue 의 색으로 칠합니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch14_grid.png")
h, w = img.shape[:2]
# 오른쪽으로 60, 아래로 20 옮기는 2×3 표. 소수 자료형(float32)으로 만든다
M = np.float32([[1, 0, 60],
[0, 1, 20]])
# 점 하나를 손계산과 같은 방법으로 옮겨 본다: 곱해서 더하기
x, y = 40, 40
print(M[0, 0] * x + M[0, 1] * y + M[0, 2], M[1, 0] * x + M[1, 1] * y + M[1, 2]) # 결과: 100.0 60.0
# cv2.warpAffine(그림, 2×3 표, 결과 크기 (가로, 세로))
moved = cv2.warpAffine(img, M, (w, h))
print(moved[60, 100]) # 결과: [ 40 40 220] → 빨간 점이 (100, 60) 으로 옮겨 갔다
print(moved[5, 5]) # 결과: [0 0 0] → 원본이 닿지 않는 빈 곳은 검정주의할 점
- 표는 반드시 2줄 3칸이고 소수 자료형(
np.float32)이어야 합니다. 정수 목록 그대로 주면 오류가 납니다. - 결과 크기는
resize처럼 (가로, 세로) 순서입니다. 원본 크기를 그대로 주면 밖으로 밀려난 칸은 잘려 나갑니다. - 빈 곳의 색은
borderValue=(255, 255, 255)처럼 바꿀 수 있습니다. 기본은 검정입니다.