openCv page Dewarp 분석 -7

이번 포스트는 굴곡을 펴기 위해서 cv2.solvePnP를 사용하는 것을 알아보겠습니다.

get_default_params

solvePnP 함수는 영상을 획득한 카메라의 위치 및 방향 (camera pzose)을 알아낼 때 유용하게 사용할 수 있습니다.
카메라 내부 파라미터 행렬 K와 3D 월드좌표 - 2D 픽셀좌표 매칭쌍들을 입력으로 주면, 카메라 외부 파라미터인 [R|t] 행렬을 계산해 줍니다. 최소 4개의 좌표쌍을 필요로 합니다.

기본형은 아래와 같습니다.

1
retval, rvec, tvec = solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec=None, tvec=None, useExtrinsicGuess=None, flags=None)

바탕이 되는 이론은 블로그openCV 사이트를 참고 바랍니다.

파라미터 설명은 다음과 같습니다.

  • objectPoints : 객체 좌표 공간의 객체 포인트 배열, 3xN / Nx3 1 채널 또는 1xN / Nx1 3 채널. 여기서 N은 포인트 수입니다. vector <Point3f>도 여기에 전달할 수 있습니다.
    • 월드 좌표계 (World Coordinate System)
      우리가 사물(물체)의 위치를 표현할 때 기준으로 삼는 좌표계입니다.

월드좌표계는 어디 하늘에서 주어져 있는 것이 아니라 문제에 따라서 우리가 임의로 잡아서 사용할 수 있는 좌표계입니다.

예를 들어, 자신의 안방 한쪽 모서리를 원점으로 잡고 한쪽 벽면 방향을 X축, 다른쪽 벽면 방향을 Y축, 하늘을 바라보는 방향을 Z축으로 잡을 수 있습니다. 좌표의 단위(unit)는 미터(meter)로 해도 되고 센티미터(centimeter)로 해도 됩니다.

중요한 점은 좌표계는 일종의 약속(protocol)이기 때문에 (3, 1, -10)이라고 했을 때 이 점이 어떤 위치인지 그 문제 내에서 만큼은 유일하게 결정될 수 있으면 되는 것입니다.

  • imagePoints : 해당 이미지 포인트의 배열, 2xN / Nx2 1 채널 또는 1xN / Nx1 2 채널. 여기서 N은 포인트 수입니다. vector <Point2f>도 여기에 전달할 수 있습니다.

    • 픽셀 좌표계 (Pixel Image Coordinate System)
      편의상 픽셀 좌표계라고 썼지만, 보통은 영상좌표계(Image Coordinate System)라고 불립니다.

픽셀 좌표계는 우리가 실제 눈으로 보는 영상에 대한 좌표계로서 아래 그림과 같이 이미지의 왼쪽상단(left-top) 모서리를 원점, 오른쪽 방향을 x축 증가방향, 아래쪽 방향을 y축 증가방향으로 합니다. 그리고 픽셀 좌표계의 x축, y축에 의해 결정되는 평면을 이미지 평면 (image plane)이라 부릅니다.

기하학적으로 볼 때, 3D 공간상의 한 점 P = (X,Y,Z)는 카메라의 초점 (또는 렌즈의 초점)을 지나서 이미지 평면의 한 점 pimg = (x, y)에 투영(projection) 됩니다. 알다시피 점 P와 점 pimg를 잊는 선(ray) 상에 있는 모든 3D 점들은 모두 pimg로 투영됩니다. 따라서 3D 점 P로부터 pimg는 유일하게 결정할 수 있지만, 반대로 영상 픽셀 pimg로부터 P를 구하는 것은 부가적인 정보 없이는 불가능합니다.

픽셀 좌표계의 단위는 픽셀(pixel)입니다.

좌표계 (Coordinate System)

  • cameraMatrix : Input camera matrix
    • 초점거리(focal length): fx, fy
      흔히 초점거리라 하면 볼록렌즈의 초점을 생각하기 쉬운데, 여기서(카메라 모델) 말하는 초점거리는 렌즈중심과 이미지센서(CCD, CMOS 등)와의 거리를 말합니다.

카메라 모델

