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

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

실습 5 — 원근 변환으로 비스듬한 화이트보드 펴기

비스듬히 찍힌 가상의 화이트보드 그림(ch14_board_photo.png)을 바로 앞에서 본 것처럼 폅니다. 네 귀퉁이 좌표는 그림을 만들 때 정한 값이고, 눈금으로 읽어 확인합니다. 점의 순서를 틀리면 어떻게 되는지도 보고, 편 그림을 out_ch14_board_flat.png 로 저장합니다.

1단계. 네 귀퉁이 확인하기

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_board_photo.png")
print("그림 shape:", img.shape)

# 화이트보드의 네 귀퉁이 (x, y): 왼쪽 위부터 시계 방향
src = np.float32([[62, 40], [268, 22], [296, 214], [34, 198]])

# 귀퉁이에 점과 번호를 그려 눈금과 함께 띄운다. 그림 함수는 (x, y) 순서(2차시)
marked = img.copy()
for k, (x, y) in enumerate(src.astype(int), start=1):
    cv2.circle(marked, (int(x), int(y)), 5, (40, 40, 220), -1)
    cv2.putText(marked, str(k), (int(x) + 6, int(y) + 16), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (40, 40, 220), 2)

plt.figure(figsize=(6, 4.5))
plt.imshow(cv2.cvtColor(marked, cv2.COLOR_BGR2RGB))
plt.title("네 귀퉁이: 1 왼쪽 위부터 시계 방향")
plt.show()
출력
그림 shape: (240, 320, 3)
비스듬한 화이트보드의 네 귀퉁이에 1부터 4까지 번호를 붙여 눈금과 함께 띄운 그림

2단계. 변의 길이로 결과 크기 짐작하기

Python
def length(p, q):
    """두 점 사이의 거리: 피타고라스로 가로 차이와 세로 차이에서 구한다(10차시)."""
    return float(np.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2))

tl, tr, br, bl = src
print(f"윗변 {length(tl, tr):.1f}, 아랫변 {length(bl, br):.1f}")
print(f"왼변 {length(tl, bl):.1f}, 오른변 {length(tr, br):.1f}")

# 실제 화이트보드는 가로:세로 = 3:2 라고 알고 있다. 그 비율로 240×160 에 편다
W, H = 240, 160
print(f"결과 크기: {W}×{H}, 가로÷세로 = {W / H}")
출력
윗변 206.8, 아랫변 262.5
왼변 160.5, 오른변 194.0
결과 크기: 240×160, 가로÷세로 = 1.5

3단계. 3×3 표를 만들어 펴기

Python
# 펼 직사각형의 네 귀퉁이: 원본과 같은 순서(왼쪽 위부터 시계 방향)
dst = np.float32([[0, 0], [W - 1, 0], [W - 1, H - 1], [0, H - 1]])

# getPerspectiveTransform(원본 네 점, 옮길 네 점) → 3×3 표
P = cv2.getPerspectiveTransform(src, dst)
print("3×3 표:")
print(P.round(4))

# cv2.perspectiveTransform: 점 목록을 3×3 표로 옮긴다. 모양은 (점 수, 1, 2) 로 준다
moved = cv2.perspectiveTransform(src.reshape(-1, 1, 2), P).reshape(-1, 2)
print("네 귀퉁이가 간 곳:", [[round(float(v), 1) for v in pt] for pt in moved])

# warpPerspective(그림, 3×3 표, 결과 크기 (가로, 세로))
flat = cv2.warpPerspective(img, P, (W, H))
print("편 그림 shape:", flat.shape)
출력
3×3 표:
[[ 1.626300e+00  2.882000e-01 -1.123614e+02]
 [ 1.288000e-01  1.473600e+00 -6.692810e+01]
 [ 1.300000e-03  2.000000e-03  1.000000e+00]]
네 귀퉁이가 간 곳: [[0.0, 0.0], [239.0, 0.0], [239.0, 159.0], [0.0, 159.0]]
편 그림 shape: (160, 240, 3)

4단계. 순서를 틀리면 어떻게 되나, 그리고 저장

Python
# 일부러 오른쪽 위(2)와 왼쪽 아래(4)를 바꿔 적는다
wrong_src = np.float32([src[0], src[3], src[2], src[1]])
wrong = cv2.warpPerspective(img, cv2.getPerspectiveTransform(wrong_src, dst), (W, H))

fig, axes = plt.subplots(1, 3, figsize=(12, 3.4), gridspec_kw={"width_ratios": [320, 240, 240]})
for ax, (title, arr) in zip(axes, [("비스듬한 원본", img), ("바르게 편 결과", flat), ("순서를 틀린 결과", wrong)]):
    ax.imshow(cv2.cvtColor(arr, cv2.COLOR_BGR2RGB))
    ax.set_title(title)
    ax.axis("off")
plt.tight_layout()
plt.show()

print("저장:", cv2.imwrite("out_ch14_board_flat.png", flat))
출력
저장: True
비스듬한 화이트보드 원본, 바르게 편 결과, 점 순서를 틀려 뒤집혀 꼬인 결과

코드 설명

  • 네 귀퉁이를 왼쪽 위부터 시계 방향으로 적고, 그림 위에 번호를 찍어 눈금과 견주며 확인했습니다. 실제 사진에서는 이렇게 눈금으로 읽거나 12장의 윤곽선으로 찾습니다.
  • 변의 길이는 윗변 206.8, 아랫변 262.5, 왼변 160.5, 오른변 194.0 으로 서로 다릅니다. 가까운 쪽(아래, 오른쪽)이 더 길게 찍혔기 때문입니다. 그래서 길이로는 비율을 정확히 알 수 없고, 실제 비율 3:2 를 알고 있어 240×160 으로 정했습니다.
  • cv2.getPerspectiveTransform 은 3×3 표를 만들고, cv2.perspectiveTransform 으로 네 귀퉁이를 옮겨 보면 (0, 0), (239, 0), (239, 159), (0, 159) 로 정확히 갑니다. cv2.warpPerspective 의 결과는 (160, 240, 3) 입니다.
  • 오른쪽 위와 왼쪽 아래를 바꿔 적으면 그림이 대각선으로 접힌 듯 뒤집혀, 글자가 옆으로 누운 거울 글씨가 됩니다. 오류가 나지 않으므로 결과를 눈으로 확인하는 습관이 필요합니다.
← 이전 절14장 · 실습 4 — 회전과 세 점 아핀 변환다음 절 →14장 · 프로젝트 1 — 비스듬히 찍힌 문서 바로 펴고 크기 맞추기