소스 뷰어

원근 투시 변환(perspective projection transformation)

  • 원근 투시 변환은 원근법을 영상 좌표계에서 표현한 것으로 3차원의 실세계의 좌표 P를 투영 스크린상의 2차원 좌표로 표현할 수 있도록 변환하는 것을 말한다.
  • 영상처리에서 원근 변환은 주로 2차원 영상을 다른 2차원 영상으로 변환할 때에 사용한다. 예를 들어 카메라에서 입력받은 영상에서 카메라 렌즈에 의한 왜곡을 보정할 수 있다. 그리고 원근갑이 잘 표현된 2차원 영상을 변환하여 3차원 공간상의 거리를 측정하고자 할 때 사용될 수 있다
  • 원근 투영 변환을 사용할 때에는 동차 좌표계(homogenous coordinates)를 사용하는 것이 편리하다. 동차 좌표계는 모든 항의 차수가 동일하기 때문에 붙여진 이름으로서 n차원의 투영 공간을 n+1개의 좌표로 나타내는 좌표계이다. 좀 더 쉽게 말하면, 직교 좌표인 (x, y)를 (x,y,1)로 표현하는 것이다. 이것을 일반화해서 표현하면, 0이 아닌 상수 W에 대해(x,y)를 (wx, wx, w)로 표현한다. 이렇게 되면 상수 w가 무한히 많기 때문에 (x,y)에 대한 동차 좌표 표현은 무한히 많이 존재하게 된다.
  • 거꾸로 동차 좌표계에서 한전(wx, wy, w)을 직교 좍표로 나타내면 각 원소를 w로 나누어서 (x/w, y/w)가 된다. 예를 들어 동차 좌표게에서 한 점 (5, 7, 5)은 직교 좌표에서 (5/5. 7/5) 즉, (1, 1.4)가 된다. 3차원 좌표에서도 같은 방법으로 적용된다.

원근 변환과 관련되 OpenCV 함수

  • cv2.getPerspectiveTransform(src,dst[, borderMode]) :4개의 좌표쌍을 입력하면 원근 변환 행렬을 반환한다
    • src : 입력 영상 4개 좌표
    • dst : 목적 영상 4개 좌표
    • borderMode : 경계 지점 방법
  • cv2.warpPerspective(src, M, dsize[, dst[, flags[, boardMode[, borderValue]]]]) : 영상에 원근 변환을 적용
    • src : 입력 영상
    • M :원근 변환 행렬
    • dsize : 결과 영상의 크기
    • dst : 결과 영상
    • flags : 보간 방법
    • borderMode : 경계 지점 방법
    • borderValue : 상수 경계일 때, 경계 화소값
  • cv2.transform(src, M) -> dst :4개의 좌표쌍을 입력하면 원근 변환 행렬을 반환한다
    • src : 입력 좌표 행렬
    • M :원근 변환 행렬
    • dst : 결과 좌표 행렬
import numpy as np, cv2
image = cv2.imread('img/perspective.jpg', cv2.IMREAD_COLOR)
if image is None: raise Exception("영상 파일을 읽기 에러")
pts1 = np.float32([(80, 40),  (315, 133), (75, 300), (335, 300)] )
pts2 = np.float32([(50, 60),  (340, 60), (50, 320), (340, 320)])
perspect_mat = cv2.getPerspectiveTransform(pts1, pts2) #.astype('float32')
dst = cv2.warpPerspective(image, perspect_mat, image.shape[1::-1], cv2.INTER_CUBIC)
print("[perspect_mat] = \n%s\n" % perspect_mat )
[perspect_mat] = 
[[ 6.25789284e-01  3.98298577e-02 -6.88839366e+00]
 [-5.02676539e-01  1.06358288e+00  5.13923399e+01]
 [-1.57086418e-03  5.25700042e-04  1.00000000e+00]]

## 변환 좌표 계산 - 행렬 내적 이용 방법
ones = np.ones((4,1), np.float64)
pts3 = np.append(pts1, ones, axis=1)              # 원본 좌표 -> 동차 좌표 저장
pts4 = cv2.gemm(pts3, perspect_mat.T, 1, None, 1)  # 행렬 곱으로 좌표 변환값 계산
## 변환 좌표 계산 - cv2.transform() 함수 이용방법
# pts3 = np.expand_dims(pts1, axis=0)             # 차원 증가
# pts4 = cv2.transform(pts3, perspect_mat)
# pts4 = np.squeeze(pts4, axis=0)                 # 차원 감소
# pts3 = np.squeeze(pts3, axis=0)                 # 출력 위해
print(" 원본 영상 좌표 \t 목적 영상 좌표 \t\t 동차 좌표 \t\t 변환 결과 좌표")
for i in range(len(pts4)):
    pts4[i] /= pts4[i][2]
    print("%i : %-14s %-14s %-18s%-18s" % (i, pts1[i], pts2[i], pts3[i], pts4[i]))
    cv2.circle(image, tuple(pts1[i].astype(int)), 4, (0, 255, 0), -1) # 원본 영상에 pts1 표시
    cv2.circle(dst  , tuple(pts2[i].astype(int)), 4, (0, 255, 0), -1) # 목적 영상에 pts2 표시
 원본 영상 좌표 	 목적 영상 좌표 		 동차 좌표 		 변환 결과 좌표
0 : [80. 40.]      [50. 60.]      [80. 40.  1.]     [50. 60.  1.]     
1 : [315. 133.]    [340.  60.]    [315. 133.   1.]  [340.  60.   1.]  
2 : [ 75. 300.]    [ 50. 320.]    [ 75. 300.   1.]  [ 50. 320.   1.]  
3 : [335. 300.]    [340. 320.]    [335. 300.   1.]  [340. 320.   1.]