디지털 카메라 등에서 초점거리는 mm 단위로 표현되지만 카메라 모델에서 말하는 초점거리(f)는 픽셀(pixel) 단위로 표현됩니다. 즉, f의 단위로 픽셀이라는 의미입니다.

이미지의 픽셀(pixel)은 이미지 센서의 셀(cell)에 대응되기 때문에, 초점거리(f)가 픽셀(pixel) 단위라는 의미는 초점거리가 이미지 센서의 셀(cell) 크기에 대한 상대적인 값으로 표현된다는 의미입니다. 예를 들어, 이미지 센서의 셀(cell)의 크기가 0.1 mm이고 카메라의 초점거리가 f = 500 pixel이라고 하면 이 카메라의 렌즈 중심에서 이미지 센서까지의 거리는 이미지 센서 셀(cell) 크기의 500배 즉, 50 mm라는 의미입니다.

컴퓨터 비전 분야에서 카메라 초점거리를 물리단위(m, cm, mm, …)가 아닌 픽셀단위로 표현하는 이유는 (이미지 픽셀과 동일한 단위로 초점거리를 표현함으로써) 영상에서의 기하학적 해석을 용이하게 하기 위함입니다.

그런데, 카메라 모델에서 초점거리를 하나의 값으로 f라 표현하지 않고 fx, fy로 구분하여 표현하는 경우가 있는데(실제로 카메라 캘리브레이션을 수행하면 fx, fy를 구분하여 반환한다) 이는 이미지 센서의 물리적인 셀 간격이 가로 방향과 세로 방향이 서로 다를 수 있음을 모델링하기 위함입니다. 이 경우 fx는 초점거리(렌즈중심에서 이미지 센서까지의 거리)가 가로 방향 셀 크기(간격)의 몇 배인지를 나타내고 fy는 초점거리가 세로 방향 센서 셀 크기(간격)의 몇 배인지를 나타냅니다. fx와 fy 모두 단위는 픽셀(pixel)이며 현대의 일반적인 카메라는 가로방향 셀 간격과 세로방향 셀 간격의 차이가 없기 때문에 f = fx = fy라 놓아도 무방합니다.

참고로, 동일한 카메라로 캘리브레이션을 수행했을 때, 이미지 해상도를 1/2로 낮추면 캘리브레이션 결과의 초점거리도 1/2로 작아집니다. 실제 물리적 초점거리가 변하는 것은 아니지만 카메라 모델에서의 초점거리는 상대적인 개념이기 때문에 해상도를 바꾸면 한 픽셀(pixel)에 대응하는 물리크기가 변하고 따라서 초점거리도 변하게 됩니다. 예컨데, 이미지 해상도를 1/2로 낮추면 이미지 센서의 2 x 2 셀(cell)들이 합쳐서 하나의 이미지 픽셀이 되기 때문에 한 픽셀에 대응하는 물리크기가 2배가 됩니다. 따라서 초점거리는 1/2이 되어야 합니다.

카메라 모델의 렌즈중심(초점)은 핀홀 카메라 모델(그림 6)에서 핀홀(pinhole)에 해당됩니다. 핀홀 카메라 모델은 모든 빛은 한 점(초점)을 직선으로 통과하여 이미지 평면(센서)에 투영된다는 모델입니다. 이러한 핀홀 모델은 3D 공간과 2D 이미지 평면 사이의 기하학적 투영(projection) 관계를 매우 단순화시켜 줍니다.

초점으로부터 거리가 1(unit distance)인 평면을 normalized image plane이라고 부르며 이 평면상의 좌표를 보통 normalized image coordinate라고 부릅니다. 물론 이것은 실제는 존재하지 않는 가상의(상상의) 이미지 평면입니다. 카메라 좌표계 상의 한 점 (Xc, Yc, Zc)를 영상좌표계로 변환할 때 먼저 Xc, Yc를 Zc(카메라 초점에서의 거리)로 나누는 것은 이 normalized image plane 상의 좌표로 변환하는 것이며, 여기에 다시 초점거리 f를 곱하면 우리가 원하는 이미지 평면에서의 영상좌표(pixel)가 나옵니다 (그림 4 참조). 그런데, 이미지에서 픽셀좌표는 이미지의 중심이 아닌 이미지의 좌상단 모서리를 기준(원점)으로 하기 때문에 실제 최종적인 영상좌표는 여기에 (cx, cy)를 더한 값이 됩니다. 즉, x = fxX/Z+cx, y = fyY/Z+cy.

