motion_estimation_icp_prob_model.cpp
Go to the documentation of this file.
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   // **** init params
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; // 1 mm
00044   if (!nh_private_.getParam ("reg/ICPProbModel/tf_epsilon_angular", tf_epsilon_angular_))
00045     tf_epsilon_angular_ = 1.7e-3; // 1 deg
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   // **** variables
00066 
00067   // derived params
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   // **** publishers
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   // **** services
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   // **** create a PCL point
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 // model_size_ == max_model_size_
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   // remove nans from distributinos
00145   removeInvalidDistributions(
00146     frame.kp_means, frame.kp_covariances, frame.kp_valid,
00147     data_means, data_covariances);
00148   
00149   // transform distributions to world frame
00150   transformDistributions(data_means, data_covariances, f2b_ * b2c_);
00151        
00152   // **** perform registration
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     // align using icp 
00164     result = alignICPEuclidean(data_means, motion);
00165 
00166     if (!result) return false;
00167 
00168     constrainMotion(motion);
00169     f2b_ = motion * f2b_;
00170     
00171     // transform distributions to world frame
00172     transformDistributions(data_means, data_covariances, motion);
00173 
00174     // update model: inserts new features and updates old ones with KF
00175     updateModelFromData(data_means, data_covariances);
00176   }
00177 
00178   // update the model tree
00179   model_tree_.setInputCloud(model_ptr_);
00180 
00181   // update the model timestamp and auxiliary info
00182   model_ptr_->header.stamp = frame.header.stamp;
00183   model_ptr_->width = model_ptr_->points.size();
00184 
00185   // publish data for visualization
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   // create a point cloud from the means
00201   PointCloudFeature data_cloud;
00202   pointCloudFromMeans(data_means, data_cloud);
00203 
00204   // initialize the result transform
00205   Eigen::Matrix4f final_transformation; 
00206   final_transformation.setIdentity();
00207   
00208   for (int iteration = 0; iteration < max_iterations_; ++iteration)
00209   {    
00210     // get corespondences
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     // estimae transformation
00222     Eigen::Matrix4f transformation; 
00223     svd.estimateRigidTransformation (data_cloud, data_indices,
00224                                      *model_ptr_, model_indices,
00225                                      transformation);
00226     
00227     // rotate   
00228     pcl::transformPointCloud(data_cloud, data_cloud, transformation);
00229     
00230     // accumulate incremental tf
00231     final_transformation = transformation * final_transformation;
00232 
00233     // check for convergence
00234     double linear, angular;
00235     getTfDifference(
00236       tfFromEigen(transformation), linear, angular);
00237     if (linear  < tf_epsilon_linear_ &&
00238         angular < tf_epsilon_angular_)
00239     {
00240       //printf("(%f %f) conv. at [%d] leaving loop\n", 
00241       //  linear*1000.0, angular*10.0*180.0/3.14, iteration);
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   // find n Euclidean nearest neighbors
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   // iterate over Euclidean NNs to find Mah. NN
00307   double best_mah_dist_sq = 0;
00308   int best_mah_nn_idx = -1;
00309   //int best_i = 0; // optionally print this to check how far in we found the best one
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       //best_i = i;
00330     }
00331   }
00332 
00333   if (best_mah_nn_idx != -1)
00334   {  
00335     //if (best_i != 0) printf("BEST NEIGHBOR WAS #%d\n", best_i);
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   // pre-allocate search vectors
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     // find nearest neighbor in model 
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       // **** KF update *********************************
00379 
00380       // predicted state
00381       const Vector3f& model_mean_pred = means_[mah_nn_idx];
00382       const Matrix3f& model_cov_pred  = covariances_[mah_nn_idx];
00383       
00384       // calculate measurement and cov residual
00385       Vector3f y = data_mean - model_mean_pred;
00386       Matrix3f S = data_cov + model_cov_pred;
00387 
00388       // calculate Kalman gain
00389       Matrix3f K = model_cov_pred * S.inverse();
00390       
00391       // updated state estimate (mean and cov)
00392       Vector3f model_mean_upd = model_mean_pred + K * y;
00393       Matrix3f model_cov_upd  = (I_ - K) * model_cov_pred;
00394       
00395       // update in model
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   // create markers
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     // compute eigenvectors
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   // save as OpenCV yml matrix
00498   std::string filename_yml = filename + ".yml";
00499 
00500   cv::FileStorage fs(filename_yml, cv::FileStorage::WRITE);
00501   fs << "means"       << means_;
00502   fs << "covariances" << covariances_; 
00503   fs << "model_idx"   << model_idx_;  
00504   fs << "model_size"  << model_size_;    
00505 */   
00506 
00507   // save as pcd
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 } // namespace ccny_rgbd
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends


ccny_rgbd
Author(s): Ivan Dryanovski
autogenerated on Fri Apr 12 2013 20:38:48