00001
00024 #include "ccny_rgbd/registration/motion_estimation_icp_prob_model.h"
00025
00026 namespace ccny_rgbd {
00027
00028 MotionEstimationICPProbModel::MotionEstimationICPProbModel(
00029 const ros::NodeHandle& nh,
00030 const ros::NodeHandle& nh_private):
00031 MotionEstimation(nh, nh_private),
00032 model_idx_(0),
00033 model_size_(0)
00034 {
00035
00036
00037 if (!nh_private_.getParam ("fixed_frame", fixed_frame_))
00038 fixed_frame_ = "/odom";
00039 if (!nh_private_.getParam ("base_frame", base_frame_))
00040 base_frame_ = "/camera_link";
00041
00042 if (!nh_private_.getParam ("reg/ICPProbModel/tf_epsilon_linear", tf_epsilon_linear_))
00043 tf_epsilon_linear_ = 1e-4;
00044 if (!nh_private_.getParam ("reg/ICPProbModel/tf_epsilon_angular", tf_epsilon_angular_))
00045 tf_epsilon_angular_ = 1.7e-3;
00046 if (!nh_private_.getParam ("reg/ICPProbModel/max_iterations", max_iterations_))
00047 max_iterations_ = 10;
00048 if (!nh_private_.getParam ("reg/ICPProbModel/min_correspondences", min_correspondences_))
00049 min_correspondences_ = 15;
00050 if (!nh_private_.getParam ("reg/ICPProbModel/max_model_size", max_model_size_))
00051 max_model_size_ = 3000;
00052
00053 if (!nh_private_.getParam ("reg/ICPProbModel/max_corresp_dist_eucl", max_corresp_dist_eucl_))
00054 max_corresp_dist_eucl_ = 0.15;
00055 if (!nh_private_.getParam ("reg/ICPProbModel/max_assoc_dist_mah", max_assoc_dist_mah_))
00056 max_assoc_dist_mah_ = 10.0;
00057 if (!nh_private_.getParam ("reg/ICPProbModel/n_nearest_neighbors", n_nearest_neighbors_))
00058 n_nearest_neighbors_ = 4;
00059
00060 if (!nh_private_.getParam ("reg/ICPProbModel/publish_model_cloud", publish_model_))
00061 publish_model_ = false;
00062 if (!nh_private_.getParam ("reg/ICPProbModel/publish_model_covariances", publish_model_cov_))
00063 publish_model_cov_ = false;
00064
00065
00066
00067
00068 max_corresp_dist_eucl_sq_ = max_corresp_dist_eucl_ * max_corresp_dist_eucl_;
00069 max_assoc_dist_mah_sq_ = max_assoc_dist_mah_ * max_assoc_dist_mah_;
00070
00071 model_ptr_.reset(new PointCloudFeature());
00072 model_ptr_->header.frame_id = fixed_frame_;
00073
00074 f2b_.setIdentity();
00075 I_.setIdentity();
00076
00077
00078
00079 if (publish_model_)
00080 {
00081 model_publisher_ = nh_.advertise<PointCloudFeature>(
00082 "model/cloud", 1);
00083 }
00084 if (publish_model_cov_)
00085 {
00086 covariances_publisher_ = nh_.advertise<visualization_msgs::Marker>(
00087 "model/covariances", 1);
00088 }
00089
00090
00091
00092 save_service_ = nh_.advertiseService(
00093 "save_sparse_map", &MotionEstimationICPProbModel::saveSrvCallback, this);
00094 }
00095
00096 MotionEstimationICPProbModel::~MotionEstimationICPProbModel()
00097 {
00098
00099 }
00100
00101 void MotionEstimationICPProbModel::addToModel(
00102 const Vector3f& data_mean,
00103 const Matrix3f& data_cov)
00104 {
00105
00106
00107 PointFeature data_point;
00108 data_point.x = data_mean(0,0);
00109 data_point.y = data_mean(1,0);
00110 data_point.z = data_mean(2,0);
00111
00112 if (model_size_ < max_model_size_)
00113 {
00114 covariances_.push_back(data_cov);
00115 means_.push_back(data_mean);
00116 model_ptr_->push_back(data_point);
00117
00118 model_size_++;
00119 }
00120 else
00121 {
00122 if (model_idx_ == max_model_size_)
00123 model_idx_ = 0;
00124
00125 covariances_.at(model_idx_) = data_cov;
00126 means_.at(model_idx_) = data_mean;
00127 model_ptr_->at(model_idx_) = data_point;
00128 }
00129
00130 model_idx_++;
00131 }
00132
00133 bool MotionEstimationICPProbModel::getMotionEstimationImpl(
00134 RGBDFrame& frame,
00135 const tf::Transform& prediction,
00136 tf::Transform& motion)
00137 {
00139
00140 bool result;
00141 Vector3fVector data_means;
00142 Matrix3fVector data_covariances;
00143
00144
00145 removeInvalidDistributions(
00146 frame.kp_means, frame.kp_covariances, frame.kp_valid,
00147 data_means, data_covariances);
00148
00149
00150 transformDistributions(data_means, data_covariances, f2b_ * b2c_);
00151
00152
00153
00154 if (model_size_ == 0)
00155 {
00156 ROS_INFO("No points in model: initializing from features.");
00157 motion.setIdentity();
00158 initializeModelFromData(data_means, data_covariances);
00159 result = true;
00160 }
00161 else
00162 {
00163
00164 result = alignICPEuclidean(data_means, motion);
00165
00166 if (!result) return false;
00167
00168 constrainMotion(motion);
00169 f2b_ = motion * f2b_;
00170
00171
00172 transformDistributions(data_means, data_covariances, motion);
00173
00174
00175 updateModelFromData(data_means, data_covariances);
00176 }
00177
00178
00179 model_tree_.setInputCloud(model_ptr_);
00180
00181
00182 model_ptr_->header.stamp = frame.header.stamp;
00183 model_ptr_->width = model_ptr_->points.size();
00184
00185
00186 if (publish_model_)
00187 model_publisher_.publish(model_ptr_);
00188 if (publish_model_cov_)
00189 publishCovariances();
00190
00191 return result;
00192 }
00193
00194 bool MotionEstimationICPProbModel::alignICPEuclidean(
00195 const Vector3fVector& data_means,
00196 tf::Transform& correction)
00197 {
00198 TransformationEstimationSVD svd;
00199
00200
00201 PointCloudFeature data_cloud;
00202 pointCloudFromMeans(data_means, data_cloud);
00203
00204
00205 Eigen::Matrix4f final_transformation;
00206 final_transformation.setIdentity();
00207
00208 for (int iteration = 0; iteration < max_iterations_; ++iteration)
00209 {
00210
00211 IntVector data_indices, model_indices;
00212 getCorrespEuclidean(data_cloud, data_indices, model_indices);
00213
00214 if ((int)data_indices.size() < min_correspondences_)
00215 {
00216 ROS_WARN("[ICP] Not enough correspondences (%d of %d minimum). Leacing ICP loop",
00217 (int)data_indices.size(), min_correspondences_);
00218 return false;
00219 }
00220
00221
00222 Eigen::Matrix4f transformation;
00223 svd.estimateRigidTransformation (data_cloud, data_indices,
00224 *model_ptr_, model_indices,
00225 transformation);
00226
00227
00228 pcl::transformPointCloud(data_cloud, data_cloud, transformation);
00229
00230
00231 final_transformation = transformation * final_transformation;
00232
00233
00234 double linear, angular;
00235 getTfDifference(
00236 tfFromEigen(transformation), linear, angular);
00237 if (linear < tf_epsilon_linear_ &&
00238 angular < tf_epsilon_angular_)
00239 {
00240
00241
00242 break;
00243 }
00244 }
00245
00246 correction = tfFromEigen(final_transformation);
00247 return true;
00248 }
00249
00250 void MotionEstimationICPProbModel::getCorrespEuclidean(
00251 const PointCloudFeature& data_cloud,
00252 IntVector& data_indices,
00253 IntVector& model_indices)
00254 {
00255 for (unsigned int data_idx = 0; data_idx < data_cloud.size(); ++data_idx)
00256 {
00257 const PointFeature& data_point = data_cloud.points[data_idx];
00258
00259 int eucl_nn_idx;
00260 double eucl_dist_sq;
00261
00262 bool nn_result = getNNEuclidean(data_point, eucl_nn_idx, eucl_dist_sq);
00263
00264 if (nn_result && eucl_dist_sq < max_corresp_dist_eucl_sq_)
00265 {
00266 data_indices.push_back(data_idx);
00267 model_indices.push_back(eucl_nn_idx);
00268 }
00269 }
00270 }
00271
00272 bool MotionEstimationICPProbModel::getNNEuclidean(
00273 const PointFeature& data_point,
00274 int& eucl_nn_idx, double& eucl_dist_sq)
00275 {
00276
00277 IntVector indices;
00278 FloatVector dist_sq;
00279
00280 indices.resize(1);
00281 dist_sq.resize(1);
00282
00283 int n_retrieved = model_tree_.nearestKSearch(data_point, 1, indices, dist_sq);
00284
00285 if (n_retrieved != 0)
00286 {
00287 eucl_nn_idx = indices[0];
00288 eucl_dist_sq = dist_sq[0];
00289 return true;
00290 }
00291 else return false;
00292 }
00293
00294 bool MotionEstimationICPProbModel::getNNMahalanobis(
00295 const Vector3f& data_mean, const Matrix3f& data_cov,
00296 int& mah_nn_idx, double& mah_dist_sq,
00297 IntVector& indices, FloatVector& dists_sq)
00298 {
00299 PointFeature p_data;
00300 p_data.x = data_mean(0,0);
00301 p_data.y = data_mean(1,0);
00302 p_data.z = data_mean(2,0);
00303
00304 int n_retrieved = model_tree_.nearestKSearch(p_data, n_nearest_neighbors_, indices, dists_sq);
00305
00306
00307 double best_mah_dist_sq = 0;
00308 int best_mah_nn_idx = -1;
00309
00310 for (int i = 0; i < n_retrieved; i++)
00311 {
00312 int nn_idx = indices[i];
00313
00314 const Vector3f& model_mean = means_[nn_idx];
00315 const Matrix3f& model_cov = covariances_[nn_idx];
00316
00317 Vector3f diff_mat = model_mean - data_mean;
00318 Matrix3f sum_cov = model_cov + data_cov;
00319 Matrix3f sum_cov_inv = sum_cov.inverse();
00320
00321 Eigen::Matrix<float,1,1> mah_mat = diff_mat.transpose() * sum_cov_inv * diff_mat;
00322
00323 double mah_dist_sq = mah_mat(0,0);
00324
00325 if (best_mah_nn_idx == -1 || mah_dist_sq < best_mah_dist_sq)
00326 {
00327 best_mah_dist_sq = mah_dist_sq;
00328 best_mah_nn_idx = nn_idx;
00329
00330 }
00331 }
00332
00333 if (best_mah_nn_idx != -1)
00334 {
00335
00336 mah_dist_sq = best_mah_dist_sq;
00337 mah_nn_idx = best_mah_nn_idx;
00338 return true;
00339 }
00340 else return false;
00341 }
00342
00343 void MotionEstimationICPProbModel::initializeModelFromData(
00344 const Vector3fVector& data_means,
00345 const Matrix3fVector& data_covariances)
00346 {
00347 for (unsigned int idx = 0; idx < data_means.size(); ++idx)
00348 {
00349 const Vector3f& mean = data_means[idx];
00350 const Matrix3f& cov = data_covariances[idx];
00351 addToModel(mean, cov);
00352 }
00353 }
00354
00355 void MotionEstimationICPProbModel::updateModelFromData(
00356 const Vector3fVector& data_means,
00357 const Matrix3fVector& data_covariances)
00358 {
00359
00360 IntVector indices;
00361 FloatVector dists_sq;
00362 indices.resize(n_nearest_neighbors_);
00363 dists_sq.resize(n_nearest_neighbors_);
00364
00365 for (unsigned int idx = 0; idx < data_means.size(); ++idx)
00366 {
00367 const Vector3f& data_mean = data_means[idx];
00368 const Matrix3f& data_cov = data_covariances[idx];
00369
00370
00371 double mah_dist_sq;
00372 int mah_nn_idx;
00373 bool nn_result = getNNMahalanobis(
00374 data_mean, data_cov, mah_nn_idx, mah_dist_sq, indices, dists_sq);
00375
00376 if (nn_result && mah_dist_sq < max_assoc_dist_mah_sq_)
00377 {
00378
00379
00380
00381 const Vector3f& model_mean_pred = means_[mah_nn_idx];
00382 const Matrix3f& model_cov_pred = covariances_[mah_nn_idx];
00383
00384
00385 Vector3f y = data_mean - model_mean_pred;
00386 Matrix3f S = data_cov + model_cov_pred;
00387
00388
00389 Matrix3f K = model_cov_pred * S.inverse();
00390
00391
00392 Vector3f model_mean_upd = model_mean_pred + K * y;
00393 Matrix3f model_cov_upd = (I_ - K) * model_cov_pred;
00394
00395
00396 means_[mah_nn_idx] = model_mean_upd;
00397 covariances_[mah_nn_idx] = model_cov_upd;
00398
00399 PointFeature updated_point;
00400 updated_point.x = model_mean_upd(0,0);
00401 updated_point.y = model_mean_upd(1,0);
00402 updated_point.z = model_mean_upd(2,0);
00403
00404 model_ptr_->points[mah_nn_idx] = updated_point;
00405 }
00406 else
00407 {
00408 addToModel(data_mean, data_cov);
00409 }
00410 }
00411 }
00412
00413 void MotionEstimationICPProbModel::publishCovariances()
00414 {
00415
00416 visualization_msgs::Marker marker;
00417 marker.header.frame_id = fixed_frame_;
00418 marker.header.stamp = model_ptr_->header.stamp;
00419 marker.type = visualization_msgs::Marker::LINE_LIST;
00420 marker.color.r = 1.0;
00421 marker.color.g = 1.0;
00422 marker.color.b = 0.0;
00423 marker.color.a = 1.0;
00424 marker.scale.x = 0.0025;
00425 marker.action = visualization_msgs::Marker::ADD;
00426 marker.ns = "model_covariances";
00427 marker.id = 0;
00428 marker.lifetime = ros::Duration();
00429
00430 for (unsigned int i = 0; i < model_ptr_->points.size(); ++i)
00431 {
00432
00433 cv::Mat evl(1, 3, CV_64F);
00434 cv::Mat evt(3, 3, CV_64F);
00435
00436 const Matrix3f& cov_eigen = covariances_[i];
00437
00438 cv::Mat cov(3,3,CV_64F);
00439 for(int j = 0; j < 3; ++j)
00440 for(int i = 0; i < 3; ++i)
00441 cov.at<double>(j,i) = cov_eigen(j,i);
00442
00443 cv::eigen(cov, evl, evt);
00444
00445 double mx = model_ptr_->points[i].x;
00446 double my = model_ptr_->points[i].y;
00447 double mz = model_ptr_->points[i].z;
00448
00449 for (int e = 0; e < 3; ++e)
00450 {
00451 geometry_msgs::Point a;
00452 geometry_msgs::Point b;
00453
00454 double sigma = sqrt(std::abs(evl.at<double>(0,e)));
00455 double scale = sigma * 3.0;
00456
00457 tf::Vector3 evt_tf(evt.at<double>(e,0),
00458 evt.at<double>(e,1),
00459 evt.at<double>(e,2));
00460
00461 a.x = mx + evt_tf.getX() * scale;
00462 a.y = my + evt_tf.getY() * scale;
00463 a.z = mz + evt_tf.getZ() * scale;
00464
00465 b.x = mx - evt_tf.getX() * scale;
00466 b.y = my - evt_tf.getY() * scale;
00467 b.z = mz - evt_tf.getZ() * scale;
00468
00469 marker.points.push_back(a);
00470 marker.points.push_back(b);
00471 }
00472 }
00473
00474 covariances_publisher_.publish(marker);
00475 }
00476
00477 bool MotionEstimationICPProbModel::saveSrvCallback(
00478 ccny_rgbd::Save::Request& request,
00479 ccny_rgbd::Save::Response& response)
00480 {
00481 ROS_INFO("Saving model to %s", request.filename.c_str());
00482
00483 bool result = saveModel(request.filename);
00484
00485 if (result)
00486 ROS_INFO("Successfully saved model.");
00487 else
00488 ROS_ERROR("Failed to save model.");
00489
00490 return result;
00491 }
00492
00493 bool MotionEstimationICPProbModel::saveModel(const std::string& filename)
00494 {
00496
00497
00498
00499
00500
00501
00502
00503
00504
00505
00506
00507
00508 std::string filename_pcd = filename + ".pcd";
00509 pcl::PCDWriter writer;
00510 int result_pcd = writer.writeBinary<PointFeature>(filename_pcd, *model_ptr_);
00511
00512 return (result_pcd == 0);
00513 }
00514
00515 }