이 글은 [3]의 번역본입니다.
발행일: 2014년 8월 25일
pyimagesearch 독자들로부터 많은 이메일을 받았는데, 거기서 나온 질문 덕에 내가 매일매일 사용하는 라이브러리나 패키지를 충분히 설명하고 있지 않다는 것을 알게 되었다.
그래서 오늘의 포스팅은 cv2.getPerspectiveTransform에 대해서 다루려고 한다.
오늘 다룰 내용은 여러분의 프로젝트에 용이하게 사용될 것 같다.
※ 이 예제의 환경은 Python2.7/Python3.4+, OpenCV 2.4X/OpenCV3.0+이다. 버전 잘 확인하길 바란다.
2.1. transform.py
이전 포스팅에서 4개의 참고점(reference points)를 안다면 마치 "새가 위에서 아래를 보는 듯한" (birds eye view) 시점으로 이미지를 변형할 수 있다고 했다. 이번 포스팅은 내 개인적인 코드로 그 기능을 구현한 것을 다루겠다. 아래의 코드 (transform.py)를 보자.
# 필요한 패키지를 임포트
import numpy as np
import cv2
def order_points(pts):
# 좌표의 초기화. 순서는 좌측상단, 우측상단, 우측하단,
# 좌측하단으로 정렬하자.
rect = np.zeros((4, 2), dtype = "float32")
# 좌측상단 좌표의 합이 가장 작을 것이고,
# 우측하단 좌표의 합이 가장 클 것이다.
s = pts.sum(axis = 1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 좌표들 간의 차이를 구해보자.
# 우측상단 좌표의 차가 가장 작을 것이고,
# 좌측하단 좌표의 차가 가장 클 것이다.
diff = np.diff(pts, axis = 1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
# 정렬된 좌표를 반환한다.
return rect
order_points() 함수는 오직 한 개의 인자(pts)만 받는데, pts는 4개의 (x, y) 좌표에 대한 리스트이다. 이 때 주의할 점은, 4개의 점은 굳이 좌측상단으로부터 시작할 필요는 없지만, 반드시 일정한 순서로 정렬되어야 한다. 좌측상단의 좌표와 우측하단의 좌표는 x 와 y의 합을 통해 구할 수 있고, 우측상단과 좌측하단의 좌표는 x 와 y의 차를 통해 구할 수 있다. (바로 이해가 되지 않는다면 아래의 보충사진을 참고하자) 최종적으로 잘 정렬된 좌표를 반환한다.
이어서 다음 함수를 보도록 하자.
def four_point_transform(image, pts):
# 일정하게 정렬된 좌표들을 가져와서
# 각각 개별적으로 unpack해준다.
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 새로운 이미지의 너비를 계산하자.
# 새로운 너비 = max(dist(우측하단 x 좌표, 좌측하단 x 좌표))
# = max(dist(우측상단 x 좌표, 좌측상단 x 좌표))
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
# 새로운 이미지의 높이를 계산하자.
# 새로운 높이 = max(dist(우측상단 y 좌표, 우측하단 y 좌표))
# = max(dist(좌측상단 y 좌표, 좌측하단 y 좌표))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# 새로운 이미지에 대한 정보를 통해
# 목표 좌표를 구성해보자.
# 좌표의 정렬 순서는 동일하게 좌측상단으로부터
# 시계방향 순서이다.
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype = "float32")
# 시점 변환 행렬을 계산하고 적용하자
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
# 변형된 이미지를 반환하자
return warped
이 함수는 image와 pts라는 2개의 인자를 요구한다.
- image 변수는 우리가 시점 변형을 적용하고 싶은 이미지이고,
- pts 리스트는 우리가 시점 변형을 하고 싶은 관심영역의 4개 좌표이다.
먼저 우리는 order_points 함수를 호출하여 pts를 일정한 순서로 정렬해준 다음, 편의를 위해 좌표를 unpack한다.
이후 새로운 이미지의 너비와 높이를 계산한다. (거리 계산은 중학교 때 배운 두 좌표 간의 거리 계산을 이용하자)
이어서, 좌측상단으로부터 시계방향 순서대로 좌표가 정렬된 목표 좌표를 설정하고,
cv2.getPerspectiveTransform 함수를 이용하여 시점 변형을 한다.
이 때 요구되는 인자는 두 개인데, 여기서 rect는 source 좌표이고, dst는 target 좌표들이다. 이 함수가 반환하는 M은 소스에서 타켓으로 가기 위한 변환 행렬(matrix)이다.
이제 이 변환 행렬 및 새로운 너비 및 높이를 이용하여 원래 이미지에 cv2.warpPerspective 함수를 적용한다. 최종적으로 four_point_transform 함수는 시점 변형된 이미지(warped)를 반환한다.
2.2. transform_example.py
위와 같이 정의한 함수를 활용하여 실제 이미지에 적용해보자.
다음은 transform_example.py에 대한 코드이다.
# 필요한 패키지를 임포트한다.
from pyimagesearch.transform import four_point_transform
import numpy as np
import argparse
import cv2
# 아규먼트 파싱을 한다
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", help = "path to the image file")
ap.add_argument("-c", "--coords",
help = "comma seperated list of source points")
args = vars(ap.parse_args())
# 이미지를 로드하고 source 좌표들을 가져오자 ((x,y) 리스트).
# 주 의: eval 함수를 사용하는 것은 별로 좋지 않지만
# 이번 예제에는 그냥 쓰도록 하자.
image = cv2.imread(args["image"])
pts = np.array(eval(args["coords"]), dtype = "float32")
# 4개 꼭짓점 시점 변형을 적용하자.
warped = four_point_transform(image, pts)
# 원본 이미지와 변형된 이미지를 보여주자.
cv2.imshow("Original", image)
cv2.imshow("Warped", warped)
cv2.waitKey(0)
가장 먼저 해야할 것은 four_point_transform 함수와 각종 필요한 패키지를 임포트하는 것이다. (경로를 잘 지정하자)
우리는 커맨드라인 아규먼트를 2가지 사용할 것이다.
--image: 우리가 시점 변형을 적용하고 싶은 이미지
--coords: 우리가 변형하고자 하는 이미지의 영역에 대한 4개 좌표 리스트
그리고 나서 우리는 이미지를 로드하고 좌표들을 넘파이 배열로 변환해준다. (예제를 위해 eval 함수를 사용하긴 했지만, 나는 이런 방식의 시점 변형을 권하진 않는다. 또한 4개의 꼭짓점을 수작업 없이 자동으로 찍어주는 방식은 이 포스팅에서 다룬다.)
다음으로, 우리는 시점 변환을 적용할 수 있다.
최종적으로, 원본 이미지와 변형된 이미지를 시각화한다.
2.3. 커맨드라인에서 실행하기
cmd창을 열고 다음과 같은 명령을 실행하자.
$ python transform_example.py --image images/example_01.png --coords
"[(73, 239), (356, 117), (475, 265), (187, 443)]"
그러면 아래 그림과 비슷한 결과를 볼 수 있을 것이다.
3. 마무리
우리는 성공적으로 시점 변환을 해보았다.
어떤 경우에는 변형된 이미지가 너무 꼬여진 느낌이 드는 경우가 있는데, 이는 원래 이미지의 각도가 심각하기 때문이다. 우리가 90도로 내려보는 것과 비슷할 수록, 변환된 이미지의 결과물은 더 좋을 것이다.
Reference
[1] cv2.getPerspectiveTransform() | TheAILearner
[2] OpenCV 파이썬, 네 점 투영 변환 (getPerspective) : 네이버 블로그 (naver.com)
[3] 4 Point OpenCV getPerspective Transform Example - PyImageSearch
YOLOR (YOLO Representation) (0) | 2021.07.07 |
---|---|
word image generator (0) | 2021.07.04 |
KakaoBrain Pororo OCR (0) | 2021.06.23 |
Custom Text Image Generator (0) | 2021.06.22 |
PIL rotate (0) | 2021.06.22 |