$search
00001 /* 00002 * Copyright (c) 2008, Willow Garage, Inc. 00003 * All rights reserved. 00004 * 00005 * Redistribution and use in source and binary forms, with or without 00006 * modification, are permitted provided that the following conditions are met: 00007 * 00008 * * Redistributions of source code must retain the above copyright 00009 * notice, this list of conditions and the following disclaimer. 00010 * * Redistributions in binary form must reproduce the above copyright 00011 * notice, this list of conditions and the following disclaimer in the 00012 * documentation and/or other materials provided with the distribution. 00013 * * Neither the name of the Willow Garage, Inc. nor the names of its 00014 * contributors may be used to endorse or promote products derived from 00015 * this software without specific prior written permission. 00016 * 00017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00018 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 00021 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00022 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00023 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00024 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00025 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00026 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00027 * POSSIBILITY OF SUCH DAMAGE. 00028 * 00029 */ 00030 00069 #include <flirtlib_ros/localization_monitor.h> 00070 #include <flirtlib_ros/conversions.h> 00071 #include <flirtlib_ros/common.h> 00072 #include <flirtlib_ros/ExecutiveState.h> 00073 #include <boost/thread.hpp> 00074 #include <ros/ros.h> 00075 #include <tf/transform_listener.h> 00076 #include <tf/exceptions.h> 00077 #include <geometry_msgs/PoseArray.h> 00078 #include <geometry_msgs/PoseWithCovarianceStamped.h> 00079 #include <mongo_ros/message_collection.h> 00080 #include <visualization_msgs/Marker.h> 00081 #include <move_base_msgs/MoveBaseActionResult.h> 00082 #include <std_srvs/Empty.h> 00083 00084 00085 namespace flirtlib_ros 00086 { 00087 00088 namespace sm=sensor_msgs; 00089 namespace gm=geometry_msgs; 00090 namespace nm=nav_msgs; 00091 namespace mr=mongo_ros; 00092 namespace vm=visualization_msgs; 00093 namespace mbm=move_base_msgs; 00094 00095 using std::string; 00096 using std::vector; 00097 using boost::format; 00098 using boost::shared_ptr; 00099 00100 typedef boost::mutex::scoped_lock Lock; 00101 typedef mr::MessageWithMetadata<RefScanRos>::ConstPtr DBScan; 00102 typedef std::pair<InterestPoint*, InterestPoint*> Correspondence; 00103 typedef vector<Correspondence> Correspondences; 00104 typedef vector<RefScan> RefScans; 00105 typedef mr::MessageWithMetadata<nm::OccupancyGrid> MapWithMetadata; 00106 00107 class Node 00108 { 00109 public: 00110 00111 Node (); 00112 00113 // Update given new laser scan. 00114 void scanCB (sm::LaserScan::ConstPtr scan); 00115 00116 // Save the map the first time it comes in 00117 void mapCB (const nm::OccupancyGrid& g); 00118 00119 // Used to count successful navigations, as an indicator that we're 00120 // well localized. 00121 void navCB (const mbm::MoveBaseActionResult& m); 00122 00123 // Publish the executive state periodically 00124 void publishExecState (const ros::TimerEvent& e); 00125 00126 // Reset the executive state 00127 bool resetExecState (std_srvs::Empty::Request& req, 00128 std_srvs::Empty::Response& resp); 00129 00130 private: 00131 00132 // Update given that we're well localized 00133 void updateLocalized (sm::LaserScan::ConstPtr scan, const gm::Pose& p); 00134 00135 // Update given that we're not well localized 00136 void updateUnlocalized (sm::LaserScan::ConstPtr scan); 00137 00138 // Republish the set of reference scan poses. 00139 void publishRefScans () const; 00140 00141 // Get saved occupancy grid from db 00142 MapWithMetadata::ConstPtr getSavedGrid() const; 00143 00144 // Compensate for base movement between when the scan was taken and now 00145 tf::Transform compensateOdometry (const tf::Pose& sensor_pose, 00146 const string& frame, const ros::Time& t1, 00147 const ros::Time& t2); 00148 00149 /************************************************************ 00150 * Needed during init 00151 ************************************************************/ 00152 00153 boost::mutex mutex_; 00154 ros::NodeHandle nh_; 00155 00156 /************************************************************ 00157 * Parameters 00158 ************************************************************/ 00159 00160 // Minimum number of feature matches needed for succesful scan match 00161 unsigned min_num_matches_; 00162 00163 // Minimum number of navigations to happen before we'll save new scans 00164 unsigned min_successful_navs_; 00165 00166 // Name of the database 00167 string db_name_; 00168 00169 // Host where the db is running 00170 string db_host_; 00171 00172 // Rate at which we'll consider new scans 00173 ros::Rate update_rate_; 00174 00175 // Bound on badness to consider the robot well localized 00176 double badness_threshold_; 00177 00178 // Odometric frame 00179 string odom_frame_; 00180 00181 // Whether we continually publish localizations on initialpose 00182 // (if false, we'll just publish one until reset) 00183 bool continual_publish_; 00184 00185 /************************************************************ 00186 * Mutable state 00187 ************************************************************/ 00188 00189 // Local copy of db scans. 00190 vector<RefScan> ref_scans_; 00191 00192 // Offset between laser and base frames 00193 tf::Transform laser_offset_; 00194 00195 // Will we publish a localization next time we find a good match 00196 bool publishing_loc_; 00197 00198 // How many successful navigations have we observed 00199 unsigned successful_navs_; 00200 00201 // The last measurement of localization badness 00202 double localization_badness_; 00203 00204 /************************************************************ 00205 * Associated objects 00206 ************************************************************/ 00207 00208 // Flirtlib detector and descriptor 00209 FlirtlibFeatures features_; 00210 00211 // Evaluates localization badness based on the scan and the static map 00212 shared_ptr<ScanPoseEvaluator> evaluator_; 00213 00214 // Db collection of scans 00215 mr::MessageCollection<RefScanRos> scans_; 00216 00217 tf::TransformListener tf_; 00218 ros::Subscriber scan_sub_; 00219 ros::Subscriber map_sub_; 00220 ros::Publisher marker_pub_; 00221 ros::Publisher ref_scan_pose_pub_; 00222 ros::Publisher pose_est_pub_; 00223 ros::Publisher match_pose_pub_; 00224 ros::Publisher exec_state_pub_; 00225 ros::Publisher pose_pub_; 00226 ros::Subscriber nav_sub_; 00227 ros::NodeHandle pnh_; 00228 ros::ServiceServer reset_srv_; 00229 ros::Timer pub_timer_; 00230 }; 00231 00232 // Reset to the executive state upon startup 00233 bool Node::resetExecState (std_srvs::Empty::Request& req, 00234 std_srvs::Empty::Response& resp) 00235 { 00236 Lock l(mutex_); 00237 publishing_loc_ = true; 00238 successful_navs_ = 0; 00239 localization_badness_ = -1; 00240 return true; 00241 } 00242 00243 00244 // Constructor sets up ros comms and flirtlib objects, loads scans from db, 00245 // and figures out the offset between the base and the laser frames 00246 Node::Node () : 00247 min_num_matches_(getPrivateParam<int>("min_num_matches", 8)), 00248 min_successful_navs_(getPrivateParam<int>("min_successful_navs", 1)), 00249 db_name_(getPrivateParam<string>("db_name", "flirtlib_place_rec")), 00250 db_host_(getPrivateParam<string>("db_host", "localhost")), 00251 update_rate_(getPrivateParam<double>("update_rate", 1.0)), 00252 badness_threshold_(0.25), 00253 odom_frame_(getPrivateParam<string>("odom_frame", "odom_combined")), 00254 continual_publish_(getPrivateParam<bool>("continual_publish", false)), 00255 publishing_loc_(true), 00256 successful_navs_(0), localization_badness_(-1), 00257 features_(ros::NodeHandle("~")), scans_(db_name_, "scans", db_host_), 00258 scan_sub_(nh_.subscribe("base_scan", 10, &Node::scanCB, this)), 00259 map_sub_(nh_.subscribe("map", 10, &Node::mapCB, this)), 00260 marker_pub_(nh_.advertise<vm::Marker>("visualization_marker", 10)), 00261 ref_scan_pose_pub_(nh_.advertise<gm::PoseArray>("ref_scan_poses", 10, true)), 00262 pose_est_pub_(nh_.advertise<gm::PoseStamped>("pose_estimate", 1)), 00263 match_pose_pub_(nh_.advertise<gm::PoseArray>("match_poses", 1)), 00264 exec_state_pub_(nh_.advertise<ExecutiveState>("startup_loc_state", 1)), 00265 pose_pub_(nh_.advertise<gm::PoseWithCovarianceStamped>("initialpose", 1)), 00266 nav_sub_(nh_.subscribe("move_base/result", 1, &Node::navCB, this)), pnh_("~"), 00267 reset_srv_(pnh_.advertiseService("reset_state", &Node::resetExecState, this)), 00268 pub_timer_(nh_.createTimer(ros::Duration(1.0), &Node::publishExecState, this)) 00269 { 00270 ROS_DEBUG_NAMED("init", "Waiting for laser offset"); 00271 laser_offset_ = getLaserOffset(tf_); 00272 00273 ROS_DEBUG_NAMED("init", "Loading scans from db"); 00274 BOOST_FOREACH (const mr::MessageWithMetadata<RefScanRos>::ConstPtr m, 00275 scans_.queryResults(mr::Query(), false)) 00276 ref_scans_.push_back(fromRos(*m)); 00277 publishRefScans(); 00278 ROS_INFO("Localization monitor initialized with %zu scans", ref_scans_.size()); 00279 } 00280 00281 // Publish the executive state 00282 void Node::publishExecState (const ros::TimerEvent& e) 00283 { 00284 Lock l(mutex_); 00285 ExecutiveState s; 00286 s.publishing_localization = publishing_loc_; 00287 s.successful_navs = successful_navs_; 00288 s.localization_badness = localization_badness_; 00289 exec_state_pub_.publish(s); 00290 } 00291 00292 // Update counter of successful navs observed 00293 void Node::navCB (const mbm::MoveBaseActionResult& res) 00294 { 00295 if (res.status.status == actionlib_msgs::GoalStatus::SUCCEEDED) 00296 { 00297 successful_navs_++; 00298 if (successful_navs_==min_successful_navs_) 00299 { 00300 ROS_INFO ("Have now observed %u successful navigations. Can " 00301 "now start saving scans", successful_navs_); 00302 } 00303 } 00304 } 00305 00306 // Publish the poses of the reference scans (for visualization) 00307 void Node::publishRefScans () const 00308 { 00309 gm::PoseArray poses; 00310 poses.header.stamp = ros::Time::now(); 00311 poses.header.frame_id = "/map"; 00312 BOOST_FOREACH (const RefScan& s, ref_scans_) 00313 poses.poses.push_back(s.pose); 00314 ref_scan_pose_pub_.publish(poses); 00315 } 00316 00318 unsigned getSum (const nm::OccupancyGrid& g) 00319 { 00320 unsigned sum=0; 00321 BOOST_FOREACH (const unsigned val, g.data) 00322 sum += val; 00323 return sum; 00324 } 00325 00326 00327 // Use the map to initialize the localization evaluator 00328 void Node::mapCB (const nm::OccupancyGrid& g) 00329 { 00330 const unsigned my_sum = getSum(g); 00331 mr::MessageCollection<nm::OccupancyGrid> coll(db_name_, "grid", db_host_); 00332 ROS_INFO("Received map"); 00333 if (coll.count()>0) 00334 { 00335 MapWithMetadata::ConstPtr saved_map = 00336 coll.pullAllResults(mr::Query(), true)[0]; 00337 const double res = saved_map->lookupDouble("resolution"); 00338 const unsigned height = saved_map->lookupInt("height"); 00339 const unsigned width = saved_map->lookupInt("width"); 00340 const unsigned sum = saved_map->lookupInt("sum"); 00341 if (my_sum!=sum || height!=g.info.height || width!=g.info.width || 00342 fabs(res-g.info.resolution)>1e-3) 00343 { 00344 ROS_FATAL("Db map is %ux%u at %.4f m/cell, with sum %u, which " 00345 "doesn't match current map of size %ux%u at %.4f m/cell" 00346 " with sum %u", height, width, res, sum, g.info.height, 00347 g.info.width, g.info.resolution, my_sum); 00348 ROS_BREAK(); 00349 } 00350 } 00351 else 00352 { 00353 ROS_WARN("Didn't find an OccupancyGrid in the database. Assuming this is " 00354 "a new db and saving current map with size %ux%u and sum %u", 00355 g.info.height, g.info.width, my_sum); 00356 mr::Metadata m = mr::Metadata().\ 00357 append("height", g.info.height).append("width", g.info.width).\ 00358 append("resolution", g.info.resolution).append("sum", my_sum); 00359 coll.insert(g, m); 00360 } 00361 ROS_INFO("Initializing scan evaluator distance field"); 00362 Lock l(mutex_); 00363 evaluator_.reset(new ScanPoseEvaluator(g, badness_threshold_*1.5)); 00364 ROS_INFO("Scan evaluator initialized"); 00365 } 00366 00367 00368 00369 // If we're not well localized, try to localize by matching against the scans in the db 00370 void Node::updateUnlocalized (sm::LaserScan::ConstPtr scan) 00371 { 00372 // Extract features from curent scan 00373 gm::Pose current = getCurrentPose(tf_, "base_footprint"); 00374 ROS_INFO("Not well localized"); 00375 InterestPointVec pts = features_.extractFeatures(scan); 00376 marker_pub_.publish(interestPointMarkers(pts, current)); 00377 00378 // Set up 00379 gm::PoseArray match_poses; 00380 match_poses.header.frame_id = "/map"; 00381 match_poses.header.stamp = ros::Time::now(); 00382 unsigned best_num_matches = 0; 00383 gm::Pose best_pose; 00384 00385 // Iterate over reference scans and match against each one, looking for the 00386 // one with the largest number of feature matches 00387 BOOST_FOREACH (const RefScan& ref_scan, ref_scans_) 00388 { 00389 Correspondences matches; 00390 OrientedPoint2D trans; 00391 features_.ransac_->matchSets(ref_scan.raw_pts, pts, trans, matches); 00392 const unsigned num_matches = matches.size(); 00393 if (num_matches > min_num_matches_) 00394 { 00395 ROS_INFO_NAMED ("match", "Found %d matches with ref scan at " 00396 "%.2f, %.2f, %.2f", num_matches, 00397 ref_scan.pose.position.x, ref_scan.pose.position.y, 00398 tf::getYaw(ref_scan.pose.orientation)); 00399 match_poses.poses.push_back(ref_scan.pose); 00400 const gm::Pose laser_pose = transformPose(ref_scan.pose, trans); 00401 if (num_matches > best_num_matches) 00402 { 00403 best_num_matches = num_matches; 00404 best_pose = laser_pose; 00405 } 00406 } 00407 } 00408 00409 // Only proceed if there are a sufficient number of feature matches 00410 if (best_num_matches<min_num_matches_) 00411 return; 00412 00413 match_pose_pub_.publish(match_poses); 00414 const double badness = (*evaluator_)(*scan, best_pose); 00415 ROS_INFO ("Badness is %.2f", badness); 00416 00417 // Do a further check of scan badness before committing to this 00418 // We'll always publish a visualization, but we'll only publish on the 00419 // initialpose topic once (so after that, this node will go into a state where 00420 // all it does is possibly save new scans). 00421 if (badness < badness_threshold_) 00422 { 00423 ROS_INFO("Found a good match"); 00424 00425 const ros::Time now = ros::Time::now(); 00426 gm::PoseStamped estimated_pose; 00427 try { 00428 tf::Pose adjusted_pose(compensateOdometry(poseMsgToTf(best_pose), 00429 scan->header.frame_id, 00430 scan->header.stamp, now)); 00431 tf::poseTFToMsg(adjusted_pose*laser_offset_, estimated_pose.pose); 00432 } 00433 catch (tf::TransformException& e) 00434 { 00435 ROS_INFO ("Discarding match due to tf exception: %s", e.what()); 00436 return; 00437 } 00438 00439 00440 estimated_pose.header.frame_id = "/map"; 00441 estimated_pose.header.stamp = now; 00442 pose_est_pub_.publish(estimated_pose); 00443 00444 if (!continual_publish_) 00445 publishing_loc_ = false; 00446 gm::PoseWithCovarianceStamped initial_pose; 00447 initial_pose.header.frame_id = "/map"; 00448 initial_pose.header.stamp = scan->header.stamp; 00449 initial_pose.pose.pose = estimated_pose.pose; 00450 pose_pub_.publish(initial_pose); 00451 } 00452 } 00453 00456 tf::Transform Node::compensateOdometry (const tf::Pose& pose, 00457 const string& frame, 00458 const ros::Time& t1, 00459 const ros::Time& t2) 00460 { 00461 tf_.waitForTransform(odom_frame_, frame, t1, ros::Duration(0.1)); 00462 tf_.waitForTransform(odom_frame_, frame, t2, ros::Duration(0.1)); 00463 00464 tf::StampedTransform current_odom, prev_odom; 00465 tf_.lookupTransform(odom_frame_, frame, t2, current_odom); 00466 tf_.lookupTransform(odom_frame_, frame, t1, prev_odom); 00467 tf::Transform current_to_prev = prev_odom.inverse()*current_odom; 00468 return pose*current_to_prev; 00469 } 00470 00471 00472 // Search the db for a nearby scan. If none is found, and also we've 00473 // successfully navigated previously (as a further cue that we're well 00474 // localized), then add this scan to the db, and remove any 00475 // nearby old scans. 00476 void Node::updateLocalized (sm::LaserScan::ConstPtr scan, 00477 const gm::Pose& current) 00478 { 00479 ROS_INFO("Well localized"); 00480 const double DPOS = 0.7; 00481 const double DTHETA = 1; 00482 const double DT = 86400; // one day 00483 00484 // Query the db for nearby scans 00485 const double t = ros::Time::now().toSec(); 00486 const double x = current.position.x; 00487 const double x0 = x - DPOS; 00488 const double x1 = x + DPOS; 00489 const double y = current.position.y; 00490 const double y0 = y - DPOS; 00491 const double y1 = y + DPOS; 00492 const double th = tf::getYaw(current.orientation); 00493 const double th0 = th - DTHETA; 00494 const double th1 = th + DTHETA; 00495 const double min_time = t-DT; 00496 format f("{x : {$gt: %.4f, $lt: %.4f}, y : {$gt: %.4f, $lt: %.4f}, " 00497 "theta: {$gt: %.4f, $lt: %.4f}, creation_time: {$gt: %.8f} }"); 00498 string str = (f % x0 % x1 % y0 % y1 % th0 % th1 % min_time).str(); 00499 mongo::Query q = mongo::fromjson(str); 00500 vector<DBScan> scans = scans_.pullAllResults(q, true); 00501 00502 // Possibly save this new scan 00503 if (scans.size()<1 && successful_navs_>=min_successful_navs_) 00504 { 00505 // First remove old scans that are nearby 00506 format old("{x : {$gt: %.4f, $lt: %.4f}, y : {$gt: %.4f, $lt: %.4f}, " 00507 "theta: {$gt: %.4f, $lt: %.4f} }"); 00508 const string old_query_str = (old % x0 % x1 % y0 % y1 % th0 % th1).str(); 00509 const mongo::Query old_query = mongo::fromjson(old_query_str); 00510 const vector<DBScan> old_scans = scans_.pullAllResults(old_query, true); 00511 scans_.removeMessages(old_query); 00512 BOOST_FOREACH (const DBScan& old_scan, old_scans) 00513 { 00514 ROS_INFO ("Removed old scan at (%.4f, %.4f, %.f) taken at %.4f", 00515 old_scan->lookupDouble("x"), old_scan->lookupDouble("y"), 00516 old_scan->lookupDouble("theta"), 00517 old_scan->lookupDouble("creation_time")); 00518 } 00519 00520 // Now add this new scan 00521 InterestPointVec pts = features_.extractFeatures(scan); 00522 sm::LaserScan::Ptr stripped_scan(new sm::LaserScan(*scan)); 00523 stripped_scan->ranges.clear(); 00524 stripped_scan->intensities.clear(); 00525 RefScan ref_scan(stripped_scan, current, pts); 00526 ref_scans_.push_back(ref_scan); 00527 scans_.insert(toRos(ref_scan), 00528 mr::Metadata("x", x, "y", y, "theta", th)); 00529 ROS_DEBUG_NAMED ("save_scan", "Saved scan at %.2f, %.2f, %.2f", x, y, th); 00530 00531 publishRefScans(); 00532 } 00533 } 00534 00535 00536 // Given current scan and pose, update state. If well localized, we'll 00537 // consider adding this new scan to the db. If not, we'll try to localize 00538 // using the db. 00539 void Node::scanCB (sm::LaserScan::ConstPtr scan) 00540 { 00541 // First, look up the sensor pose at the time of the scan 00542 const std::string frame = scan->header.frame_id; 00543 tf_.waitForTransform("/map", frame, scan->header.stamp, ros::Duration(0.5)); 00544 tf::StampedTransform trans; 00545 try 00546 { 00547 tf_.lookupTransform("/map", frame, scan->header.stamp, trans); 00548 } 00549 catch (tf::TransformException& e) 00550 { 00551 ROS_INFO_STREAM ("Skipping scan due to tf exception " << e.what()); 00552 return; 00553 } 00554 00555 // Have we initialized the evaluator yet? 00556 { 00557 Lock l(mutex_); 00558 if (!evaluator_) 00559 { 00560 ROS_INFO_STREAM ("Skipping scan as evaluator not yet initialized"); 00561 return; 00562 } 00563 00564 // Evaluate how well this scan is localized 00565 const gm::Pose pose = tfTransformToPose(trans); 00566 const double dist = (*evaluator_)(*scan, pose); 00567 localization_badness_ = dist; 00568 ROS_INFO ("Localization badness is %.2f", dist); 00569 00570 if (dist < badness_threshold_) 00571 updateLocalized(scan, pose); 00572 else if (publishing_loc_) 00573 updateUnlocalized(scan); 00574 00575 } 00576 // Make this callback happen at a bounded rate 00577 update_rate_.sleep(); 00578 } 00579 00580 } // namespace 00581 00582 int main (int argc, char** argv) 00583 { 00584 ros::init(argc, argv, "localization_monitor_node"); 00585 flirtlib_ros::Node node; 00586 ros::spin(); 00587 return 0; 00588 }