14장. 영상 기하학 변환과 Affine, Perspective
세 점으로 아핀 변환
천막을 칠 때 말뚝 세 개를 박으면 천의 모양이 정해집니다. 말뚝 하나를 옮기면 천 전체가 따라 기울어집니다. 아핀 변환도 점 세 개가 어디로 갈지만 정하면 나머지 모든 점이 어디로 갈지 정해집니다. cv2.getAffineTransform 은 "이 세 점을 저 세 점으로"를 받아 2×3 표를 계산해 줍니다.
왼쪽의 세 점(빨강, 초록, 주황)을 오른쪽의 세 점으로 옮기도록 정했습니다. 위의 두 점은 오른쪽으로 30칸, 아래 점은 왼쪽으로 30칸 옮겨, 네모가 평행사변형으로 기울었습니다(기울이기, shear). 정하지 않은 넷째 점(파랑)도 저절로 따라갑니다. 여기서 말뚝 세 개가 세 쌍의 점이고, 천 전체가 그림의 모든 칸입니다.
아핀 변환이 지키는 것과 바꾸는 것
| 지키는 것 | 바꿀 수 있는 것 |
|---|---|
| 곧은 선은 곧은 선으로 | 길이 |
| 평행한 선은 평행한 선으로 | 각도(직각이 비스듬해질 수 있다) |
| 한 선 위의 점들의 순서와 간격 비율 | 넓이 |
평행한 선이 평행하게 남으므로, 네모는 평행사변형까지만 바뀝니다. 위가 좁고 아래가 넓은 사다리꼴로는 바꿀 수 없습니다. 그것은 다음 절의 원근 변환이 합니다.
넷째 점을 손으로 짐작하기
평행사변형에서는 "넷째 점 = 둘째 점 + 셋째 점 − 첫째 점"입니다. 왼쪽 위 (40, 40) → (70, 40), 오른쪽 위 (280, 40) → (310, 40), 왼쪽 아래 (40, 200) → (10, 200) 이면, 오른쪽 아래 (280, 200) 은 (310 + 10 − 70, 40 + 200 − 40) = (250, 200) 으로 갑니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch14_grid.png")
h, w = img.shape[:2]
# 원본의 세 점: 왼쪽 위, 오른쪽 위, 왼쪽 아래 색 점 (x, y)
src = np.float32([[40, 40], [280, 40], [40, 200]])
# 옮길 세 점: 위는 오른쪽으로 30, 아래는 왼쪽으로 30
dst = np.float32([[70, 40], [310, 40], [10, 200]])
# cv2.getAffineTransform(원본 세 점, 옮길 세 점) → 2×3 표
M = cv2.getAffineTransform(src, dst)
print(M.round(3))
# 결과: [[ 1. -0.375 45. ]
# [-0. 1. 0. ]] ← -0. 은 0 과 같다(소수 계산의 부호만 남은 것)
# 넷째 점 (280, 200) 이 가는 곳: 표에 곱해 더한다
x, y = 280, 200
print(float(M[0, 0] * x + M[0, 1] * y + M[0, 2]), float(M[1, 0] * x + M[1, 1] * y + M[1, 2])) # 결과: 250.0 200.0
sheared = cv2.warpAffine(img, M, (w, h), borderValue=(255, 255, 255))표의 윗줄 둘째 칸 −0.375 가 기울이기를 만듭니다. 새 x 에 y 의 −0.375 배가 더해지므로, 아래로 갈수록(y 가 클수록) 왼쪽으로 밀립니다. 160칸 내려가면 160 × 0.375 = 60칸 밀립니다. 위 두 점이 오른쪽으로 30, 아래 점이 왼쪽으로 30 이니 차이가 60 입니다.
주의할 점
- 세 점은 한 줄 위에 있으면 안 됩니다. 한 줄 위의 세 점으로는 천이 펴지지 않아 표를 계산할 수 없습니다.
- 원본과 옮길 점의 순서는 짝이 맞아야 합니다. 첫째 점은 첫째 점으로 갑니다.
- 점 목록은
np.float32로 줍니다.