14장. 영상 기하학 변환과 Affine, Perspective
원근 변환
책상 위의 종이를 비스듬히 서서 찍으면, 가까운 아래쪽은 넓게, 먼 위쪽은 좁게 찍혀 사다리꼴이 됩니다. 스캐너 앱은 이 사다리꼴을 바로 위에서 내려다본 직사각형으로 펴 줍니다. 이것이 원근 변환입니다. 아핀 변환으로는 평행사변형까지만 바꿀 수 있지만, 원근 변환은 사다리꼴을 직사각형으로 바꿀 수 있습니다.
왼쪽 상자가 아핀 변환, 오른쪽 상자가 원근 변환입니다. 아핀은 점 세 쌍이면 정해지지만, 원근은 네 귀퉁이를 모두 따로 옮겨야 하므로 점 네 쌍이 필요합니다. 여기서 비스듬한 종이의 네 귀퉁이가 원본의 네 점이고, 펼친 직사각형의 네 귀퉁이가 옮길 네 점입니다.
왼쪽 그림의 네 귀퉁이에 번호를 붙였습니다. 왼쪽 위(1)부터 시계 방향으로 오른쪽 위(2), 오른쪽 아래(3), 왼쪽 아래(4)입니다. 오른쪽은 이 네 점을 240×160 직사각형의 네 귀퉁이로 옮겨 편 결과입니다. 기울었던 글자와 네모가 반듯해졌습니다.
원근 변환 순서
| 단계 | 하는 일 | 코드 |
|---|---|---|
| ① 네 점 찾기 | 비스듬한 종이의 네 귀퉁이 (x, y) | 눈금으로 읽기, 12장의 윤곽선 |
| ② 순서 맞추기 | 왼쪽 위부터 시계 방향 | 목록 순서 확인 |
| ③ 결과 크기 정하기 | 펼 직사각형의 가로, 세로 | 실제 종이 비율로 |
| ④ 3×3 표 계산 | 네 쌍으로 표를 만든다 | cv2.getPerspectiveTransform(src, dst) |
| ⑤ 펴기 | 표대로 그림을 옮긴다 | cv2.warpPerspective(img, M, (가로, 세로)) |
① 의 네 점은 2장에서 본 것처럼 Matplotlib 눈금으로 읽을 수 있습니다. 12장에서 본 윤곽선을 찾고 꼭짓점 넷으로 줄이는 방법을 쓰면 자동으로 찾을 수도 있습니다. 이 장의 실습은 데이터를 만들 때 정한 꼭짓점 좌표를 씁니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch14_board_photo.png")
# ① ② 화이트보드의 네 귀퉁이: 왼쪽 위부터 시계 방향 (x, y)
src = np.float32([[62, 40], [268, 22], [296, 214], [34, 198]])
# ③ 펼 직사각형의 네 귀퉁이: 가로 240, 세로 160 (마지막 칸 번호는 239, 159)
dst = np.float32([[0, 0], [239, 0], [239, 159], [0, 159]])
# ④ cv2.getPerspectiveTransform(원본 네 점, 옮길 네 점) → 3×3 표
M = cv2.getPerspectiveTransform(src, dst)
print(M.shape) # 결과: (3, 3)
# ⑤ cv2.warpPerspective(그림, 3×3 표, 결과 크기 (가로, 세로))
flat = cv2.warpPerspective(img, M, (240, 160))
print(flat.shape) # 결과: (160, 240, 3)원근 변환의 표는 3×3 입니다. 아핀의 2×3 표에 한 줄이 더 붙어, 먼 쪽은 더 크게 늘리고 가까운 쪽은 덜 늘리는 계산을 합니다. 이 계산은 함수가 하므로 표를 직접 만들 일은 거의 없습니다.
주의할 점
- 네 점의 순서가 원본과 결과에서 같아야 합니다. 원본에서 오른쪽 위와 왼쪽 아래를 바꿔 적으면 그림이 꼬여 뒤집힌 결과가 나옵니다(실습 5).
- 결과 크기는 실제 종이의 비율로 정합니다. A4 처럼 비율을 알면 그것을 쓰고, 모르면 위아래·양옆 변의 길이를 재어 짐작합니다.
- 원근 변환은 평평한 면만 바로 폅니다. 구겨진 종이나 둥근 병의 상표는 펴지지 않습니다.