カメラの位置・姿勢推定2 PNP問題 実験編1

下記のエントリで透視投影モデル,およびその計算式を導入し,
daily-tech.hatenablog.com

下記のエントリでPNP問題とその解法を導入しました.
daily-tech.hatenablog.com

今回のエントリとその次のエントリで実際に PNP 問題を解いてみたいと思います.両方のエントリで同じ問題「1.世界座標が既知の7点を,位置が既知のカメラに投影した時の画像座標を求める.」と「2.世界座標,画像座標が既知の点7点をつかってカメラの位置を求める」を扱いますが,今回のエントリでは OpenCV の関数を用いて,次回のエントリでは自作のプログラムで問題を解きます.

0.問題のイメージ図

f:id:rkoichi2001:20180321234144j:plain
※以前アップしていた絵だとすべての点が同一平面上にあるので,自作のPNP関数ではうまくいきませんでした.OpenCVのsolvePNPでは,同一平面上にあっても解は求まりましたが.

1.世界座標が既知の7点を,位置が既知のカメラに投影した時の画像座標を求める.

ここでは,投影画像座標を求めます.問題を簡単にするために,画像にレンズひずみはないものとし,内部パラメータは下記のように定義します.
画像幅 :640pix
画像高さ:480pix
主点x  :320
主点y  :240
焦点距離:160pix
カメラ高さ:1m
カメラ角度:俯角30度

ここでは,投影点を求めるために,OpenCV の projectPoint 関数を使います.下記, Pythonスクリプトです.
ここで注意が必要なのは,R, t は「カメラ座標から見たワールド座標の位置」でなければいけないことと,オイラー角表現を用いているので回転行列の掛け算が Rx * Ry * Rz になってます.

import numpy as np
import cv2
import math

world = np.array(\
[\
( 5.00,  0.00,  0.00), \
( 5.00,  0.00,  0.50), \
( 6.00, -1.00,  0.00), \
( 6.00, -1.00,  0.50), \
( 6.00,  1.00,  0.00), \
( 6.00,  1.00,  0.50), \
( 7.00,  0.00,  0.50), \
])

# Camera Extrinsic Parameter
xRadEuler_C2W = -120 / 180 * math.pi
yRadEuler_C2W = 0 / 180 * math.pi
zRadEuler_C2W = -90 / 180 * math.pi

Rx = np.matrix([[1, 0, 0], [0, math.cos(xRadEuler_C2W), -math.sin(xRadEuler_C2W)], [0, math.sin(xRadEuler_C2W),  math.cos(xRadEuler_C2W)]])
Ry = np.matrix([[ math.cos(yRadEuler_C2W), 0, math.sin(yRadEuler_C2W)], [0, 1, 0], [-math.sin(yRadEuler_C2W), 0, math.cos(yRadEuler_C2W)]])
Rz = np.matrix([[ math.cos(zRadEuler_C2W), -math.sin(zRadEuler_C2W), 0], [ math.sin(zRadEuler_C2W),  math.cos(zRadEuler_C2W), 0], [0, 0, 1]])

# Notice : Rotation Matrix from Euler Angle.
R = Rx * Ry * Rz

# tvec is expressed wrt camra coord.
tvec = R * np.matrix((0, 0, -1)).T

# Camera Intrinsic Paramter
dist_coeffs = np.zeros((5, 1))
width = 640
height = 480
focal_length = 160
center = (width / 2, height / 2)

camera_matrix = np.array([[focal_length, 0, center[0]], 
                                           [0, focal_length, center[1]],
                                           [0, 0, 1]], dtype = "double")

if __name__ == "__main__":
  print("Calculate Projected Point")

  print("\nExtrinsic Paramter")
  print("Translation")
  print(tvec)
  
  print("Rotation")
  print(R)
  

  print("\nCamera Matrix : ")
  print(camera_matrix)

  print("\nDistortion Coefficient")
  print(dist_coeffs)
  
  print("\nGenerate Rotation Vector from Rotation Matrix")
  rvec, jacob = cv2.Rodrigues(R)
  print(rvec)
  
  print("\nProject Point on Screen")
  result = cv2.projectPoints(world, rvec, tvec, camera_matrix, None)
  
  for n in range(len(world)):
    print(world[n], '==>', result[0][n])

上記スクリプトを実行すると,下記の結果(画像座標での7点の投影位置)が得られます.

Project Point on Screen
[ 5.   0.  0.   ] ==> [[ 320.                  294.12609945]]
[ 5.   0.  0.5 ] ==> [[ 320.                  312.2071607]]
[ 6.  -1.  0.   ] ==> [[ 291.91086401  299.94150262]]
[ 6.  -1.  0.5 ] ==> [[ 290.62146125  315.41433581]]
[ 6.   1.  0.   ] ==> [[ 348.08913599  299.94150262]]
[ 6.   1.  0.5 ] ==> [[ 349.37853875  315.41433581]]
[ 7.   0.  0.5 ] ==> [[ 320.                  317.74146755]]

2.世界座標,画像座標が既知の点7点をつかってカメラの位置を求める

今度は逆に,上記の結果を使ってカメラ位置を求めます.

import numpy as np
import cv2
import math

world = np.array(\
[\
( 5.00,  0.00,  0.00), \
( 5.00,  0.00,  0.50), \
( 6.00, -1.00,  0.00), \
( 6.00, -1.00,  0.50), \
( 6.00,  1.00,  0.00), \
( 6.00,  1.00,  0.50), \
( 7.00,  0.00,  0.50), \
])

img_pnts = np.array(\
[\
(320.                , 294.12609945), \
(320.                , 312.2071607), \
(291.91086401, 299.94150262), \
(290.62146125, 315.41433581), \
(348.08913599, 299.94150262), \
(349.37853875, 315.41433581), \
(320.                , 317.74146755), \
])


# Camera Intrinsic Paramter
dist_coeffs = np.zeros((5, 1))
width = 640
height = 480
focal_length = 160
center = (width / 2, height / 2)

camera_matrix = np.array(
						[[focal_length, 0, center[0]], 
						[0, focal_length, center[1]],
						[0, 0, 1]], dtype = "double"
						)


if __name__ == "__main__":
  print("Solve Perspective N Point Problem")

  print("\nCamera Matrix : ")
  print(camera_matrix)

  print("\nDistortion Coefficient")
  print(dist_coeffs)
  
  (success, rot_vec, trans_vec) = cv2.solvePnP(world, img_pnts, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE)
  
  print("\nTranslation Vector")
  print(trans_vec)
  
  print("\nRotation Vector")
  print(rot_vec)
  
  print("\nRotation Matrix")
  R, jacob = cv2.Rodrigues(rot_vec)
  print(R)
  

上記スクリプトを実行すると,下記の結果(画像座標での7点の投影位置)が得られます.もともとのカメラ位置が求まりました.

Translation Vector
[[ -8.87481507e-11]
 [ -8.66025403e-01]
 [  4.99999999e-01]]

Rotation Vector
[[-1.58351453]
 [-1.58351453]
 [-0.91424254]]

Rotation Matrix
[[  1.53020929e-11   1.00000000e+00  -5.93469718e-13]
 [  5.00000000e-01  -7.13717974e-12   8.66025404e-01]
 [  8.66025404e-01  -1.35487732e-11  -5.00000000e-01]]

次のエントリでは,自作のアルゴリズムでPNP問題を解いてみます.それではまた!