explore_frontier.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  *
00003  * Software License Agreement (BSD License)
00004  *
00005  *  Copyright (c) 2008, Robert Bosch LLC.
00006  *  All rights reserved.
00007  *
00008  *  Redistribution and use in source and binary forms, with or without
00009  *  modification, are permitted provided that the following conditions
00010  *  are met:
00011  *
00012  *   * Redistributions of source code must retain the above copyright
00013  *     notice, this list of conditions and the following disclaimer.
00014  *   * Redistributions in binary form must reproduce the above
00015  *     copyright notice, this list of conditions and the following
00016  *     disclaimer in the documentation and/or other materials provided
00017  *     with the distribution.
00018  *   * Neither the name of the Robert Bosch nor the names of its
00019  *     contributors may be used to endorse or promote products derived
00020  *     from this software without specific prior written permission.
00021  *
00022  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033  *  POSSIBILITY OF SUCH DAMAGE.
00034  *
00035  *********************************************************************/
00036 
00037 /*
00038  *  Created on: Apr 6, 2009
00039  *      Author: duhadway
00040  */
00041 
00042 #include <explore_hrl/explore_frontier.h>
00043 #include <explore_hrl/Line.h>
00044 #include <explore_hrl/Vector.h>
00045 #include <math.h>
00046 
00047 using namespace visualization_msgs;
00048 using namespace geom;
00049 using namespace costmap_2d;
00050 
00051 namespace explore {
00052 
00053 ExploreFrontier::ExploreFrontier() :
00054   map_(),
00055   lastMarkerCount_(0),
00056   planner_(NULL),
00057   frontiers_()
00058 {
00059 }
00060 
00061 ExploreFrontier::~ExploreFrontier()
00062 {
00063 
00064 }
00065 
00066 bool ExploreFrontier::getFrontiers(Costmap2DROS& costmap, std::vector<geometry_msgs::Pose>& frontiers)
00067 {
00068   findFrontiers(costmap);
00069   if (frontiers_.size() == 0)
00070     return false;
00071 
00072   frontiers.clear();
00073   for (uint i=0; i < frontiers_.size(); i++) {
00074     geometry_msgs::Pose frontier;
00075     frontiers.push_back(frontiers_[i].pose);
00076   }
00077 
00078   return (frontiers.size() > 0);
00079 }
00080 
00081 float ExploreFrontier::getFrontierCost(const Frontier& frontier) {
00082   //planner_->computePotential( frontier.pose.position );
00083   ROS_DEBUG("cost of frontier: %f, at position: (%.2f, %.2f, %.2f)\n", planner_->getPointPotential(frontier.pose.position), 
00084       frontier.pose.position.x, frontier.pose.position.y, tf::getYaw(frontier.pose.orientation));
00085   if (planner_ != NULL)
00086     return planner_->getPointPotential(frontier.pose.position);
00087   else
00088     return 1.0;
00089 }
00090 
00091 double ExploreFrontier::getOrientationChange(const Frontier& frontier, const tf::Stamped<tf::Pose>& robot_pose){
00092     double robot_yaw = tf::getYaw(robot_pose.getRotation());
00093     double robot_atan2 = atan2(robot_pose.getOrigin().y() + sin(robot_yaw), robot_pose.getOrigin().x() + cos(robot_yaw));
00094     double frontier_atan2 = atan2(frontier.pose.position.x, frontier.pose.position.y);
00095     double orientation_change = robot_atan2 - frontier_atan2;
00096     ROS_DEBUG("Orientation change: %.3f", orientation_change * (180.0 / M_PI));
00097     return orientation_change;
00098 }
00099 
00100 float ExploreFrontier::getFrontierGain(const Frontier& frontier, double map_resolution) {
00101   return frontier.size * map_resolution;
00102 }
00103 
00104 bool ExploreFrontier::getExplorationGoals(Costmap2DROS& costmap, geometry_msgs::Point start, navfn::NavfnROS* planner, std::vector<geometry_msgs::Pose>& goals, double potential_scale, double orientation_scale, double gain_scale)
00105 {
00106   planner->computePotential(start);
00107   planner_ = planner;
00108   costmapResolution_ = costmap.getResolution();
00109 
00110   findFrontiers(costmap);
00111   if (frontiers_.size() == 0){
00112     return false;
00113   }
00114 
00115 
00116   //we'll make sure that we set goals for the frontier at least the circumscribed
00117   //radius away from unknown space
00118   double goal_stepback = costmap.getCircumscribedRadius();
00119 
00120   WeightedFrontier goal;
00121   std::vector<WeightedFrontier> weightedFrontiers;
00122   weightedFrontiers.reserve(frontiers_.size());
00123   for (uint i=0; i < frontiers_.size(); i++) {
00124     Frontier& frontier = frontiers_[i];
00125     WeightedFrontier weightedFrontier;
00126     weightedFrontier.frontier = frontier;
00127 
00128     tf::Quaternion bt;
00129     tf::quaternionMsgToTF(frontier.pose.orientation, bt);
00130 
00131     double distance = 0.0;
00132 
00133     //we'll walk back along the potential function generated by the planner from the frontier point at least the inscribed radius
00134     while(distance < goal_stepback){
00135       geometry_msgs::Point check_point = frontier.pose.position;
00136       check_point.x -= costmapResolution_;
00137       check_point.y -= costmapResolution_;
00138       double best_potential = planner->getPointPotential(check_point);
00139       geometry_msgs::Point best_point = check_point;
00140 
00141       //look at the neighbors of the current point to find the one with the lowest potential
00142       for(unsigned int i = 0; i < 3; ++i){
00143         for(unsigned int j = 0; j < 3; ++j){
00144           check_point.x += costmapResolution_;
00145           double potential = planner->getPointPotential(check_point);
00146 
00147           //check if the potential is better, make sure that we don't check the frontier point itself
00148           if(potential < best_potential && !(i == 1 && j == 1)){
00149             best_potential = potential;
00150             best_point = check_point;
00151           }
00152         }
00153         check_point.x = frontier.pose.position.x - costmapResolution_;
00154         check_point.y += costmapResolution_;
00155       }
00156 
00157       //update the frontier position and add to the distance traveled
00158       frontier.pose.position.x = best_point.x;
00159       frontier.pose.position.y = best_point.y;
00160       distance += costmapResolution_;
00161     }
00162 
00163     //create a line using the vector we stored in the frontier
00164     //Line l(Point(frontier.pose.position.x, frontier.pose.position.y, frontier.pose.position.z), bt.getAngle());
00165 
00166     //step back the inscribed radius along that line to find a goal point
00167     //Point t = l.eval(goal_stepback);
00168 
00169     weightedFrontier.frontier.pose.position.x = frontier.pose.position.x;
00170     weightedFrontier.frontier.pose.position.y = frontier.pose.position.y;
00171     weightedFrontier.frontier.pose.position.z = frontier.pose.position.z;
00172 
00173     tf::Stamped<tf::Pose> robot_pose;
00174     costmap.getRobotPose(robot_pose);
00175 
00176     //compute the cost of the frontier
00177     weightedFrontier.cost = potential_scale * getFrontierCost(weightedFrontier.frontier) + orientation_scale * getOrientationChange(weightedFrontier.frontier, robot_pose) - gain_scale * getFrontierGain(weightedFrontier.frontier, costmapResolution_);
00178 
00179     weightedFrontiers.push_back(weightedFrontier);
00180   }
00181 
00182   goals.clear();
00183   goals.reserve(weightedFrontiers.size());
00184   std::sort(weightedFrontiers.begin(), weightedFrontiers.end());
00185   for (uint i = 0; i < weightedFrontiers.size(); i++) {
00186     goals.push_back(weightedFrontiers[i].frontier.pose);
00187   }
00188   return (goals.size() > 0);
00189 }
00190 
00191 void ExploreFrontier::findFrontiers(Costmap2DROS& costmap_) {
00192   frontiers_.clear();
00193 
00194   Costmap2D costmap;
00195   costmap_.getCostmapCopy(costmap);
00196 
00197   int idx;
00198   int w = costmap.getSizeInCellsX();
00199   int h = costmap.getSizeInCellsY();
00200   int size = (w * h);
00201   int pts = 0;
00202 
00203   map_.info.width = w;
00204   map_.info.height = h;
00205   map_.set_data_size(size);
00206   map_.info.resolution = costmap.getResolution();
00207   map_.info.origin.position.x = costmap.getOriginX();
00208   map_.info.origin.position.y = costmap.getOriginY();
00209 
00210 
00211   // Find all frontiers (open cells next to unknown cells).
00212   const unsigned char* map = costmap.getCharMap();
00213   for (idx = 0; idx < size; idx++) {
00214     //get the world point for the index
00215     unsigned int mx, my;
00216     costmap.indexToCells(idx, mx, my);
00217     geometry_msgs::Point p;
00218     costmap.mapToWorld(mx, my, p.x, p.y);
00219 
00220     //check if the point has valid potential and is next to unknown space
00221     //bool valid_point = planner_->validPointPotential(p);
00222     //bool valid_point = planner_->computePotential(p) && planner_->validPointPotential(p);
00223     //bool valid_point = (map[idx] < LETHAL_OBSTACLE);
00224     //bool valid_point = (map[idx] < INSCRIBED_INFLATED_OBSTACLE );
00225     bool valid_point = (map[idx] == FREE_SPACE);
00226 
00227     if ((valid_point && map) &&
00228         (((idx+1 < size) && (map[idx+1] == NO_INFORMATION)) ||
00229          ((idx-1 >= 0) && (map[idx-1] == NO_INFORMATION)) ||
00230          ((idx+w < size) && (map[idx+w] == NO_INFORMATION)) ||
00231          ((idx-w >= 0) && (map[idx-w] == NO_INFORMATION))))
00232     {
00233       map_.data[idx] = -128;
00234       //ROS_INFO("Candidate Point %d.", pts++ );
00235     } else {
00236       map_.data[idx] = -127;
00237     }
00238   }
00239 
00240   // Clean up frontiers detected on separate rows of the map
00241   //   I don't know what this means -- Travis.
00242   idx = map_.info.height - 1;
00243   for (unsigned int y=0; y < map_.info.width; y++) {
00244     map_.data[idx] = -127;
00245     idx += map_.info.height;
00246   }
00247 
00248   // Group adjoining map_ pixels
00249   int segment_id = 127;
00250   std::vector< std::vector<FrontierPoint> > segments;
00251   for (int i = 0; i < size; i++) {
00252     if (map_.data[i] == -128) {
00253       std::vector<int> neighbors, candidates;
00254       std::vector<FrontierPoint> segment;
00255       neighbors.push_back(i);
00256 
00257       int points_in_seg = 0;
00258       unsigned int mx, my;
00259       geometry_msgs::Point p_init, p;
00260 
00261       costmap.indexToCells(i, mx, my);
00262       costmap.mapToWorld(mx, my, p_init.x, p_init.y);
00263 
00264       // claim all neighbors
00265       while (neighbors.size() > 0) {
00266         int idx = neighbors.back();
00267         neighbors.pop_back();
00268 
00269         btVector3 tot(0,0,0);
00270         int c = 0;
00271         if ((idx+1 < size) && (map[idx+1] == NO_INFORMATION)) {
00272           tot += btVector3(1,0,0);
00273           c++;
00274         }
00275         if ((idx-1 >= 0) && (map[idx-1] == NO_INFORMATION)) {
00276           tot += btVector3(-1,0,0);
00277           c++;
00278         }
00279         if ((idx+w < size) && (map[idx+w] == NO_INFORMATION)) {
00280           tot += btVector3(0,1,0);
00281           c++;
00282         }
00283         if ((idx-w >= 0) && (map[idx-w] == NO_INFORMATION)) {
00284           tot += btVector3(0,-1,0);
00285           c++;
00286         }
00287         assert(c > 0);
00288 
00289         // Only adjust the map and add idx to segment when its near enough to start point.
00290         //    This should prevent greedy approach where all cells belong to one frontier!
00291 
00292         costmap.indexToCells(idx, mx, my);
00293         costmap.mapToWorld(mx, my, p.x, p.y);
00294         float dist;
00295         dist = sqrt( pow( p.x - p_init.x, 2 ) + pow( p.y - p_init.y, 2 ));
00296 
00297         if ( dist <= 0.8 ){
00298           map_.data[idx] = segment_id;  // This used to be up before "assert" block above.
00299           points_in_seg += 1;
00300           //ROS_INFO( "Dist to start cell: %3.2f", dist );
00301 
00302           segment.push_back(FrontierPoint(idx, tot / c));
00303 
00304           // consider 8 neighborhood
00305           if (((idx-1)>0) && (map_.data[idx-1] == -128))
00306             neighbors.push_back(idx-1);
00307           if (((idx+1)<size) && (map_.data[idx+1] == -128))
00308             neighbors.push_back(idx+1);
00309           if (((idx-map_.info.width)>0) && (map_.data[idx-map_.info.width] == -128))
00310             neighbors.push_back(idx-map_.info.width);
00311           if (((idx-map_.info.width+1)>0) && (map_.data[idx-map_.info.width+1] == -128))
00312             neighbors.push_back(idx-map_.info.width+1);
00313           if (((idx-map_.info.width-1)>0) && (map_.data[idx-map_.info.width-1] == -128))
00314             neighbors.push_back(idx-map_.info.width-1);
00315           if (((idx+(int)map_.info.width)<size) && (map_.data[idx+map_.info.width] == -128))
00316             neighbors.push_back(idx+map_.info.width);
00317           if (((idx+(int)map_.info.width+1)<size) && (map_.data[idx+map_.info.width+1] == -128))
00318             neighbors.push_back(idx+map_.info.width+1);
00319           if (((idx+(int)map_.info.width-1)<size) && (map_.data[idx+map_.info.width-1] == -128))
00320             neighbors.push_back(idx+map_.info.width-1);
00321         }
00322       }
00323 
00324       segments.push_back(segment);
00325       ROS_INFO( "Segment %d has %d costmap cells", segment_id, points_in_seg );
00326       segment_id--;
00327       if (segment_id < -127)
00328         break;
00329     }
00330   }
00331 
00332   int num_segments = 127 - segment_id;
00333   ROS_INFO("Segments: %d", num_segments );
00334   if (num_segments <= 0)
00335     return;
00336 
00337   for (unsigned int i=0; i < segments.size(); i++) {
00338     Frontier frontier;
00339     std::vector<FrontierPoint>& segment = segments[i];
00340     uint size = segment.size();
00341 
00342     //we want to make sure that the frontier is big enough for the robot to fit through
00343     //  This seems like a goofy heuristic rather than fact.  What happens if we don't do this...?
00344     if (size * costmap.getResolution() < costmap.getInscribedRadius()){
00345       ROS_INFO( "Discarding segment... too small?" );
00346       //continue;
00347     }
00348 
00349     float x = 0, y = 0;
00350     btVector3 d(0,0,0);
00351 
00352     for (uint j=0; j<size; j++) {
00353       d += segment[j].d;
00354       int idx = segment[j].idx;
00355       x += (idx % map_.info.width);
00356       y += (idx / map_.info.width);
00357       // x = (idx % map_.info.width);
00358       // y = (idx / map_.info.width);
00359     }
00360     d = d / size;
00361     frontier.pose.position.x = map_.info.origin.position.x + map_.info.resolution * (x / size);
00362     frontier.pose.position.y = map_.info.origin.position.y + map_.info.resolution * (y / size);
00363     // frontier.pose.position.x = map_.info.origin.position.x + map_.info.resolution * (x);
00364     // frontier.pose.position.y = map_.info.origin.position.y + map_.info.resolution * (y);
00365     frontier.pose.position.z = 0.0;
00366 
00367     frontier.pose.orientation = tf::createQuaternionMsgFromYaw(btAtan2(d.y(), d.x()));
00368     frontier.size = size;
00369 
00370     geometry_msgs::Point p;
00371     p.x = map_.info.origin.position.x + map_.info.resolution * (x);  // frontier.pose.position
00372     p.y = map_.info.origin.position.y + map_.info.resolution * (y);
00373     if (!planner_->validPointPotential(p)){
00374       ROS_INFO( "Discarding segment... can't reach?" );
00375       //continue;
00376     }
00377 
00378     frontiers_.push_back(frontier);
00379   }
00380 
00381 }
00382 
00383 void ExploreFrontier::getVisualizationMarkers(std::vector<Marker>& markers)
00384 {
00385   Marker m;
00386   m.header.frame_id = "map";
00387   m.header.stamp = ros::Time::now();
00388   m.id = 0;
00389   m.ns = "frontiers";
00390   m.type = Marker::ARROW;
00391   m.pose.position.x = 0.0;
00392   m.pose.position.y = 0.0;
00393   m.pose.position.z = 0.0;
00394   m.scale.x = 1.0;
00395   m.scale.y = 1.0;
00396   m.scale.z = 1.0;
00397   m.color.r = 0;
00398   m.color.g = 0;
00399   m.color.b = 255;
00400   m.color.a = 255;
00401   m.lifetime = ros::Duration(0);
00402 
00403   m.action = Marker::ADD;
00404   uint id = 0;
00405   for (uint i=0; i<frontiers_.size(); i++) {
00406     Frontier frontier = frontiers_[i];
00407     m.id = id;
00408     m.pose = frontier.pose;
00409     markers.push_back(Marker(m));
00410     id++;
00411   }
00412 
00413   ROS_INFO("Number of frontiers being published: %d. Removed: %d. Last count: %d, id: %d", (unsigned int)frontiers_.size(), lastMarkerCount_ - id, lastMarkerCount_, id);
00414 
00415   m.action = Marker::DELETE;
00416   for (; id < lastMarkerCount_; id++) {
00417     m.id = id;
00418     markers.push_back(Marker(m));
00419   }
00420 
00421   lastMarkerCount_ = markers.size();
00422 }
00423 
00424 }


explore_hrl
Author(s): Travis Deyle
autogenerated on Wed Nov 27 2013 11:48:01