이번 장에서는 openCV에서 어떤 타겟을 마치 정면에서 본 듯 만들 수 있는 투영 변환에 대한 글을 작성하겠다

이전 장에서도 이용했던 mouse_events를 이용한다 

import cv2 

class MouseEvents:
    def __init__(self) -> None:
        self.points = []

    def left_button_down(self, x, y, frame):
        self.points.append([x,y])  
        cv2.circle(frame, (x,y),5,(0,0,255),-1)
        cv2.imshow('origin',frame)
        print(x,y)      

    def right_button_down(self,x,y, frame):
        self.points.pop()
        print(x,y)      

    def onMouse(self, event, x, y, flags, img):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.left_button_down(x,y, img)
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.right_button_down(x,y,img)

1. 원근(투영) 변환 - perspective (projection) transform 

  • 원근  
     일반적으로 사람들이 2차원 화면에서 어떤것이 더 멀리 있는지 인식하는 방법은 원근법을 이용한 것이다 가까운 것은 크게, 멀리 있는 것은 작게, 자신의 초점이 있는 곳을 기준으로 한 점으로 모인다 
  • 투영이란 
    도형이나 입체를 다른 평면에 옮기는 일
  • 우리가 진행할 문제는 이미지에서 원하는 부분의 원근감을 없에는 문제이다. 

 

2. opencv perspective transform 

import cv2 
import numpy as np 

from mouse_events import MouseEvents

POINTS_CHECK = False
# 좌표점은 좌상->좌하-> 우상 -> 우하 
# 변경할 좌표를 4x2 행렬로 전환 
# 이동할 좌표를 설정
mouse_events = MouseEvents()
mouse_events2 = MouseEvents() 

path = 'images.jpg'
frame = cv2.imread(path)
height, width  = frame.shape[:2]
cv2.imshow('origin', frame)
if POINTS_CHECK != True:
    copy_frame = frame.copy()
    cv2.setMouseCallback('origin',mouse_events.onMouse, copy_frame)
    cv2.waitKey(0)
    # 원본 이미지에서 원하는 지점의 좌표
    source_coord = np.float32( mouse_events.points )
    # H와 W 는 새로 생성될 이미지(destination_coord)의 크기를 선택한 지점의 상하좌우 최대값으로 넣기 위해 구한다
    H = max(mouse_events.points[2][0],mouse_events.points[3][0]) - min(mouse_events.points[0][0],mouse_events.points[1][0])
    W = max(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] ) - min(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] )
    # 변환 이미지에서 원하는 지점의 좌표 ( (0,0) ~ (W,H)로 변환 )
    destination_coord = np.float32( [ [0,0], [0,H], [W,0], [W,H] ] )
    # 원본 -> 변환 미지수 8개를 구한다
    M = cv2.getPerspectiveTransform(source_coord, destination_coord)
    # 변환 -> 원본 미지수 8개를 구한다
    M2 = cv2.getPerspectiveTransform(destination_coord, source_coord)
	# 원본 -> 변환의 역행렬을 구한다 
    INV_M = np.linalg.pinv(M)
    
    
transformed = cv2.warpPerspective(frame, M, (W,H))
cv2.imshow('transformed', transformed)
  • opecv에서는 간단하게 원근변환을 할 수 있게 해주는 getPerspectiveTransform 함수와 warpPerspective 함수를 제공한다 
    getPerspectiveTransform은 이용을 하자면 미지수를 구하는 단순한 수학 계산이다. 사용하기 위해서는 해당 점이 이동할 위치를 알고 있어야 한다 또한 사용상 원하는 지점의 좌표를 선택할 때 좌상, 좌하, 우상, 우하 순서대로 입력해야 한다.

  • cv2.warpPerspective(frame, M, (W, H)) 
    첫 번째 파라미터로는 사용될 이미지의 데이터가 들어간다 
    두 번째 파라미터 M 은 getPerspectiveTranform을 통해 구한 변환 행렬이 들어간다 ( 아래 행렬식에서 P들이 M이 된다)
    세 번째 파라미터는 결과로 나오는 이미지의 높이, 넓이이다 

다음과 같이 M은 3x3의 행렬이다
미지수는 P11 ~ P32 8개

  • MouseCallback을 이용하여 원하는 영역의 꼭짓점 4개를 선택한 후 아무 키나 눌러 진행한다 

 

3. 역변환

  • 만약 변환된 이미지에서 특정 좌표가 원본 이미지에서 어느 지점인지 알고 싶다면 어떻게 할 수 있을까? 

  • 위 사진과 같이 변환된 이미지에서 파란 선의 좌표가 원본 이미지에서 어느 지점인지 알기 위해서는 이전에 구한 변환에 사용한 행렬의 역행렬을 구하여 변환 이미지에서 원하는 지점의 좌표와 행렬 곱해주면 된다 
if POINTS_CHECK != True:
    cv2.setMouseCallback('transformed',mouse_events2.onMouse, transformed)
    cv2.waitKey(0)
    line_coord = np.float32(mouse_events2.points)
    One = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[0], 1), axis=1))
    Two = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[1], 1), axis=1))
    One = One[:2] / One[2]
    Two = Two[:2] / Two[2]

    POINTS_CHECK = True

cv2.line(transformed, line_coord[0].astype(int), line_coord[1].astype(int), (255,0,0),2,1)
cv2.line(frame, One.astype(int), Two.astype(int), (255,0,0),2,1)

dst2 = cv2.warpPerspective(transformed, M2, (height, width))
cv2.imshow('transform2', dst2)

cv2.imshow('origin', frame)
cv2.imshow('transformed', transformed)
cv2.waitKey(0)
  • 행렬곱은 간단하게 @를 이용하여 구할 수 있다. 
  • 행렬의 길이가 서로 다르니 맞추기 위해 np.append로 값 1을 넣었다 그 후 하나의 차원을 추가해 3x1의 차원을 가지도록 만들었다 ( 3x3 @ 3x1 ) 
  • 위의 결과로 나온 One 은 wx, wy, w 형태이다 ( 위의 공식 참고 ) 그렇기 때문에 원본의 x, y를 구하기 위해서는 w를 구하여 나누어줘야 한다. 
  • M = P11 ~ P 32 
  • 원하는 지점 = x, y를 x, y, 1로 변경 
  • One = (wx`, wy`, w) 

위의 공식을 잘 이해하고 있다면 무리 없이 진행할 수 있다. 복잡한 수학 계산은 컴퓨터가 해주니 공식을 가지고 사용하면 된다! 

 

+ Recent posts