OpenCV 비전 실무14장 · 영상 기하학 변환과 Affine, Perspective17 / 21← 이전목차다음 →MetaContents

14장. 영상 기하학 변환과 Affine, Perspective

실습 4 — 회전과 세 점 아핀 변환

cv2.getRotationMatrix2D 로 모눈 그림을 30도 돌립니다. 원본 크기로 돌리면 모서리가 잘리는 것을 네 구석 점의 새 자리로 확인하고, 결과 크기를 키워 안 잘리게 돌립니다. 마지막으로 세 점을 짝지어 그림을 기울입니다.

1단계. 원본 크기로 30도 돌리기

Python
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]
center = (w / 2, h / 2)

# getRotationMatrix2D(중심 (x, y), 각도(양수는 반시계), 배율)
M = cv2.getRotationMatrix2D(center, 30, 1.0)
print("2×3 표:")
print(M.round(3))

def move_point(M, x, y):
    """2×3 표로 점 하나를 옮긴다: 줄마다 곱해서 더하기."""
    return (float(M[0, 0] * x + M[0, 1] * y + M[0, 2]),
            float(M[1, 0] * x + M[1, 1] * y + M[1, 2]))

# 그림의 네 모서리가 어디로 가는지 보고, 그림 밖(0 미만 또는 크기 이상)이면 잘린 것이다
corners = {"왼쪽 위": (0, 0), "오른쪽 위": (w - 1, 0), "오른쪽 아래": (w - 1, h - 1), "왼쪽 아래": (0, h - 1)}
for name, (x, y) in corners.items():
    nx, ny = move_point(M, x, y)
    inside = 0 <= nx < w and 0 <= ny < h
    print(f"{name:6} ({x}, {y}) → ({nx:.1f}, {ny:.1f}) {'안' if inside else '밖: 잘림'}")

rot_cut = cv2.warpAffine(img, M, (w, h))
출력
2×3 표:
[[  0.866   0.5   -38.564]
 [ -0.5     0.866  96.077]]
왼쪽 위   (0, 0) → (-38.6, 96.1) 밖: 잘림
오른쪽 위  (319, 0) → (237.7, -63.4) 밖: 잘림
오른쪽 아래 (319, 239) → (357.2, 143.6) 밖: 잘림
왼쪽 아래  (0, 239) → (80.9, 303.1) 밖: 잘림

2단계. 결과 크기를 키워 안 잘리게 돌리기

Python
# 표의 첫째 칸과 둘째 칸 숫자(0.866, 0.5)로 돌린 그림을 모두 담는 가로·세로를 계산한다
a, b = abs(M[0, 0]), abs(M[0, 1])
new_w = int(round(h * b + w * a))
new_h = int(round(h * a + w * b))
print(f"새 크기: 가로 {new_w}, 세로 {new_h}")

# 표의 셋째 칸(옮길 거리)을 고쳐 그림의 중심이 새 그림의 한가운데로 오게 한다
M2 = M.copy()
M2[0, 2] += new_w / 2 - center[0]
M2[1, 2] += new_h / 2 - center[1]
for name, (x, y) in corners.items():
    nx, ny = move_point(M2, x, y)
    inside = -0.5 <= nx < new_w and -0.5 <= ny < new_h
    print(f"{name:6} → ({nx:.1f}, {ny:.1f}) {'안' if inside else '밖'}")

rot_full = cv2.warpAffine(img, M2, (new_w, new_h), borderValue=(255, 255, 255))
print("돌린 그림 shape:", rot_full.shape)
출력
새 크기: 가로 397, 세로 368
왼쪽 위   → (-0.1, 160.1) 안
오른쪽 위  → (276.2, 0.6) 안
오른쪽 아래 → (395.7, 207.6) 안
왼쪽 아래  → (119.4, 367.1) 안
돌린 그림 shape: (368, 397, 3)

3단계. 세 점을 짝지어 기울이기

Python
# 원본의 세 점(왼쪽 위 빨강, 오른쪽 위 초록, 왼쪽 아래 주황)과 옮길 세 점
src = np.float32([[40, 40], [280, 40], [40, 200]])
dst = np.float32([[70, 40], [310, 40], [10, 200]])

# getAffineTransform(원본 세 점, 옮길 세 점) → 2×3 표
A = cv2.getAffineTransform(src, dst)
print("기울이기 표:")
print(A.round(3))

# 정하지 않은 넷째 점(오른쪽 아래 파랑, (280, 200))이 가는 곳
nx, ny = move_point(A, 280, 200)
print(f"넷째 점 (280, 200) → ({nx:.1f}, {ny:.1f})")

sheared = cv2.warpAffine(img, A, (w, h), borderValue=(255, 255, 255))
# 파란 점이 실제로 그 자리에 있는지 색으로 확인한다
print("그 자리의 색:", [int(v) for v in sheared[int(round(ny)), int(round(nx))]])
출력
기울이기 표:
[[ 1.    -0.375 45.   ]
 [-0.     1.     0.   ]]
넷째 점 (280, 200) → (250.0, 200.0)
그 자리의 색: [200, 90, 30]

4단계. 한눈에 견주기

Python
fig, axes = plt.subplots(1, 4, figsize=(13, 3.4), gridspec_kw={"width_ratios": [320, 320, new_w, 320]})
panels = [("원본", img), ("30도, 원본 크기 (잘림)", rot_cut),
          (f"30도, {new_w}×{new_h} (안 잘림)", rot_full), ("세 점으로 기울이기", sheared)]
for ax, (title, arr) in zip(axes, panels):
    ax.imshow(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))
    ax.set_title(title)
    ax.axis("off")
plt.tight_layout()
plt.show()
원본, 원본 크기로 30도 돌려 모서리가 잘린 결과, 크기를 키워 안 잘리게 돌린 결과, 세 점으로 기울인 결과

코드 설명

  • cv2.getRotationMatrix2D((160.0, 120.0), 30, 1.0) 은 [[0.866, 0.5, -38.564], [-0.5, 0.866, 96.077]] 을 만듭니다. 원본 크기로 돌리면 네 모서리가 모두 그림 밖(예: 왼쪽 위 (0, 0) → (−38.6, 96.1))으로 나가 잘립니다.
  • 표의 0.866 과 0.5 로 계산한 새 크기는 397×368 입니다. 셋째 칸에 new_w / 2 - 160, new_h / 2 - 120 을 더해 그림을 새 한가운데로 옮기면 네 모서리가 모두 안에 들어옵니다. 왼쪽 위의 −0.1 은 반올림에서 생긴 반 칸 안쪽의 차이라 안으로 셌습니다.
  • cv2.getAffineTransform 은 세 쌍의 점으로 [[1, -0.375, 45], [0, 1, 0]] 을 만들었습니다. 정하지 않은 넷째 점 (280, 200) 은 (250.0, 200.0) 으로 가고, 그 자리의 색 [200, 90, 30] 이 파란 점입니다. 본문에서 평행사변형 규칙으로 짐작한 자리와 같습니다.
  • 4단계의 gridspec_kw={"width_ratios": ...} 는 칸마다 너비 비율을 정합니다. 그림의 가로 칸 수(320, 397)로 주어, 키운 회전 그림이 다른 그림과 같은 배율로 보이게 했습니다.
← 이전 절14장 · 실습 3 — 뒤집기, 90도 돌리기, 평행 이동다음 절 →14장 · 실습 5 — 원근 변환으로 비스듬한 화이트보드 펴기