카메라 투영(projection)모델

  • 주점(principal point): cx, cy
    주점 cx, cy는 카메라 렌즈의 중심 즉, 핀홀에서 이미지 센서에 내린 수선의 발의 영상좌표(단위는 픽셀)로서 일반적으로 말하는 영상 중심점(image center)과는 다른 의미입니다. 예를 들어서, 카메라 조립과정에서 오차로 인해 렌즈와 이미지 센서가 수평이 어긋나면 주점과 영상중심은 다른 값을 가질 것입니다.

영상기하학에서는 단순한 이미지 센터보다는 principal point가 훨씬 중요하며 영상의 모든 기하학적 해석은 이 주점을 이용하여 이루어집니다.

cameraMatrix

  • distCoeffs : 4, 5 또는 8 요소의 왜곡 계수 (k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6]])의 입력 벡터 벡터가 NULL / empty이면, 제로 왜곡 계수가 가정됩니다. (왜곡 보정 관련 링크)
  • rvec : tvec과 함께 모델 좌표계에서 카메라 좌표계로 점을 가져 오는 출력 회전 벡터(Output rotation vector) (Rodrigues() 참조)
    • Rodrigues’ rotation formula
      opencv의 solvePnP 함수에서 반환되는 rvec는 Rodrigues를 컴팩트(compact)하게 표현한 벡터입니다. 먼저, Rodrigues가 무엇인지 살펴본 후 opencv에서 사용하는 Rodrigues 표현법에 대해 살펴보겠습니다.

3차원에서 회전변환은 보통 3 × 3 행렬로 표현됩니다. 그런데, Rodrigues를 사용하면 임의의 3차원 회전변환을 4개의 값(회전축 벡터 + 회전각) 만으로 표현할 수 있습니다. 3차원 공간에서 점 p를 회전축 v에 대하여 θ만큼 회전시킨 값은 다음 식에 의해 계산될 수 있는데, 이 식을 Rodrigues’ rotation formula라고 부릅니다 (Rodrigues는 이 식을 만든 프랑스 수학자의 이름).

Rodrigues' rotation formula

이 때, v는 단위벡터(unit vector)이어야 하고 회전방향은 오른손 법칙을 따릅니다 (엄지를 펴고 오른손을 쥐었을 때 엄지의 방향이 회전축 방향, 쥔 손가락의 방향이 + 회전방향).

위식을 행렬 형태로 표현하면 다음과 같습니다.

행렬 표현

위식으로부터 Rodrigues v = (vx,vy,vz), θ에 대응하는 회전변환 행렬 R이 다음과 같음을 알 수 있습니다.

회전 변환 행렬 R

위식은 우리가 임의의 회전축에 대한 회전을 회전변환 행렬로 표현할 수 있음을 나타냅니다.

반대의 경우로, 임의의 회전변환 행렬에 대한 Rodrigues 표현은 다음 수식을 이용하여 구할 수 있다고 합니다.

임의의 회전축에 대한 회전을 회전변환 행렬로 표현

opencv에서는 회전변환행렬 표현과 Rodrigues 표현 사이의 상호 변환을 위해 Rodrigues()란 함수를 제공합니다. 그런데, opencv에서 사용하는 Rodrigues 표현은 보다 컴팩트(compact)한 형태로서 단 3개의 값만으로 회전변환을 표현합니다.

opencv API 설명문서에 따르면 원래 회전변환은 3 자유도이기 때문에 opencv에서는 회전변환을 rod2 = [a, b, c]의 3차원 벡터로 컴팩트하게 표현하고 이로부터 회전각(θ) 및 회전축 벡터(v)는 다음과 같이 추출합니다.

회전각(θ) 및 회전축 벡터(v)

