00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044 import numpy
00045 from numpy import matrix, reshape, array, zeros, diag, real
00046
00047 import roslib; roslib.load_manifest('pr2_calibration_estimation')
00048 import rospy
00049 from pr2_calibration_estimation.full_chain import FullChainRobotParams
00050 from sensor_msgs.msg import JointState
00051
00052 class CameraChainBundler:
00053 """
00054 Tool used to generate a list of CameraChain sensors from a single calibration sample message
00055 """
00056 def __init__(self, valid_configs):
00057 """
00058 Inputs:
00059 - valid_configs: A list of dictionaries, where each list elem stores the configuration of a potential
00060 camera chain that we might encounter.
00061 Example (in yaml format):
00062 - camera_id: forearm_right_rect
00063 sensor_id: forearm_right_rect
00064 chain:
00065 before_chain: [r_shoulder_pan_joint]
00066 chain_id: right_arm_chain
00067 after_chain: [r_forearm_roll_adj, r_forearm_cam_frame_joint,
00068 r_forearm_cam_optical_frame_joint]
00069 dh_link_num: 4
00070 """
00071 self._valid_configs = valid_configs
00072
00073
00074 def build_blocks(self, M_robot):
00075 """
00076 Input:
00077 - M_robot: A calibration sample of type calibration_msgs/RobotMeasurement
00078 Returns:
00079 - sensors: A list of CameraChainSensor objects that corresponds to all camera chain sensors found
00080 in the calibration message that was passed in.
00081 """
00082 sensors = []
00083 for cur_config in self._valid_configs:
00084 if cur_config["camera_id"] in [ x.camera_id for x in M_robot.M_cam ] and \
00085 cur_config["chain"]["chain_id"] in [ x.chain_id for x in M_robot.M_chain ] :
00086 M_cam = M_robot.M_cam [ [ x.camera_id for x in M_robot.M_cam ].index(cur_config["camera_id"])]
00087 M_chain = M_robot.M_chain[ [ x.chain_id for x in M_robot.M_chain ].index(cur_config["chain"]["chain_id"]) ]
00088 cur_sensor = CameraChainSensor(cur_config, M_cam, M_chain)
00089 sensors.append(cur_sensor)
00090 else:
00091 rospy.logdebug(" Didn't find block")
00092 return sensors
00093
00094 class CameraChainSensor:
00095 def __init__(self, config_dict, M_cam, M_chain):
00096 """
00097 Generates a single sensor block for a single configuration
00098 Inputs:
00099 - config_dict: The configuration for this specific sensor. This looks exactly like
00100 a single element from the valid_configs list passed into CameraChainBundler.__init__
00101 - M_cam: The camera measurement of type calibration_msgs/CameraMeasurement
00102 - M_chain: The chain measurement of type calibration_msgs/ChainMeasurement
00103 """
00104
00105 self.sensor_type = "camera"
00106 self.sensor_id = config_dict["camera_id"]
00107
00108 self._config_dict = config_dict
00109 self._M_cam = M_cam
00110 self._M_chain = M_chain
00111
00112 self._chain = FullChainRobotParams(config_dict["chain"])
00113
00114 self.terms_per_sample = 2
00115
00116 def update_config(self, robot_params):
00117 """
00118 On each optimization cycle the set of system parameters that we're optimizing over will change. Thus,
00119 after each change in parameters we must call update_config to ensure that our calculated residual reflects
00120 the newest set of system parameters.
00121 """
00122 self._camera = robot_params.rectified_cams[ self._config_dict["camera_id"] ]
00123
00124 self._chain.update_config(robot_params)
00125
00126 def compute_residual(self, target_pts):
00127 """
00128 Computes the measurement residual for the current set of system parameters and target points
00129 Input:
00130 - target_pts: 4XN matrix, storing features point locations in world cartesian homogenous coordinates.
00131 Output:
00132 - r: 2N long vector, storing pixel residuals for the target points in the form [u1, v1, u2, v2, ..., uN, vN]
00133 """
00134 z_mat = self.get_measurement()
00135 h_mat = self.compute_expected(target_pts)
00136 assert(z_mat.shape[1] == 2)
00137 assert(h_mat.shape[1] == 2)
00138 assert(z_mat.shape[0] == z_mat.shape[0])
00139 r = array(reshape(h_mat - z_mat, [-1,1]))[:,0]
00140 return r
00141
00142
00143 def compute_residual_scaled(self, target_pts):
00144 """
00145 Computes the residual, and then scales it by sqrt(Gamma), where Gamma
00146 is the information matrix for this measurement (Cov^-1).
00147 """
00148 r = self.compute_residual(target_pts)
00149 gamma_sqrt = self.compute_marginal_gamma_sqrt(target_pts)
00150 r_scaled = gamma_sqrt * matrix(r).T
00151 return array(r_scaled.T)[0]
00152
00153 def compute_marginal_gamma_sqrt(self, target_pts):
00154 """
00155 Calculates the square root of the information matrix for the measurement of the
00156 current set of system parameters at the passed in set of target points.
00157 """
00158 import scipy.linalg
00159 cov = self.compute_cov(target_pts)
00160 gamma = matrix(zeros(cov.shape))
00161 num_pts = self.get_residual_length()/2
00162
00163 for k in range(num_pts):
00164
00165 first = 2*k
00166 last = 2*k+2
00167 sub_cov = matrix(cov[first:last, first:last])
00168 sub_gamma_sqrt_full = matrix(scipy.linalg.sqrtm(sub_cov.I))
00169 sub_gamma_sqrt = real(sub_gamma_sqrt_full)
00170 assert(scipy.linalg.norm(sub_gamma_sqrt_full - sub_gamma_sqrt) < 1e-6)
00171 gamma[first:last, first:last] = sub_gamma_sqrt
00172 return gamma
00173
00174 def get_residual_length(self):
00175 N = len(self._M_cam.image_points)
00176 return N*2
00177
00178
00179 def get_measurement(self):
00180 """
00181 Get the target's pixel coordinates as measured by the actual sensor
00182 """
00183 camera_pix = numpy.matrix([[pt.x, pt.y] for pt in self._M_cam.image_points])
00184 return camera_pix
00185
00186 def compute_expected(self, target_pts):
00187 """
00188 Compute the expected pixel coordinates for a set of target points.
00189 target_pts: 4xN matrix, storing feature points of the target, in homogeneous coords
00190 Returns: target points projected into pixel coordinates, in a Nx2 matrix
00191 """
00192 return self._compute_expected(self._M_chain.chain_state, target_pts)
00193
00194 def _compute_expected(self, chain_state, target_pts):
00195 """
00196 Compute what measurement we would expect to see, based on the current system parameters
00197 and the specified target point locations.
00198
00199 Inputs:
00200 - chain_state: The joint angles of this sensor's chain of type sensor_msgs/JointState.
00201 - target_pts: 4XN matrix, storing features point locations in world cartesian homogenous coordinates.
00202
00203 Returns: target points projected into pixel coordinates, in a Nx2 matrix
00204 """
00205
00206 camera_pose_root = self._chain.calc_block.fk(chain_state)
00207 cam_frame_pts = camera_pose_root.I * target_pts
00208
00209 pixel_pts = self._camera.project(self._M_cam.cam_info.P, cam_frame_pts)
00210
00211 return pixel_pts.T
00212
00213 def compute_expected_J(self, target_pts):
00214 """
00215 The output Jacobian J shows how moving target_pts in cartesian space affects
00216 the expected measurement in (u,v) camera coordinates.
00217 For n points in target_pts, J is a 2nx3n matrix
00218 Note: This doesn't seem to be used anywhere, except maybe in some drawing code
00219 """
00220 epsilon = 1e-8
00221 N = len(self._M_cam.image_points)
00222 Jt = zeros([N*3, N*2])
00223 for k in range(N):
00224
00225 sub_Jt = zeros([3,2])
00226 x = target_pts[:,k].copy()
00227 f0 = self.compute_expected(x)
00228 for i in [0,1,2]:
00229 x[i,0] += epsilon
00230 fTest = self.compute_expected(x)
00231 sub_Jt[i,:] = array((fTest - f0) / epsilon)
00232 x[i,0] -= epsilon
00233 Jt[k*3:(k+1)*3, k*2:(k+1)*2] = sub_Jt
00234 return Jt.T
00235
00236
00237 def compute_cov(self, target_pts):
00238 '''
00239 Computes the measurement covariance in pixel coordinates for the given
00240 set of target points (target_pts)
00241 Input:
00242 - target_pts: 4xN matrix, storing N feature points of the target, in homogeneous coords
00243 '''
00244 epsilon = 1e-8
00245
00246 num_joints = len(self._M_chain.chain_state.position)
00247 Jt = zeros([num_joints, self.get_residual_length()])
00248
00249 x = JointState()
00250 x.position = self._M_chain.chain_state.position[:]
00251
00252
00253 f0 = reshape(array(self._compute_expected(x, target_pts)), [-1])
00254 for i in range(num_joints):
00255 x.position = [cur_pos for cur_pos in self._M_chain.chain_state.position]
00256 x.position[i] += epsilon
00257 fTest = reshape(array(self._compute_expected(x, target_pts)), [-1])
00258 Jt[i] = (fTest - f0)/epsilon
00259 cov_angles = [x*x for x in self._chain.calc_block._chain._cov_dict['joint_angles']]
00260
00261
00262 chain_cov = matrix(Jt).T * matrix(diag(cov_angles)) * matrix(Jt)
00263 cam_cov = matrix(zeros(chain_cov.shape))
00264
00265
00266 var_u = self._camera._cov_dict['u'] * self._camera._cov_dict['u']
00267 var_v = self._camera._cov_dict['v'] * self._camera._cov_dict['v']
00268 for k in range(cam_cov.shape[0]/2):
00269 cam_cov[2*k , 2*k] = var_u
00270 cam_cov[2*k+1, 2*k+1] = var_v
00271
00272
00273 cov = chain_cov + cam_cov
00274 return cov
00275
00276 def build_sparsity_dict(self):
00277 """
00278 Build a dictionary that defines which parameters will in fact affect this measurement.
00279
00280 Returned dictionary should be of the following form:
00281 transforms:
00282 my_transformA: [1, 1, 1, 1, 1, 1]
00283 my_transformB: [1, 1, 1, 1, 1, 1]
00284 dh_chains:
00285 my_chain:
00286 dh:
00287 - [1, 1, 1, 1]
00288 - [1, 1, 1, 1]
00289 |
00290 - [1, 1, 1, 1]
00291 gearing: [1, 1, ---, 1]
00292 rectified_cams:
00293 my_cam:
00294 baseline_shift: 1
00295 f_shift: 1
00296 cx_shift: 1
00297 cy_shift: 1
00298 """
00299 sparsity = dict()
00300 sparsity['transforms'] = {}
00301 for cur_transform_name in ( self._config_dict['chain']['before_chain'] + self._config_dict['chain']['after_chain'] ):
00302 sparsity['transforms'][cur_transform_name] = [1, 1, 1, 1, 1, 1]
00303
00304 sparsity['dh_chains'] = {}
00305 chain_id = self._config_dict['chain']['chain_id']
00306 num_links = self._chain.calc_block._chain._M
00307 assert(num_links == len(self._M_chain.chain_state.position))
00308 sparsity['dh_chains'][chain_id] = {}
00309 sparsity['dh_chains'][chain_id]['dh'] = [ [1,1,1,1] ] * num_links
00310 sparsity['dh_chains'][chain_id]['gearing'] = [1] * num_links
00311
00312 sparsity['rectified_cams'] = {}
00313 sparsity['rectified_cams'][self.sensor_id] = dict( [(x,1) for x in self._camera.get_param_names()] )
00314
00315 return sparsity
00316
00317