Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
hand_eye_calibration_show_extrinsics.py
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# ViSP, open source Visual Servoing Platform software.
5# Copyright (C) 2005 - 2025 by Inria. All rights reserved.
6# Redistribution and use in source and binary forms, with or without modification,
7# are permitted provided that the following conditions are met:
8#
9# * Redistributions of source code must retain the above copyright notice,
10# this list of conditions and the following disclaimer.
11#
12# * Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# * Neither the names of the copyright holders nor the names of the contributors
17# may be used to endorse or promote products derived from this software
18# without specific prior written permission.
19#
20# This software is provided by the copyright holders and contributors "as is" and
21# any express or implied warranties, including, but not limited to, the implied
22# warranties of merchantability and fitness for a particular purpose are disclaimed.
23# In no event shall copyright holders or contributors be liable for any direct,
24# indirect, incidental, special, exemplary, or consequential damages
25# (including, but not limited to, procurement of substitute goods or services;
26# loss of use, data, or profits; or business interruption) however caused
27# and on any theory of liability, whether in contract, strict liability,
28# or tort (including negligence or otherwise) arising in any way out of
29# the use of this software, even if advised of the possibility of such damage.
30
31# Python 2/3 compatibility
32from __future__ import print_function
33from __future__ import division
34
35from scipy.spatial.transform import Rotation
36import numpy as np
37from numpy import linspace
38# pip install pyyaml for Python2
39# pip3 install pyyaml for Python3
40import yaml
41
43 R = M[0:3, 0:3]
44 T = M[0:3, 3]
45 M_inv = np.identity(4)
46 M_inv[0:3, 0:3] = R.T
47 M_inv[0:3, 3] = -(R.T).dot(T)
48
49 return M_inv
50
52 #R = Rotation.from_rotvec(pose[3:]).as_dcm()
53 R = Rotation.from_rotvec(pose[3:]).as_matrix()
54 M = np.identity(4)
55 M[0:3,0:3] = R
56 M[0:3,3] = pose[:3]
57 return M
58
59def transform_to_matplotlib_frame(cMo, X, patternCentric=False):
60 M = np.identity(4)
61 if patternCentric:
62 M[1,1] = -1
63 M[2,2] = -1
64 else:
65 M[1,1] = 0
66 M[1,2] = 1
67 M[2,1] = -1
68 M[2,2] = 0
69
70 if patternCentric:
71 return M.dot(inverse_homogeneoux_matrix(cMo).dot(X))
72 else:
73 return M.dot(cMo.dot(X))
74
75def create_frame(size):
76 X_frame = np.ones((4,4))
77 X_frame[0:3,0] = [0, 0, 0]
78 X_frame[0:3,1] = [size, 0, 0]
79 X_frame[0:3,2] = [0, size, 0]
80 X_frame[0:3,3] = [0, 0, size]
81
82 return X_frame
83
84def create_camera_model(width, height, focal_px, scale_focal, draw_frame_axis=False):
85 f_scale = scale_focal / focal_px
86
87 # draw image plane
88 X_img_plane = np.ones((4,5))
89 X_img_plane[0:3,0] = [-width, height, f_scale]
90 X_img_plane[0:3,1] = [width, height, f_scale]
91 X_img_plane[0:3,2] = [width, -height, f_scale]
92 X_img_plane[0:3,3] = [-width, -height, f_scale]
93 X_img_plane[0:3,4] = [-width, height, f_scale]
94
95 # draw triangle above the image plane
96 X_triangle = np.ones((4,3))
97 X_triangle[0:3,0] = [-width, -height, f_scale]
98 X_triangle[0:3,1] = [0, -2*height, f_scale]
99 X_triangle[0:3,2] = [width, -height, f_scale]
100
101 # draw camera
102 X_center1 = np.ones((4,2))
103 X_center1[0:3,0] = [0, 0, 0]
104 X_center1[0:3,1] = [-width, height, f_scale]
105
106 X_center2 = np.ones((4,2))
107 X_center2[0:3,0] = [0, 0, 0]
108 X_center2[0:3,1] = [width, height, f_scale]
109
110 X_center3 = np.ones((4,2))
111 X_center3[0:3,0] = [0, 0, 0]
112 X_center3[0:3,1] = [width, -height, f_scale]
113
114 X_center4 = np.ones((4,2))
115 X_center4[0:3,0] = [0, 0, 0]
116 X_center4[0:3,1] = [-width, -height, f_scale]
117
118 # draw camera frame axis
119 X_frame1 = np.ones((4,2))
120 X_frame1[0:3,0] = [0, 0, 0]
121 X_frame1[0:3,1] = [f_scale/2, 0, 0]
122
123 X_frame2 = np.ones((4,2))
124 X_frame2[0:3,0] = [0, 0, 0]
125 X_frame2[0:3,1] = [0, f_scale/2, 0]
126
127 X_frame3 = np.ones((4,2))
128 X_frame3[0:3,0] = [0, 0, 0]
129 X_frame3[0:3,1] = [0, 0, f_scale/2]
130
131 if draw_frame_axis:
132 return [X_img_plane, X_triangle, X_center1, X_center2, X_center3, X_center4, X_frame1, X_frame2, X_frame3]
133 else:
134 return [X_img_plane, X_triangle, X_center1, X_center2, X_center3, X_center4]
135
136def create_board_model(extrinsics, board_width, board_height, square_size, draw_frame_axis=False):
137 width = board_width*square_size
138 height = board_height*square_size
139
140 # draw calibration board
141 X_board = np.ones((4,5))
142 X_board[0:3,0] = [0,0,0]
143 X_board[0:3,1] = [width,0,0]
144 X_board[0:3,2] = [width,height,0]
145 X_board[0:3,3] = [0,height,0]
146 X_board[0:3,4] = [0,0,0]
147
148 # draw board frame axis
149 X_frame1 = np.ones((4,2))
150 X_frame1[0:3,0] = [0, 0, 0]
151 X_frame1[0:3,1] = [height/2, 0, 0]
152
153 X_frame2 = np.ones((4,2))
154 X_frame2[0:3,0] = [0, 0, 0]
155 X_frame2[0:3,1] = [0, height/2, 0]
156
157 X_frame3 = np.ones((4,2))
158 X_frame3[0:3,0] = [0, 0, 0]
159 X_frame3[0:3,1] = [0, 0, height/2]
160
161 if draw_frame_axis:
162 return [X_board, X_frame1, X_frame2, X_frame3]
163 else:
164 return [X_board]
165
166def draw(ax, cam_width, cam_height, focal_px, scale_focal,
167 extrinsics, board_width, board_height, square_size,
168 eMc, frame_size):
169 from matplotlib import cm
170
171 min_values = np.zeros((3,1))
172 min_values = np.inf
173 max_values = np.zeros((3,1))
174 max_values = -np.inf
175
176 X_moving = create_camera_model(cam_width, cam_height, focal_px, scale_focal)
177 X_static = create_board_model(extrinsics, board_width, board_height, square_size, True)
178 X_frame = create_frame(frame_size)
179
180 cm_subsection = linspace(0.0, 1.0, extrinsics.shape[0])
181 colors = [ cm.jet(x) for x in cm_subsection ]
182
183 patternCentric = True
184
185 for i in range(len(X_static)):
186 X = np.zeros(X_static[i].shape)
187 for j in range(X_static[i].shape[1]):
188 X[:,j] = transform_to_matplotlib_frame(np.eye(4), X_static[i][:,j], patternCentric)
189 ax.plot3D(X[0,:], X[1,:], X[2,:], color='r')
190 min_values = np.minimum(min_values, X[0:3,:].min(1))
191 max_values = np.maximum(max_values, X[0:3,:].max(1))
192
193 for idx in range(extrinsics.shape[0]):
194 cMo = pose_to_homogeneous_matrix(extrinsics[idx,:])
195 for i in range(len(X_moving)):
196 X = np.zeros(X_moving[i].shape)
197 for j in range(X_moving[i].shape[1]):
198 X[0:4,j] = transform_to_matplotlib_frame(cMo, X_moving[i][0:4,j], patternCentric)
199 ax.plot3D(X[0,:], X[1,:], X[2,:], color=colors[idx])
200 min_values = np.minimum(min_values, X[0:3,:].min(1))
201 max_values = np.maximum(max_values, X[0:3,:].max(1))
202
203 eMo = eMc.dot(cMo)
204 oX = np.zeros((4,2), dtype=np.float64)
205 oY = np.zeros((4,2), dtype=np.float64)
206 oZ = np.zeros((4,2), dtype=np.float64)
207 ec = np.zeros((4,2), dtype=np.float64)
208
209 oX[:,0] = transform_to_matplotlib_frame(eMo, X_frame[:,0], patternCentric)
210 oX[:,1] = transform_to_matplotlib_frame(eMo, X_frame[:,1], patternCentric)
211 ax.plot3D(oX[0,:], oX[1,:], oX[2,:], color='r')
212
213 oY[:,0] = transform_to_matplotlib_frame(eMo, X_frame[:,0], patternCentric)
214 oY[:,1] = transform_to_matplotlib_frame(eMo, X_frame[:,2], patternCentric)
215 ax.plot3D(oY[0,:], oY[1,:], oY[2,:], color='g')
216
217 oZ[:,0] = transform_to_matplotlib_frame(eMo, X_frame[:,0], patternCentric)
218 oZ[:,1] = transform_to_matplotlib_frame(eMo, X_frame[:,3], patternCentric)
219 ax.plot3D(oZ[0,:], oZ[1,:], oZ[2,:], color='b')
220
221 ec[:,0] = transform_to_matplotlib_frame(eMo, X_frame[:,0], patternCentric)
222 ec[:,1] = transform_to_matplotlib_frame(cMo, X_moving[2][0:4,0], patternCentric)
223 ax.plot3D(ec[0,:], ec[1,:], ec[2,:], color=colors[idx])
224
225 return min_values, max_values
226
227def main():
228 import argparse
229
230 parser = argparse.ArgumentParser(description='Plot hand-eye calibration extrinsics.',
231 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
232 parser.add_argument('--ndata', type=int, required=True,
233 help='Number of camera poses.')
234 parser.add_argument('--eMc-yaml', type=str, required=True,
235 help='Path to the estimated eMc yaml file.')
236 parser.add_argument('--start-index', type=int, default=1,
237 help='Start index.')
238 parser.add_argument('--cPo-file-pattern', type=str, default='pose_cPo_%d.yaml',
239 help='cPo filename pattern for camera poses.')
240 parser.add_argument('--cam-width', type=float, default=0.064/2,
241 help='Width/2 of the displayed camera.')
242 parser.add_argument('--cam-height', type=float, default=0.048/2,
243 help='Height/2 of the displayed camera.')
244 parser.add_argument('--focal-px', type=float, default=600,
245 help='Camera focal length to draw the camera visualization.')
246 parser.add_argument('--scale-focal', type=float, default=40,
247 help='Value to scale the focal length.')
248 parser.add_argument('--board-width', type=int, default=9,
249 help='Calibration board width.')
250 parser.add_argument('--board-height', type=int, default=6,
251 help='Calibration board height.')
252 parser.add_argument('--square-size', type=float, default=0.025,
253 help='Calibration board square size.')
254 parser.add_argument('--frame-size', type=float, default=0.025,
255 help='End-effector frame size for visualization.')
256 args = parser.parse_args()
257
258 def load_yaml_pose(filename):
259 with open(filename) as file:
260 pose_dict = yaml.load(file, Loader=yaml.FullLoader)
261 return np.array(pose_dict['data']).flatten()
262
263 camera_poses = np.empty((args.ndata, 6), dtype=np.float64)
264 for idx in range(args.start_index, args.start_index + args.ndata):
265 camera_pose_filename = args.cPo_file_pattern % (idx)
266 camera_poses[idx-args.start_index,:] = load_yaml_pose(camera_pose_filename)
267
268 print('camera_poses:\n', camera_poses)
269 eMc = load_yaml_pose(args.eMc_yaml)
270 print('eMc:', eMc.transpose())
271
272 import matplotlib.pyplot as plt
273 from mpl_toolkits.mplot3d import Axes3D # pylint: disable=unused-variable
274
275 fig = plt.figure()
276 #ax = fig.gca(projection='3d')
277 ax = fig.add_subplot(projection='3d')
278 #ax.set_aspect("equal")
279
280 cam_width = args.cam_width
281 cam_height = args.cam_height
282 scale_focal = args.scale_focal
283 min_values, max_values = draw(ax, cam_width, cam_height, args.focal_px,
284 scale_focal, camera_poses, args.board_width,
285 args.board_height, args.square_size,
286 pose_to_homogeneous_matrix(eMc), args.frame_size)
287
288 X_min = min_values[0]
289 X_max = max_values[0]
290 Y_min = min_values[1]
291 Y_max = max_values[1]
292 Z_min = min_values[2]
293 Z_max = max_values[2]
294 max_range = np.array([X_max-X_min, Y_max-Y_min, Z_max-Z_min]).max() / 2.0
295
296 mid_x = (X_max+X_min) * 0.5
297 mid_y = (Y_max+Y_min) * 0.5
298 mid_z = (Z_max+Z_min) * 0.5
299 ax.set_xlim(mid_x - max_range, mid_x + max_range)
300 ax.set_ylim(mid_y - max_range, mid_y + max_range)
301 ax.set_zlim(mid_z - max_range, mid_z + max_range)
302
303 ax.set_xlabel('x')
304 ax.set_ylabel('-y')
305 ax.set_zlabel('-z')
306 ax.set_title('Hand-Eye Calibration Visualization')
307
308 plt.show()
309
310if __name__ == '__main__':
311 main()
draw(ax, cam_width, cam_height, focal_px, scale_focal, extrinsics, board_width, board_height, square_size, eMc, frame_size)
create_board_model(extrinsics, board_width, board_height, square_size, draw_frame_axis=False)
create_camera_model(width, height, focal_px, scale_focal, draw_frame_axis=False)