-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcuboid_PNP_solver.py
188 lines (146 loc) · 6.78 KB
/
cuboid_PNP_solver.py
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 18 13:35:57 2019
@author: matusmacbookpro
"""
#adapted from: https://github.com/NVlabs/Deep_Object_Pose/blob/master/src/dope/inference/cuboid_pnp_solver.py
import cv2
import numpy as np
from pyrr import Quaternion
class CuboidPNPSolver():
"""
This class is used to find the 6-DoF pose of a cuboid given its projected vertices.
Runs perspective-n-point (PNP) algorithm.
"""
# INIT PARAMETERS:
# camera_intrinsic_matrix: size (3,3)
# cuboid3d : default dimensions of object cuboid in 3d (canonical locations of every vertex + centroid), size (9,3)
# default cuboid3d for glue gun:
# array([[-0.265 , -0.135 , -0.06 ],
# [-0.265 , -0.135 , 0.06 ],
# [-0.265 , 0.18 , 0.06 ],
# [-0.265 , 0.18 , -0.06 ],
# [ 0. , -0.135 , -0.06 ],
# [ 0. , -0.135 , 0.06 ],
# [ 0. , 0.18 , 0.06 ],
# [ 0. , 0.18 , -0.06 ],
# [-0.1325, 0. , 0. ]])
# dist_coeffs: camera distortion coefficients, size: (4,1), by default set to zeros, should work fine if you dont use camera with ridiculous distortion (i.e. gopro)
# Class variables
cv2version = cv2.__version__.split('.')
cv2majorversion = int(cv2version[0])
def __init__(self, camera_intrinsic_matrix = None, cuboid3d = None, dist_coeffs = np.zeros((4, 1))):
if (not camera_intrinsic_matrix is None):
self._camera_intrinsic_matrix = camera_intrinsic_matrix
else:
self._camera_intrinsic_matrix = np.array([
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
])
self._cuboid3d = cuboid3d
self._dist_coeffs = dist_coeffs
def set_camera_intrinsic_matrix(self, new_intrinsic_matrix):
'''Sets the camera intrinsic matrix'''
self._camera_intrinsic_matrix = new_intrinsic_matrix
def set_dist_coeffs(self, dist_coeffs):
'''Sets the camera intrinsic matrix'''
self._dist_coeffs = dist_coeffs
def solve_pnp(self, cuboid2d_points, pnp_algorithm = None):
"""
Detects the rotation and traslation
of a cuboid object from its vertexes'
2D location in the image
"""
# INPUT:
# cuboid2d_points: size (9,2)
# cuboid2d contains the 2d coordinates of bb vertcies + the 2d coordinates of the bb centroid
# last coordinate is the centroid
# pnp_algorithm: type of pnp algotithm used, if None uses cv2.SOLVEPNP_ITERATIVE
# OUTPUT:
# location: [x,y,z]
# quaternion: [x,y,z,w]
# projected_points (9x2) (DONT USE THIS, sometimes gives incorrect output)
# RTT_matrix (3x4)
# Fallback to default PNP algorithm base on OpenCV version
if pnp_algorithm is None:
if CuboidPNPSolver.cv2majorversion == 2:
pnp_algorithm = cv2.CV_ITERATIVE
elif CuboidPNPSolver.cv2majorversion == 3:
pnp_algorithm = cv2.SOLVEPNP_ITERATIVE
# pnp_algorithm = cv2.SOLVE_PNP_P3P
else:
# pnp_algorithm = cv2.SOLVEPNP_ITERATIVE
# Alternative algorithms:
# pnp_algorithm = SOLVE_PNP_P3P
pnp_algorithm = cv2.SOLVEPNP_EPNP
location = None
quaternion = None
projected_points = cuboid2d_points
cuboid3d_points = np.array(self._cuboid3d)
obj_2d_points = []
obj_3d_points = []
for i in range(9):
check_point_2d = cuboid2d_points[i]
# Ignore invalid points
if (check_point_2d is None):
continue
obj_2d_points.append(check_point_2d)
obj_3d_points.append(cuboid3d_points[i])
obj_2d_points = np.array(obj_2d_points, dtype=np.float64)
obj_3d_points = np.array(obj_3d_points, dtype=np.float64)
valid_point_count = len(obj_2d_points)
# Can only do PNP if we have more than 3 valid points
is_points_valid = valid_point_count >= 4
if is_points_valid:
ret, rvec, tvec = cv2.solvePnP(
obj_3d_points,
obj_2d_points,
self._camera_intrinsic_matrix,
self._dist_coeffs,
flags=pnp_algorithm
)
# ret, rvec, tvec,_ = cv2.solvePnPRansac(
# obj_3d_points,
# obj_2d_points,
# self._camera_intrinsic_matrix,
# self._dist_coeffs,
# reprojectionError=16
# )
if ret:
location = list(x[0] for x in tvec)
quaternion = self.convert_rvec_to_quaternion(rvec)
projected_points, _ = cv2.projectPoints(cuboid3d_points, rvec, tvec, self._camera_intrinsic_matrix, self._dist_coeffs)
projected_points = np.squeeze(projected_points)
# If the location.Z is negative or object is behind the camera then flip both location and rotation
x, y, z = location
if z < 0:
# Get the opposite location
location = [-x, -y, -z]
# Change the rotation by 180 degree
rotate_angle = np.pi
rotate_quaternion = Quaternion.from_axis_rotation(location, rotate_angle)
quaternion = rotate_quaternion.cross(quaternion)
# calculate rotation and translation matrix from quaternion and location
rt_matrix = quaternion.matrix33
RTT_matrix = np.concatenate((rt_matrix,np.array(location).reshape(3,1)),axis=1)
return location, quaternion, projected_points,RTT_matrix
def convert_rvec_to_quaternion(self, rvec):
'''Convert rvec (which is log quaternion) to quaternion'''
theta = np.sqrt(rvec[0] * rvec[0] + rvec[1] * rvec[1] + rvec[2] * rvec[2]) # in radians
raxis = [rvec[0] / theta, rvec[1] / theta, rvec[2] / theta]
# pyrr's Quaternion (order is XYZW), https://pyrr.readthedocs.io/en/latest/oo_api_quaternion.html
return Quaternion.from_axis_rotation(raxis, theta)
# Alternatively: pyquaternion
# return Quaternion(axis=raxis, radians=theta) # uses OpenCV's Quaternion (order is WXYZ)
def project_points(self, rvec, tvec):
'''Project points from model onto image using rotation, translation'''
output_points, tmp = cv2.projectPoints(
self.__object_vertex_coordinates,
rvec,
tvec,
self.__camera_intrinsic_matrix,
self.__dist_coeffs)
output_points = np.squeeze(output_points)
return output_points