00001
00024 #include "ccny_rgbd/apps/visual_odometry.h"
00025 #include "rgbdtools/rgbdtools.h"
00026 #include "tf_conversions/tf_eigen.h"
00027 namespace ccny_rgbd {
00028
00029 VisualOdometry::VisualOdometry(
00030 const ros::NodeHandle& nh,
00031 const ros::NodeHandle& nh_private):
00032 nh_(nh),
00033 nh_private_(nh_private),
00034 initialized_(false),
00035 frame_count_(0)
00036 {
00037 ROS_INFO("Starting RGBD Visual Odometry");
00038
00039
00040
00041 initParams();
00042
00043
00044
00045 f2b_.setIdentity();
00046
00047
00048
00049 odom_publisher_ = nh_.advertise<OdomMsg>(
00050 "vo", queue_size_);
00051 pose_stamped_publisher_ = nh_.advertise<geometry_msgs::PoseStamped>(
00052 "pose", queue_size_);
00053 path_pub_ = nh_.advertise<PathMsg>(
00054 "path", queue_size_);
00055
00056 feature_cloud_publisher_ = nh_.advertise<PointCloudFeature>(
00057 "feature/cloud", 1);
00058 feature_cov_publisher_ = nh_.advertise<visualization_msgs::Marker>(
00059 "feature/covariances", 1);
00060
00061 model_cloud_publisher_ = nh_.advertise<PointCloudFeature>(
00062 "model/cloud", 1);
00063 model_cov_publisher_ = nh_.advertise<visualization_msgs::Marker>(
00064 "model/covariances", 1);
00065 reset_pos_ = nh_.advertiseService(
00066 "reset_pos_", &VisualOdometry::resetposSrvCallback, this);
00067
00068
00069 ImageTransport rgb_it(nh_);
00070 ImageTransport depth_it(nh_);
00071
00072 sub_rgb_.subscribe(rgb_it, "/camera/rgb/image_rect_color", queue_size_);
00073 sub_depth_.subscribe(depth_it, "/camera/depth_registered/image_rect_raw", queue_size_);
00074 sub_info_.subscribe(nh_, "/camera/rgb/camera_info", queue_size_);
00075
00076
00077 sync_.reset(new RGBDSynchronizer3(
00078 RGBDSyncPolicy3(queue_size_), sub_rgb_, sub_depth_, sub_info_));
00079
00080 sync_->registerCallback(boost::bind(&VisualOdometry::RGBDCallback, this, _1, _2, _3));
00081 }
00082
00083 VisualOdometry::~VisualOdometry()
00084 {
00085 fclose(diagnostics_file_);
00086 ROS_INFO("Destroying RGBD Visual Odometry");
00087 }
00088
00089 bool VisualOdometry::resetposSrvCallback(
00090 Save::Request& request,
00091 Save::Response& response)
00092 {
00093
00094 const std::string& new_tr = request.filename;
00095
00096 double m00,m01,m02,m03,m10,m11,m12,m13,m20,m21,m22,m23,m30,m31,m32,m33;
00097 int res;
00098
00099 res = sscanf(new_tr.c_str(), "%lg %lg %lg %lg\n%lg %lg %lg %lg\n%lg %lg %lg %lg\n%lg %lg %lg %lg\n)",
00100 &m00,&m01,&m02,&m03,&m10,&m11,&m12,&m13,&m20,&m21,&m22,&m23,&m30,&m31,&m32,&m33);
00101 if (res != 16)
00102 {
00103 res = sscanf(new_tr.c_str(), "%lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg %lg)",
00104 &m00,&m01,&m02,&m03,&m10,&m11,&m12,&m13,&m20,&m21,&m22,&m23,&m30,&m31,&m32,&m33);
00105 if (res != 16)
00106 {
00107 return false;
00108 }
00109 }
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128 double yaw,pitch,roll;
00129 tf::Matrix3x3 rot(m00,m01,m02,m10,m11,m12,m20,m21,m22);
00130 tf::Matrix3x3 correction(0,0,1,-1,0,0,0,-1,0);
00131 rot = rot*correction.inverse();
00132 rot.getEulerYPR(yaw,pitch,roll);
00133
00134 Eigen::Affine3d pose;
00135 pose.setIdentity();
00136 pose = Eigen::Translation<double,3>(m03,m13,m23)*
00137 Eigen::AngleAxis<double>(yaw,Eigen::Vector3d::UnitZ()) *
00138 Eigen::AngleAxis<double>(pitch,Eigen::Vector3d::UnitY()) *
00139 Eigen::AngleAxis<double>(roll,Eigen::Vector3d::UnitX());
00140
00141 tf::TransformEigenToTF(pose,f2b_);
00142 motion_estimation_.resetModel();
00143
00144
00145
00146 return true;
00147 }
00148
00149 void VisualOdometry::initParams()
00150 {
00151 if (!nh_private_.getParam ("publish_tf", publish_tf_))
00152 publish_tf_ = true;
00153 if (!nh_private_.getParam ("publish_path", publish_path_))
00154 publish_path_ = true;
00155 if (!nh_private_.getParam ("publish_odom", publish_odom_))
00156 publish_odom_ = true;
00157 if (!nh_private_.getParam ("publish_pose", publish_pose_))
00158 publish_pose_ = true;
00159 if (!nh_private_.getParam ("fixed_frame", fixed_frame_))
00160 fixed_frame_ = "/odom";
00161 if (!nh_private_.getParam ("base_frame", base_frame_))
00162 base_frame_ = "/camera_link";
00163 if (!nh_private_.getParam ("queue_size", queue_size_))
00164 queue_size_ = 5;
00165
00166
00167
00168 if (!nh_private_.getParam ("feature/publish_feature_cloud", publish_feature_cloud_))
00169 publish_feature_cloud_ = false;
00170 if (!nh_private_.getParam ("feature/publish_feature_covariances", publish_feature_cov_))
00171 publish_feature_cov_ = false;
00172 if (!nh_private_.getParam ("feature/detector_type", detector_type_))
00173 detector_type_ = "GFT";
00174
00175 resetDetector();
00176
00177 int smooth;
00178 double max_range, max_stdev;
00179
00180 if (!nh_private_.getParam ("feature/smooth", smooth))
00181 smooth = 0;
00182 if (!nh_private_.getParam ("feature/max_range", max_range))
00183 max_range = 25.5;
00184 if (!nh_private_.getParam ("feature/max_stdev", max_stdev))
00185 max_stdev = 20.03;
00186
00187 feature_detector_->setSmooth(smooth);
00188 feature_detector_->setMaxRange(max_range);
00189 feature_detector_->setMaxStDev(max_stdev);
00190
00191
00192
00193 configureMotionEstimation();
00194
00195
00196
00197 if (!nh_private_.getParam("verbose", verbose_))
00198 verbose_ = true;
00199 if (!nh_private_.getParam("save_diagnostics", save_diagnostics_))
00200 save_diagnostics_ = false;
00201 if (!nh_private_.getParam("diagnostics_file_name", diagnostics_file_name_))
00202 diagnostics_file_name_ = "diagnostics.csv";
00203
00204 if(save_diagnostics_)
00205 {
00206 diagnostics_file_ = fopen(diagnostics_file_name_.c_str(), "w");
00207
00208 if (diagnostics_file_ == NULL)
00209 {
00210 ROS_ERROR("Can't create diagnostic file %s\n", diagnostics_file_name_.c_str());
00211 return;
00212 }
00213
00214
00215 fprintf(diagnostics_file_, "%s, %s, %s, %s, %s, %s, %s, %s\n",
00216 "Frame id",
00217 "Frame dur.",
00218 "All features", "Valid features",
00219 "Feat extr. dur.",
00220 "Model points", "Registration dur.",
00221 "Total dur.");
00222 }
00223 }
00224
00225 void VisualOdometry::configureMotionEstimation()
00226 {
00227 int motion_constraint;
00228
00229 if (!nh_private_.getParam ("reg/motion_constraint", motion_constraint))
00230 motion_constraint = 0;
00231
00232 motion_estimation_.setMotionConstraint(motion_constraint);
00233
00234 double tf_epsilon_linear;
00235 double tf_epsilon_angular;
00236 int max_iterations;
00237 int min_correspondences;
00238 int max_model_size;
00239 double max_corresp_dist_eucl;
00240 double max_assoc_dist_mah;
00241 int n_nearest_neighbors;
00242
00243 if (!nh_private_.getParam ("reg/ICPProbModel/tf_epsilon_linear", tf_epsilon_linear))
00244 tf_epsilon_linear = 1e-4;
00245 if (!nh_private_.getParam ("reg/ICPProbModel/tf_epsilon_angular", tf_epsilon_angular))
00246 tf_epsilon_angular = 1.7e-3;
00247 if (!nh_private_.getParam ("reg/ICPProbModel/max_iterations", max_iterations))
00248 max_iterations = 10;
00249 if (!nh_private_.getParam ("reg/ICPProbModel/min_correspondences", min_correspondences))
00250 min_correspondences = 15;
00251 if (!nh_private_.getParam ("reg/ICPProbModel/max_model_size", max_model_size))
00252 max_model_size = 3000;
00253 if (!nh_private_.getParam ("reg/ICPProbModel/max_corresp_dist_eucl", max_corresp_dist_eucl))
00254 max_corresp_dist_eucl = 0.15;
00255 if (!nh_private_.getParam ("reg/ICPProbModel/max_assoc_dist_mah", max_assoc_dist_mah))
00256 max_assoc_dist_mah = 10.0;
00257 if (!nh_private_.getParam ("reg/ICPProbModel/n_nearest_neighbors", n_nearest_neighbors))
00258 n_nearest_neighbors = 4;
00259
00260 if (!nh_private_.getParam ("reg/ICPProbModel/publish_model_cloud", publish_model_cloud_))
00261 publish_model_cloud_ = false;
00262 if (!nh_private_.getParam ("reg/ICPProbModel/publish_model_covariances", publish_model_cov_))
00263 publish_model_cov_ = false;
00264
00265 motion_estimation_.setTfEpsilonLinear(tf_epsilon_linear);
00266 motion_estimation_.setTfEpsilonAngular(tf_epsilon_angular);
00267 motion_estimation_.setMaxIterations(max_iterations);
00268 motion_estimation_.setMinCorrespondences(min_correspondences);
00269 motion_estimation_.setMaxModelSize(max_model_size);
00270 motion_estimation_.setMaxCorrespondenceDistEuclidean(max_corresp_dist_eucl);
00271 motion_estimation_.setMaxAssociationDistMahalanobis(max_assoc_dist_mah);
00272 motion_estimation_.setNNearestNeighbors(n_nearest_neighbors);
00273 }
00274
00275 void VisualOdometry::resetDetector()
00276 {
00277 gft_config_server_.reset();
00278 star_config_server_.reset();
00279 orb_config_server_.reset();
00280 surf_config_server_.reset();
00281
00282 if (detector_type_ == "ORB")
00283 {
00284 ROS_INFO("Creating ORB detector");
00285 feature_detector_.reset(new rgbdtools::OrbDetector());
00286 orb_config_server_.reset(new
00287 OrbDetectorConfigServer(ros::NodeHandle(nh_private_, "feature/ORB")));
00288
00289
00290 OrbDetectorConfigServer::CallbackType f = boost::bind(
00291 &VisualOdometry::orbReconfigCallback, this, _1, _2);
00292 orb_config_server_->setCallback(f);
00293 }
00294 else if (detector_type_ == "SURF")
00295 {
00296 ROS_INFO("Creating SURF detector");
00297 feature_detector_.reset(new rgbdtools::SurfDetector());
00298 surf_config_server_.reset(new
00299 SurfDetectorConfigServer(ros::NodeHandle(nh_private_, "feature/SURF")));
00300
00301
00302 SurfDetectorConfigServer::CallbackType f = boost::bind(
00303 &VisualOdometry::surfReconfigCallback, this, _1, _2);
00304 surf_config_server_->setCallback(f);
00305 }
00306 else if (detector_type_ == "GFT")
00307 {
00308 ROS_INFO("Creating GFT detector");
00309 feature_detector_.reset(new rgbdtools::GftDetector());
00310 gft_config_server_.reset(new
00311 GftDetectorConfigServer(ros::NodeHandle(nh_private_, "feature/GFT")));
00312
00313
00314 GftDetectorConfigServer::CallbackType f = boost::bind(
00315 &VisualOdometry::gftReconfigCallback, this, _1, _2);
00316 gft_config_server_->setCallback(f);
00317 }
00318 else if (detector_type_ == "STAR")
00319 {
00320 ROS_INFO("Creating STAR detector");
00321 feature_detector_.reset(new rgbdtools::StarDetector());
00322 star_config_server_.reset(new
00323 StarDetectorConfigServer(ros::NodeHandle(nh_private_, "feature/STAR")));
00324
00325
00326 StarDetectorConfigServer::CallbackType f = boost::bind(
00327 &VisualOdometry::starReconfigCallback, this, _1, _2);
00328 star_config_server_->setCallback(f);
00329 }
00330 else
00331 {
00332 ROS_FATAL("%s is not a valid detector type! Using GFT", detector_type_.c_str());
00333 feature_detector_.reset(new rgbdtools::GftDetector());
00334 gft_config_server_.reset(new
00335 GftDetectorConfigServer(ros::NodeHandle(nh_private_, "feature/GFT")));
00336
00337
00338 GftDetectorConfigServer::CallbackType f = boost::bind(
00339 &VisualOdometry::gftReconfigCallback, this, _1, _2);
00340 gft_config_server_->setCallback(f);
00341 }
00342 }
00343
00344 void VisualOdometry::RGBDCallback(
00345 const ImageMsg::ConstPtr& rgb_msg,
00346 const ImageMsg::ConstPtr& depth_msg,
00347 const CameraInfoMsg::ConstPtr& info_msg)
00348 {
00349 ros::WallTime start = ros::WallTime::now();
00350
00351
00352
00353 if (!initialized_)
00354 {
00355 initialized_ = getBaseToCameraTf(rgb_msg->header);
00356 init_time_ = rgb_msg->header.stamp;
00357 if (!initialized_) return;
00358
00359 motion_estimation_.setBaseToCameraTf(eigenAffineFromTf(b2c_));
00360 }
00361
00362
00363
00364 ros::WallTime start_frame = ros::WallTime::now();
00365 rgbdtools::RGBDFrame frame;
00366 createRGBDFrameFromROSMessages(rgb_msg, depth_msg, info_msg, frame);
00367 ros::WallTime end_frame = ros::WallTime::now();
00368
00369
00370
00371 ros::WallTime start_features = ros::WallTime::now();
00372 feature_detector_->findFeatures(frame);
00373 ros::WallTime end_features = ros::WallTime::now();
00374
00375
00376
00377 ros::WallTime start_reg = ros::WallTime::now();
00378 AffineTransform m = motion_estimation_.getMotionEstimation(frame);
00379 tf::Transform motion = tfFromEigenAffine(m);
00380 f2b_ = motion * f2b_;
00381 ros::WallTime end_reg = ros::WallTime::now();
00382
00383
00384
00385 if (publish_tf_) publishTf(rgb_msg->header);
00386 if (publish_odom_) publishOdom(rgb_msg->header);
00387 if (publish_path_) publishPath(rgb_msg->header);
00388 if (publish_pose_) publishPoseStamped(rgb_msg->header);
00389
00390 if (publish_feature_cloud_) publishFeatureCloud(frame);
00391 if (publish_feature_cov_) publishFeatureCovariances(frame);
00392
00393 if (publish_model_cloud_) publishModelCloud();
00394 if (publish_model_cov_) publishModelCovariances();
00395
00396
00397
00398 ros::WallTime end = ros::WallTime::now();
00399
00400 frame_count_++;
00401
00402 int n_features = frame.keypoints.size();
00403 int n_valid_features = frame.n_valid_keypoints;
00404 int n_model_pts = motion_estimation_.getModelSize();
00405
00406 double d_frame = 1000.0 * (end_frame - start_frame ).toSec();
00407 double d_features = 1000.0 * (end_features - start_features).toSec();
00408 double d_reg = 1000.0 * (end_reg - start_reg ).toSec();
00409 double d_total = 1000.0 * (end - start ).toSec();
00410
00411 diagnostics(n_features, n_valid_features, n_model_pts,
00412 d_frame, d_features, d_reg, d_total);
00413 }
00414
00415 void VisualOdometry::publishTf(const std_msgs::Header& header)
00416 {
00417 tf::StampedTransform transform_msg(
00418 f2b_, header.stamp, fixed_frame_, base_frame_);
00419 tf_broadcaster_.sendTransform (transform_msg);
00420 }
00421
00422 void VisualOdometry::publishOdom(const std_msgs::Header& header)
00423 {
00424 OdomMsg odom;
00425 odom.header.stamp = header.stamp;
00426 odom.header.frame_id = fixed_frame_;
00427 tf::poseTFToMsg(f2b_, odom.pose.pose);
00428 odom_publisher_.publish(odom);
00429 }
00430
00431 void VisualOdometry::publishPoseStamped(const std_msgs::Header& header)
00432 {
00433 geometry_msgs::PoseStamped::Ptr pose_stamped_msg;
00434 pose_stamped_msg = boost::make_shared<geometry_msgs::PoseStamped>();
00435 pose_stamped_msg->header.stamp = header.stamp;
00436 pose_stamped_msg->header.frame_id = fixed_frame_;
00437 tf::poseTFToMsg(f2b_, pose_stamped_msg->pose);
00438 pose_stamped_publisher_.publish(pose_stamped_msg);
00439 }
00440
00441
00442 void VisualOdometry::publishPath(const std_msgs::Header& header)
00443 {
00444 path_msg_.header.stamp = header.stamp;
00445 path_msg_.header.frame_id = fixed_frame_;
00446
00447 geometry_msgs::PoseStamped pose_stamped;
00448 pose_stamped.header.stamp = header.stamp;
00449 pose_stamped.header.frame_id = fixed_frame_;
00450 tf::poseTFToMsg(f2b_, pose_stamped.pose);
00451
00452 path_msg_.poses.push_back(pose_stamped);
00453 path_pub_.publish(path_msg_);
00454 }
00455
00456 bool VisualOdometry::getBaseToCameraTf(const std_msgs::Header& header)
00457 {
00458 tf::StampedTransform tf_m;
00459
00460 try
00461 {
00462 tf_listener_.waitForTransform(
00463 base_frame_, header.frame_id, header.stamp, ros::Duration(1.0));
00464 tf_listener_.lookupTransform (
00465 base_frame_, header.frame_id, header.stamp, tf_m);
00466 }
00467 catch (tf::TransformException& ex)
00468 {
00469 ROS_WARN("Base to camera transform unavailable %s", ex.what());
00470 return false;
00471 }
00472
00473 b2c_ = tf_m;
00474
00475 return true;
00476 }
00477
00478 void VisualOdometry::gftReconfigCallback(GftDetectorConfig& config, uint32_t level)
00479 {
00480 rgbdtools::GftDetectorPtr gft_detector =
00481 boost::static_pointer_cast<rgbdtools::GftDetector>(feature_detector_);
00482
00483 gft_detector->setNFeatures(config.n_features);
00484 gft_detector->setMinDistance(config.min_distance);
00485 }
00486
00487 void VisualOdometry::starReconfigCallback(StarDetectorConfig& config, uint32_t level)
00488 {
00489 rgbdtools::StarDetectorPtr star_detector =
00490 boost::static_pointer_cast<rgbdtools::StarDetector>(feature_detector_);
00491
00492 star_detector->setThreshold(config.threshold);
00493 star_detector->setMinDistance(config.min_distance);
00494 }
00495
00496 void VisualOdometry::surfReconfigCallback(SurfDetectorConfig& config, uint32_t level)
00497 {
00498 rgbdtools::SurfDetectorPtr surf_detector =
00499 boost::static_pointer_cast<rgbdtools::SurfDetector>(feature_detector_);
00500
00501 surf_detector->setThreshold(config.threshold);
00502 }
00503
00504 void VisualOdometry::orbReconfigCallback(OrbDetectorConfig& config, uint32_t level)
00505 {
00506 rgbdtools::OrbDetectorPtr orb_detector =
00507 boost::static_pointer_cast<rgbdtools::OrbDetector>(feature_detector_);
00508
00509 orb_detector->setThreshold(config.threshold);
00510 orb_detector->setNFeatures(config.n_features);
00511 }
00512
00513 void VisualOdometry::diagnostics(
00514 int n_features, int n_valid_features, int n_model_pts,
00515 double d_frame, double d_features, double d_reg, double d_total)
00516 {
00517 if(save_diagnostics_ && diagnostics_file_ != NULL)
00518 {
00519
00520 fprintf(diagnostics_file_, "%d, %2.1f, %d, %3.1f, %d, %4.1f, %4.1f\n",
00521 frame_count_,
00522 d_frame,
00523 n_valid_features, d_features,
00524 n_model_pts, d_reg,
00525 d_total);
00526 }
00527 if (verbose_)
00528 {
00529
00530 ROS_INFO("[VO %d] %s[%d]: %.1f Reg[%d]: %.1f TOT: %.1f\n",
00531 frame_count_,
00532 detector_type_.c_str(), n_valid_features, d_features,
00533 n_model_pts, d_reg,
00534 d_total);
00535 }
00536
00537 return;
00538 }
00539
00540 void VisualOdometry::publishFeatureCloud(rgbdtools::RGBDFrame& frame)
00541 {
00542 PointCloudFeature feature_cloud;
00543 frame.constructFeaturePointCloud(feature_cloud);
00544 feature_cloud_publisher_.publish(feature_cloud);
00545 }
00546
00547 void VisualOdometry::publishFeatureCovariances(rgbdtools::RGBDFrame& frame)
00548 {
00550 }
00551
00552 void VisualOdometry::publishModelCloud()
00553 {
00554 PointCloudFeature::Ptr model_cloud_ptr = motion_estimation_.getModel();
00555 model_cloud_ptr->header.frame_id = fixed_frame_;
00556 model_cloud_publisher_.publish(model_cloud_ptr);
00557 }
00558
00559 void VisualOdometry::publishModelCovariances()
00560 {
00562 }
00563
00564 }