이와같이 solvePnP 함수에서 반환되는 rvec는 Rodrigues에 대한 3차원의 컴팩트한 표현법이므로 회전각 및 회전축을 알기 위해서는 위식을 이용해야 합니다.

  • tvec – 출력 변환 벡터.(Output translation vector.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def get_default_params(corners, ycoords, xcoords):

# page width and height
page_width = np.linalg.norm(corners[1] - corners[0])
page_height = np.linalg.norm(corners[-1] - corners[0])
rough_dims = (page_width, page_height)

# our initial guess for the cubic has no slope
cubic_slopes = [0.0, 0.0]

# object points of flat page in 3D coordinates
corners_object3d = np.array([
[0, 0, 0],
[page_width, 0, 0],
[page_width, page_height, 0],
[0, page_height, 0]])

# estimate rotation and translation from four 2D-to-3D point
# correspondences
_, rvec, tvec = cv2.solvePnP(corners_object3d,
corners, K, np.zeros(5))

span_counts = [len(xc) for xc in xcoords]

params = np.hstack((np.array(rvec).flatten(),
np.array(tvec).flatten(),
np.array(cubic_slopes).flatten(),
ycoords.flatten()) +
tuple(xcoords))

return rough_dims, span_counts, params

np.hstack은 두 배열을 왼쪽에서 오른쪽으로 붙이는 함수 입니다. 아래 예를 보면 이해가 쉽습니다.

1
2
3
4
5
6
7
8
9
In [1]: import numpy as np

In [2]: a = np.array([1, 2, 3])

In [3]: b = np.array([4, 5, 6])

In [7]: np.hstack([a, b])

Out[7]: array([1, 2, 3, 4, 5, 6])

결론적으로 cv2.solvePnP에서 취득한 값들을 배열화해 params라는 배열로 리턴합니다.

optimize_params

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def project_xy(xy_coords, pvec):

# get cubic polynomial coefficients given
#
# f(0) = 0, f'(0) = alpha
# f(1) = 0, f'(1) = beta

alpha, beta = tuple(pvec[CUBIC_IDX])

poly = np.array([
alpha + beta,
-2*alpha - beta,
alpha,
0])

xy_coords = xy_coords.reshape((-1, 2))
z_coords = np.polyval(poly, xy_coords[:, 0])

objpoints = np.hstack((xy_coords, z_coords.reshape((-1, 1))))

image_points, _ = cv2.projectPoints(objpoints,
pvec[RVEC_IDX],
pvec[TVEC_IDX],
K, np.zeros(5))

return image_points

np.polyval함수는 스칼라 값에서 다항식의 해를 구합니다. 기본형은 polyval(p, x)입니다.

  • 첫번째 파라미터 p는 다항식을 의미합니다.
  • 두번째 파라미터 x는 해를 뜻합니다.

예를 들어 np.polyval([1, -2, 1], 2)가 있다면 [1, -2, 1]은 x²-2x+1을 뜻하고 여기에 2를 대입한 결과 1을 리턴합니다.
np.polyval([1, -12, 10, 7, -10], 3)가 있다면 [1, -12, 10, 7, -10] 은 x⁴-12x³+10x²+7x-10을 뜻하고 여기에 3을 대입한 결과 -142를 리턴합니다.

cv2.projectPoints는 3차원 공간상의 객체의 Pose(rorcpdml dnlcldhk qkdgid)정보를 2차원 화면상의 이미지의 Pose 정보로 변경해 줍니다. 위에서 설명한 월드 좌표계(3D)의 값을 이미지 좌표계(2D)로 변환합니다.

기본형은 아래와 같습니다.

1
imagePoints, jacobian = cv2.projectPoints(objectPoints, rvec, tvec, cameraMatrix, distCoeffs[, imagePoints[, jacobian[, aspectRatio]]])

파라미터의 설명은 cv2.solvePnP에서 설명한 파라미터와 같습니다.

최적화 문제는 함수 f의 값을 최대화 혹은 최소화하는 변수 x의 값 x∗를 찾는 것입니다. 이 값 x∗ 를 최적화 문제의 해(solution)라고 합니다. 만약 최소화 문제를 풀 수 있다면 f(x) 를 −f(x) 로 바꾸어 위아래를 뒤집은 다음 최소화 문제를 풀면 f(x) 의 최대화 문제를 푼 것과 같습니다. 따라서 보통은 최소화 문제만 고려합니다.

최적화에 대한 설명 리스트입니다.

최적화 전 이미지

최적화 후 이미지

최종 output

공유하기