00001 #include <fstream>
00002
00003 #include <boost/version.hpp>
00004
00005 #include "ros/ros.h"
00006 #include "ros/console.h"
00007 #include "pointmatcher/PointMatcher.h"
00008 #include "pointmatcher/Timer.h"
00009
00010 #include "pointmatcher_ros/point_cloud.h"
00011 #include "pointmatcher_ros/transform.h"
00012 #include "pointmatcher_ros/get_params_from_server.h"
00013 #include "pointmatcher_ros/ros_logger.h"
00014
00015 #include "nav_msgs/Odometry.h"
00016 #include "tf/transform_broadcaster.h"
00017 #include "tf_conversions/tf_eigen.h"
00018 #include "tf/transform_listener.h"
00019
00020 #include <message_filters/subscriber.h>
00021 #include <message_filters/synchronizer.h>
00022 #include <message_filters/sync_policies/exact_time.h>
00023
00024 #include <interactive_markers/interactive_marker_server.h>
00025 #include <interactive_markers/menu_handler.h>
00026
00027 #include "std_msgs/String.h"
00028 #include "std_srvs/Empty.h"
00029
00030 using namespace std;
00031 using namespace PointMatcherSupport;
00032 using namespace visualization_msgs;
00033 using namespace interactive_markers;
00034
00035 class MapMaintener
00036 {
00037 typedef PointMatcher<float> PM;
00038 typedef PM::DataPoints DP;
00039 typedef PM::TransformationParameters TP;
00040
00041 ros::NodeHandle& n;
00042 ros::NodeHandle& pn;
00043
00044 message_filters::Subscriber<sensor_msgs::PointCloud2> cloud_sub;
00045 message_filters::Subscriber<nav_msgs::Odometry> odom_sub;
00046
00047 typedef message_filters::sync_policies::ExactTime<sensor_msgs::PointCloud2, nav_msgs::Odometry> MySyncPolicy;
00048 message_filters::Synchronizer<MySyncPolicy> sync;
00049
00050 ros::Subscriber scanSub;
00051 ros::Subscriber cloudSub;
00052 ros::Publisher mapPub;
00053
00054
00055
00056
00057
00058
00059 PM::ICP icp;
00060
00061 PM::DataPointsFilters inputFilters;
00062 PM::DataPointsFilters mapPreFilters;
00063 PM::DataPointsFilters mapPostFilters;
00064 unique_ptr<PM::Transformation> transformation;
00065
00066 PM::DataPoints mapPointCloud;
00067
00068
00069 string vtkFinalMapName;
00070 int inputQueueSize;
00071 const float size_x;
00072 const float size_y;
00073 const float size_z;
00074
00075
00076 boost::shared_ptr<InteractiveMarkerServer> server;
00077 MenuHandler menu_handler;
00078 bool mappingActive;
00079 bool singleScan;
00080 MenuHandler::EntryHandle h_single;
00081 MenuHandler::EntryHandle h_start;
00082 MenuHandler::EntryHandle h_pause;
00083 MenuHandler::EntryHandle h_stop;
00084 MenuHandler::EntryHandle h_save;
00085
00086
00087 tf::TransformListener tfListener;
00088
00089 public:
00090 boost::mutex publishLock;
00091 PM::TransformationParameters TObjectToMap;
00092
00093 string objectFrame;
00094 string mapFrame;
00095
00096 public:
00097 MapMaintener(ros::NodeHandle& n, ros::NodeHandle& pn);
00098 ~MapMaintener();
00099
00100 protected:
00101 void gotCloud(const sensor_msgs::PointCloud2ConstPtr& cloudMsgIn, const nav_msgs::OdometryConstPtr& odom);
00102 void processCloud(DP cloud, const TP TScannerToMap);
00103
00104 void makeMenuMarker( std::string name );
00105 void addRotAndTransCtrl(InteractiveMarker &int_marker, const double w, const double x, const double y, const double z, const std::string name);
00106 void update_tf(const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00107
00108
00109 void singleScanCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00110 void startMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00111 void pauseMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00112 void stopMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00113 void saveMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback );
00114
00115
00116
00117
00118 };
00119
00120 MapMaintener::MapMaintener(ros::NodeHandle& n, ros::NodeHandle& pn):
00121 n(n),
00122 pn(pn),
00123 cloud_sub(n, "cloud_in", 1),
00124 odom_sub(n, "icp_odom", 1),
00125 sync(MySyncPolicy(1), cloud_sub, odom_sub),
00126 transformation(PM::get().REG(Transformation).create("RigidTransformation")),
00127 vtkFinalMapName(getParam<string>("vtkFinalMapName", "finalMap_maintained.vtk")),
00128 size_x(getParam<double>("size_x", 1.0)),
00129 size_y(getParam<double>("size_y", 1.0)),
00130 size_z(getParam<double>("size_z", 1.0)),
00131 mappingActive(false),
00132 singleScan(false),
00133 tfListener(ros::Duration(30)),
00134 TObjectToMap(PM::TransformationParameters::Identity(4,4)),
00135 objectFrame(getParam<string>("object_frame", "map")),
00136 mapFrame(getParam<string>("map_frame", "map"))
00137 {
00138
00139
00140 string configFileName;
00141 if (ros::param::get("~icpConfig", configFileName))
00142 {
00143 ifstream ifs(configFileName.c_str());
00144 if (ifs.good())
00145 {
00146 icp.loadFromYaml(ifs);
00147 }
00148 else
00149 {
00150 ROS_ERROR_STREAM("Cannot load ICP config from YAML file " << configFileName);
00151 icp.setDefault();
00152 }
00153 }
00154 else
00155 {
00156 ROS_INFO_STREAM("No ICP config file given, using default");
00157 icp.setDefault();
00158 }
00159
00160 if (ros::param::get("~inputFiltersConfig", configFileName))
00161 {
00162 ifstream ifs(configFileName.c_str());
00163 if (ifs.good())
00164 {
00165 inputFilters = PM::DataPointsFilters(ifs);
00166 }
00167 else
00168 {
00169 ROS_ERROR_STREAM("Cannot load input filters config from YAML file " << configFileName);
00170 }
00171 }
00172 else
00173 {
00174 ROS_INFO_STREAM("No input filters config file given, not using these filters");
00175 }
00176
00177 if (ros::param::get("~mapPreFiltersConfig", configFileName))
00178 {
00179 ifstream ifs(configFileName.c_str());
00180 if (ifs.good())
00181 {
00182 mapPreFilters = PM::DataPointsFilters(ifs);
00183
00184
00185 PM::Parameters params;
00186 params["removeInside"] = toParam(0);
00187 params["xMin"] = toParam(-size_x/2);
00188 params["xMax"] = toParam(size_x/2);
00189 params["yMin"] = toParam(-size_y/2);
00190 params["yMax"] = toParam(size_y/2);
00191 params["zMin"] = toParam(-size_z/2);
00192 params["zMax"] = toParam(size_z/2);
00193
00194 PM::DataPointsFilter* box =
00195 PM::get().DataPointsFilterRegistrar.create("BoundingBoxDataPointsFilter", params);
00196 mapPreFilters.push_back(box);
00197
00198 }
00199 else
00200 {
00201 ROS_ERROR_STREAM("Cannot load map pre-filters config from YAML file " << configFileName);
00202 }
00203 }
00204 else
00205 {
00206 ROS_INFO_STREAM("No map pre-filters config file given, not using these filters");
00207 }
00208
00209 if (ros::param::get("~mapPostFiltersConfig", configFileName))
00210 {
00211 ifstream ifs(configFileName.c_str());
00212 if (ifs.good())
00213 {
00214 mapPostFilters = PM::DataPointsFilters(ifs);
00215 }
00216 else
00217 {
00218 ROS_ERROR_STREAM("Cannot load map post-filters config from YAML file " << configFileName);
00219 }
00220 }
00221 else
00222 {
00223 ROS_INFO_STREAM("No map post-filters config file given, not using these filters");
00224 }
00225
00226
00227 sync.registerCallback(boost::bind(&MapMaintener::gotCloud, this, _1, _2));
00228
00229
00230 mapPub = n.advertise<sensor_msgs::PointCloud2>("object_map", 2, true);
00231
00232
00233
00234
00235
00236
00237 server.reset( new InteractiveMarkerServer("MapCenter","", false) );
00238
00239 h_single = menu_handler.insert( "Record single scan", boost::bind(&MapMaintener::singleScanCallback, this, _1));
00240 h_start = menu_handler.insert( "Start object mapping", boost::bind(&MapMaintener::startMapCallback, this, _1));
00241 h_pause = menu_handler.insert( "Pause object mapping", boost::bind(&MapMaintener::pauseMapCallback, this, _1));
00242 h_stop = menu_handler.insert( "Stop/reset object mapping", boost::bind(&MapMaintener::stopMapCallback, this, _1));
00243 menu_handler.setVisible(h_stop, false);
00244 h_save = menu_handler.insert( "Save object to VTK", boost::bind(&MapMaintener::saveMapCallback, this, _1));
00245
00246 makeMenuMarker( "object_menu" );
00247 menu_handler.apply( *server, "object_menu" );
00248 menu_handler.reApply( *server);
00249 server->applyChanges();
00250
00251 }
00252
00253 MapMaintener::~MapMaintener()
00254 {
00255 server.reset();
00256
00257
00258 if (mapPointCloud.features.cols())
00259 {
00260 mapPointCloud.save(vtkFinalMapName);
00261 }
00262 }
00263
00264
00265
00266 void MapMaintener::gotCloud(const sensor_msgs::PointCloud2ConstPtr& cloudMsgIn, const nav_msgs::OdometryConstPtr& odom)
00267 {
00268
00269
00270 if(mappingActive || singleScan)
00271 {
00272
00273 DP cloud(PointMatcher_ros::rosMsgToPointMatcherCloud<float>(*cloudMsgIn));
00274
00275 TP TScannerToMap = PointMatcher_ros::odomMsgToEigenMatrix<float>(*odom);
00276 processCloud(cloud, TScannerToMap);
00277 }
00278
00279 singleScan = false;
00280 }
00281
00282
00283 void MapMaintener::processCloud(DP newPointCloud, const TP TScannerToMap)
00284 {
00285 string reason;
00286 timer t;
00287
00288
00289 const size_t goodCount(newPointCloud.features.cols());
00290 if (goodCount == 0)
00291 {
00292 ROS_ERROR("I found no good points in the cloud");
00293 return;
00294 }
00295
00296 ROS_INFO("Processing new point cloud");
00297 {
00298 timer t;
00299
00300
00301 inputFilters.apply(newPointCloud);
00302
00303 ROS_INFO_STREAM("Input filters took " << t.elapsed() << " [s]");
00304 }
00305
00306
00307
00308
00309
00310
00311 newPointCloud = transformation->compute(newPointCloud, transformation->correctParameters(TObjectToMap.inverse() * TScannerToMap));
00312
00313
00314 mapPreFilters.apply(newPointCloud);
00315
00316
00317 if(newPointCloud.features.cols() < 400)
00318 return;
00319
00320
00321 if(!mapPointCloud.features.rows())
00322 {
00323 mapPointCloud = newPointCloud;
00324 return;
00325 }
00326
00327
00328
00329 if (newPointCloud.features.rows() != mapPointCloud.features.rows())
00330 {
00331 ROS_ERROR_STREAM("Dimensionality missmatch: incoming cloud is " << newPointCloud.features.rows()-1 << " while map is " << mapPointCloud.features.rows()-1);
00332 return;
00333 }
00334
00335
00336 PM::TransformationParameters Tcorr;
00337 try
00338 {
00339 Tcorr = icp(newPointCloud, mapPointCloud);
00340 TObjectToMap = TObjectToMap * Tcorr.inverse();
00341 }
00342 catch (PM::ConvergenceError error)
00343 {
00344 ROS_WARN_STREAM("ICP failed to converge: " << error.what());
00345 return;
00346 }
00347
00348
00349 const double estimatedOverlap = icp.errorMinimizer->getOverlap();
00350 if(estimatedOverlap < 0.40)
00351 {
00352 ROS_WARN_STREAM("Estimated overlap too small: " << estimatedOverlap);
00353 return;
00354 }
00355
00356 ROS_DEBUG_STREAM("Tcorr:\n" << Tcorr);
00357
00358 cout << "Tcorr: " << endl << Tcorr << endl;
00359 newPointCloud = transformation->compute(newPointCloud, Tcorr);
00360
00361 mapPointCloud.concatenate(newPointCloud);
00362
00363
00364 mapPostFilters.apply(mapPointCloud);
00365
00366 ROS_INFO_STREAM("New map available (" << mapPointCloud.features.cols() << " pts), update took " << t.elapsed() << " [s]");
00367
00368
00369
00370 if (mapPub.getNumSubscribers())
00371 mapPub.publish(PointMatcher_ros::pointMatcherCloudToRosMsg<float>(mapPointCloud, objectFrame, ros::Time::now()));
00372
00373 ROS_INFO_STREAM("Total map merging: " << t.elapsed() << " [s]");
00374
00375
00376
00377 }
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418 void MapMaintener::makeMenuMarker( std::string name )
00419 {
00420 InteractiveMarker int_marker;
00421
00422
00423
00424 int_marker.header.frame_id = mapFrame;
00425 int_marker.pose.position.x = 0.0;
00426 int_marker.pose.position.y = 0.0;
00427 int_marker.pose.position.z = 0.0;
00428
00429 int_marker.scale = 1;
00430
00431
00432 int_marker.name = name;
00433
00434
00435
00436 addRotAndTransCtrl(int_marker, 1, 1, 0, 0, "x");
00437 addRotAndTransCtrl(int_marker, 1, 0, 1, 0, "z");
00438 addRotAndTransCtrl(int_marker, 1, 0, 0, 1, "y");
00439
00440
00441 Marker grayBox;
00442 grayBox.type = Marker::CUBE;
00443 grayBox.scale.x = size_x;
00444 grayBox.scale.y = size_y;
00445 grayBox.scale.z = size_z;
00446 grayBox.color.r = 0.5;
00447 grayBox.color.g = 0.5;
00448 grayBox.color.b = 0.5;
00449 grayBox.color.a = 0.5;
00450
00451 InteractiveMarkerControl control;
00452 control = InteractiveMarkerControl();
00453 control.markers.push_back(grayBox);
00454 control.interaction_mode = InteractiveMarkerControl::BUTTON;
00455 int_marker.controls.push_back(control);
00456 int_marker.scale = std::max(std::max(size_x, size_y), size_z);
00457
00458 server->insert( int_marker );
00459 server->setCallback(int_marker.name, boost::bind(&MapMaintener::update_tf, this, _1), visualization_msgs::InteractiveMarkerFeedback::POSE_UPDATE);
00460 }
00461
00462 void MapMaintener::addRotAndTransCtrl(InteractiveMarker &int_marker, const double w, const double x, const double y, const double z, const std::string name)
00463 {
00464 InteractiveMarkerControl control;
00465 control.orientation.w = w;
00466 control.orientation.x = x;
00467 control.orientation.y = y;
00468 control.orientation.z = z;
00469
00470 control.interaction_mode = InteractiveMarkerControl::MOVE_ROTATE;
00471 control.name = "rotate_" + name;
00472
00473 int_marker.controls.push_back( control );
00474
00475 }
00476
00477
00478 void MapMaintener::update_tf(const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00479 {
00480
00481
00482
00483 if(mapPointCloud.features.cols() == 0)
00484 {
00485 nav_msgs::Odometry odom;
00486 odom.pose.pose = feedback->pose;
00487 publishLock.lock();
00488 TObjectToMap = PointMatcher_ros::odomMsgToEigenMatrix<float>(odom);
00489 publishLock.unlock();
00490
00491
00492 menu_handler.reApply( *server);
00493 server->applyChanges();
00494 }
00495 else
00496 {
00497
00498 menu_handler.reApply( *server);
00499 server->applyChanges();
00500 }
00501 }
00502
00503 void MapMaintener::singleScanCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00504 {
00505 singleScan = true;
00506 }
00507
00508 void MapMaintener::startMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00509 {
00510 menu_handler.setVisible(h_start, false);
00511 menu_handler.setVisible(h_pause, true);
00512 menu_handler.setVisible(h_stop, true);
00513 menu_handler.reApply( *server);
00514 server->applyChanges();
00515 mappingActive = true;
00516 }
00517
00518 void MapMaintener::pauseMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00519 {
00520 menu_handler.setVisible(h_start, true);
00521 menu_handler.setVisible(h_pause, false);
00522 menu_handler.setVisible(h_stop, true);
00523 menu_handler.reApply( *server);
00524 server->applyChanges();
00525 mappingActive = false;
00526 }
00527
00528 void MapMaintener::stopMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00529 {
00530 menu_handler.setVisible(h_start, true);
00531 menu_handler.setVisible(h_pause, false);
00532 menu_handler.setVisible(h_stop, false);
00533 menu_handler.reApply( *server);
00534 server->applyChanges();
00535 mappingActive = false;
00536
00537 mapPointCloud = PM::DataPoints();
00538 update_tf(feedback);
00539 }
00540
00541 void MapMaintener::saveMapCallback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
00542 {
00543
00544 if (mapPointCloud.features.cols())
00545 {
00546 mapPointCloud.save(vtkFinalMapName);
00547 }
00548 }
00549
00550
00551 int main(int argc, char **argv)
00552 {
00553 ros::init(argc, argv, "map_maintainer");
00554 ros::NodeHandle n;
00555 ros::NodeHandle pn("~");
00556 MapMaintener map(n, pn);
00557
00558 tf::TransformBroadcaster tfBroadcaster;
00559
00560 ros::Rate r(50);
00561 while(ros::ok())
00562 {
00563 map.publishLock.lock();
00564 tfBroadcaster.sendTransform(PointMatcher_ros::eigenMatrixToStampedTransform<float>(map.TObjectToMap, map.mapFrame, map.objectFrame, ros::Time::now()));
00565 map.publishLock.unlock();
00566 ros::spinOnce();
00567 r.sleep();
00568 }
00569
00570 return 0;
00571 }