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