카메라/기하학

카메라의 위치 및 3D 자세정보

dohyeon2 2022. 2. 18. 16:01

2차원 영상 내에서 3차원 거리정보를 얻기 위해서는 

카메라 내부 파라미터 뿐만 아니라 카메라의 위치 및 3D 자세정보가 필요하다. 

 

지난 글에서 카메라 내부 파라미터를 구했으니 

https://dohyeon.tistory.com/23

 

[영상처리] 카메라 캘리브레이션 (Camera Calibration)

2022-02-18 실험실에서 진행하는 프로젝트에서 2D 이미지 내에서 카메라와 물체까지의 3차원 거리를 구하는 task가 주어졌다. 기하학적 계산에 앞서 2차원 이미지의 3차원 변환을 위해서는 camera의 내

dohyeon.tistory.com

이번 글에서 카메라의 위치 및 3D 자세정보를 구해보겠다.


카메라로 촬영한 영상을 보고 이 영상을 획득할 당시의 카메라의 위치 및 3D 자세정보(팬,틸트)를 추출하기 위해서는

먼저 다음과 같은 전제조건이 필요하다.

 

a. 해당 카메라의 내부 파라미터 및 왜곡계수 : fx, fy, cx, cy, k1, k2, p1, p2

b. 물체에 대한 최소 4개 이상의 3D 월드좌표+ 이에 대응되는 2D 영상좌표

 

a. 는 이전 글에서 구했으니 넘어가고,

b. 를 각각 구해줘야 하는데 말로만 하면 이해가 가지 않으니 그림으로 예를 들겠다. 

 

1. 물체의 3D 월드좌표 + 이에 대응되는 2D 영상좌표 구하기

A4 용지에 10cm x 10cm 크기의 정사각형을 그리고 프린트하였다. 

위 그림과 같이 각각의 사각형 꼭지점들에 대한 3D 월드좌표를 임의로 정의해준 뒤, 해당점에 매칭되는 사각형 꼭지점들의 2D 영상좌표쌍을 구해준다. 구해준 네쌍의 점의 좌표를 solvePnP 함수에 인자로 넣어주면 카메라의 위치 및 자세정보가 나온다.

 

위 그림에서는

사각형의 3D 월드좌표 = (0,0,0), (10,0,0), (0,10,0), (10,10,0)

사각형의 2D 영상좌표 = ?

 

사각형의 2D 영상좌표는 어떻게 구할 수 있을까? 

openCV 마우스클릭 이벤트를 이용하면 구할 수 있다.

import cv2
import numpy as np  

# 마우스 이벤트 콜백함수 정의
def mouse_callback(event, x, y, flags, param): 
    print("마우스 이벤트 발생, x:", x ," y:", y) # 마우스 위치 출력

img = cv2.imread("이미지 절대경로")

cv2.namedWindow('image')  #마우스 이벤트 영역 윈도우 생성
cv2.setMouseCallback('image', mouse_callback)

while(True):

    cv2.imshow('image', img)

    k = cv.waitKey(1) & 0xFF
    if k == 27:    # ESC 키 눌러졌을 경우 종료
        print("ESC 키 눌러짐")
        break
cv2.destroyAllWindows()

위 코드를 이용하면

사각형의 2D 영상좌표 = (236,420), (423,428), (244,239), (428,246)

를 구할 수 있다. 

 

결과적으로

사각형의 2D 영상좌표 = (236,420), (423,428), (244,239), (428,246)

사각형의 3D 월드좌표 = (0,0,0), (10,0,0), (0,10,0), (10,10,0)

를 구하였다. 

 

 

따라서 사각형 네 꼭지점의

2차원 영상좌표와 3차원 월드좌표를 코드로 나타내면 아래와 같다.

#2차원 영상좌표
points_2D = np.array([
                        (966, 543),  #좌 하단 
                        (1155, 554),  #우 하단
                        (975, 363),  #좌 상단
                        (1159, 369),  #우 상단
                      ], dtype="double")
                      
#3차원 월드좌표
points_3D = np.array([
                      (0.0, 0.0, 0.0),       #좌 하단
                      (10, 0.0, 0.0),        #우 하단
                      (0.0, 10, 0.0),        #좌 상단
                      (10, 10, 0.0)          #우 상단
                     ], dtype="double")

 

solvePnP 함수의 인자를 살펴보면 

2. cameraMatrix 인자 정의하기

cameraMatrix (카메라 내부 파라미터) 는 행렬 형태로 나타내줘야 하기 때문에 

numpy 라이브러리를 이용하면 다음과 같다.

# camera 내부 파라미터 
cameraMatrix = np.array([[1065.352, 0, 960.127], [0, 1064.480, 569.483], [0, 0, 1]])

3. distCoeffs 인자 정의하기

distCoeffs 인자는 왜곡을 무시하고 진행할것이기 때문에 Null 값을 넣어주면 된다.

#distcoeffs는 카메라의 왜곡을 무시하기 때문에 null값 전달
dist_coeffs = np.zeros((4,1))

 

4. <최종 코드>

import cv2
import numpy as np

img = cv2.imread("C:/Users/MSDL-DESK-02/Desktop/img.png")
size = img.shape

#2차원 영상좌표
points_2D = np.array([
                        (966, 543),  #좌 하단 
                        (1155, 554),  #우 하단
                        (975, 363),  #좌 상단
                        (1159, 369),  #우 상단
                      ], dtype="double")
                      
#3차원 월드좌표
points_3D = np.array([
                      (0.0, 0.0, 0.0),       #좌 하단
                      (10, 0.0, 0.0),        #우 하단
                      (0.0, 10, 0.0),        #좌 상단
                      (10, 10, 0.0)          #우 상단
                     ], dtype="double")


# camera 내부 파라미터 
cameraMatrix = np.array([[1065.352, 0, 960.127], [0, 1064.480, 569.483], [0, 0, 1]])

#distcoeffs는 카메라의 왜곡을 무시하기 때문에 null값 전달
dist_coeffs = np.zeros((4,1))

#solvePnp 함수적용
retval, rvec, tvec = cv2.solvePnP(points_3D, points_2D, cameraMatrix, dist_coeffs, rvec=None, tvec=None, useExtrinsicGuess=None, flags=None)

R = cv2.Rodrigues(rvec)
t= tvec

print(R)
print("\n")
print(t

solvePnp 함수는 기본적으로 3D 월드좌표를 3D 카메라 좌표로 변환시키는 변환정보 (retval, rvec, tvec)를 반환하며 이로부터 회전변환 R과 평행이동 T를 아래와 같이 얻을 수 있다. 

 

rvec = 회전 벡터

tvec = 평행이동 벡터

 

R = cv2.Rodrigues(rvec)

T = tvec

 

openCV의 solvePnP 함수가 반환하는 rvec은 회전변환에 대한 Rodrigues 표현이기 때문에

실제 회전변환 행렬 R을 구하기 위해서는 openCV의 Rodrigues() 함수를 취해주어야 한다.

 

결과적으로 

R = (array([[ 0.99833387,  0.05028667, -0.02829721],
       [ 0.0462584 , -0.99063762, -0.12844172],
       [-0.03449118,  0.12691873, -0.99131327]])

 

t = [[ 0.29921233]
 [-1.35458807]
 [56.65101544]]