njockey.cpp
Go to the documentation of this file.
00001 #include <featurenav_base/njockey.h>
00002 
00003 namespace featurenav_base
00004 {
00005 
00006 const ros::Duration NJockey::max_odom_age_ = ros::Duration(0.5);
00007 const double NJockey::reach_angular_distance_ = 0.001;  // (rad)
00008 const int NJockey::histogram_bin_size_ = 20;
00009 
00010 NJockey::NJockey(const std::string& name, const std::string& segment_interface_name, const std::string& segment_getter_name) :
00011   lama_jockeys::NavigatingJockey(name),
00012   it_(private_nh_),
00013   forward_velocity_(0.5),
00014   kp_(0.01),
00015   matcher_max_relative_distance_(0.8),
00016   max_angular_velocity_(1.0),
00017   min_angular_velocity_(0.0),
00018   segment_interface_name_(segment_interface_name),
00019   segment_getter_name_(segment_getter_name),
00020   has_odom_(false),
00021   start_angle_reached_(false),
00022   image_processing_running_(false)
00023 {
00024   // Debug log level
00025   if(ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Debug))
00026   {
00027     ros::console::notifyLoggerLevelsChanged();
00028   }
00029 
00030   private_nh_.getParam("forward_velocity", forward_velocity_);
00031   private_nh_.getParam("kp", kp_);
00032   private_nh_.getParam("matcher_max_relative_distance", matcher_max_relative_distance_);
00033   private_nh_.getParam("max_angular_velocity", max_angular_velocity_);
00034   private_nh_.getParam("min_angular_velocity", min_angular_velocity_);
00035 
00036   twist_publisher_ = private_nh_.advertise<geometry_msgs::Twist>("cmd_vel", 1);
00037 
00038   ROS_DEBUG_STREAM("Waiting for service \"" << segment_getter_name_ << "\"");
00039   segment_getter_proxy_ = nh_.serviceClient< ::featurenav_base::GetSegment>(segment_getter_name_);
00040   segment_getter_proxy_.waitForExistence();
00041 }
00042 
00043 void NJockey::reset()
00044 {
00045   has_odom_ = false;
00046   start_angle_reached_ = false;
00047   image_processing_running_ = false;
00048 }
00049 
00050 bool NJockey::retrieveSegment()
00051 {
00052   ros::Time start = ros::Time::now();
00053   lama_interfaces::ActOnMap map_action;
00054   map_action.request.action = lama_interfaces::ActOnMapRequest::GET_DESCRIPTOR_LINKS;
00055   map_action.request.object.id = goal_.edge.id;
00056   map_action.request.interface_name = segment_interface_name_;
00057   map_agent_.call(map_action);
00058   if (map_action.response.descriptor_links.empty())
00059   {
00060     ROS_DEBUG_STREAM("No segment associated with vertex " << map_action.request.object.id <<
00061         " (interface \"" << segment_interface_name_ << "\")");
00062     return false;
00063   }
00064   if (map_action.response.descriptor_links.size() > 1)
00065   {
00066     ROS_WARN("More than segment associated with edge %d, taking the first one",
00067         map_action.request.object.id);
00068   }
00069   GetSegment get_segment_srv;
00070   get_segment_srv.request.id = map_action.response.descriptor_links[0].descriptor_id;
00071   if (!segment_getter_proxy_.call(get_segment_srv))
00072   {
00073     ROS_ERROR_STREAM("Failed to get segment with id " << get_segment_srv.request.id <<
00074         " and interface " << segment_interface_name_ << " (service " << 
00075         segment_getter_proxy_.getService() << ")");
00076     return false;
00077   }
00078   segment_ = get_segment_srv.response.descriptor;
00079   ROS_DEBUG("Received segment %d with %zu landmarks in %.3f s", get_segment_srv.request.id,
00080       segment_.landmarks.size(), (ros::Time::now() - start).toSec());
00081   return true;
00082 }
00083 
00084 void NJockey::onTraverse()
00085 {
00086   ROS_DEBUG("Received action TRAVERSE");
00087 
00088   if ((canDo == NULL) || (startDo == NULL))
00089   {
00090     ROS_ERROR("Internal initialization problem, will not start learning...");
00091     server_.setAborted();
00092     return;
00093   }
00094   if (extract_features_ == NULL)
00095   {
00096     ROS_ERROR("extract_feature function not set, will not start learning...");
00097     server_.setAborted();
00098     return;
00099   }
00100   if (match_descriptors_ == NULL)
00101   {
00102     ROS_ERROR("match_descriptor function not set, will not start learning...");
00103     server_.setAborted();
00104     return;
00105   }
00106 
00107   // Get the segment.
00108   ROS_DEBUG("Getting the segment"); // DEBUG
00109   if (!retrieveSegment())
00110   {
00111     ROS_ERROR("No edge with id %d or no segment associated with it", goal_.edge.id);
00112     server_.setAborted();
00113     return;
00114   }
00115   ROS_INFO("Starting to follow a segment with %zu landmarks at angle %.3f rad over %.3f m",
00116       segment_.landmarks.size(), segment_.yaw, segment_.distance);
00117 
00118   odom_handler_ = private_nh_.subscribe("odom", 1, &NJockey::callback_odom, this);
00119   
00120   // Waiting for the first odometry message.
00121   ros::Duration(0.2).sleep();
00122   ros::spinOnce();
00123   while (!has_odom_)
00124   {
00125     ros::spinOnce();
00126     ROS_WARN_STREAM_THROTTLE(5, "Waiting for odometry on topic " << odom_handler_.getTopic());
00127 
00128     if (!ros::ok() || server_.isPreemptRequested())
00129     {
00130       return;
00131     }
00132 
00133     ros::Duration(0.01).sleep();
00134   }
00135 
00136   ROS_DEBUG("Orienting the robot");
00137 
00138   // Orient the robot according to the segment angle.
00139   ros::Rate r_start_angle(100);
00140   while(!start_angle_reached_ && !server_.isPreemptRequested() && ros::ok())
00141   {
00142     ros::spinOnce();
00143     geometry_msgs::Twist twist = turnToAngle(segment_.yaw);
00144     twist_publisher_.publish(twist);
00145     r_start_angle.sleep();
00146   }
00147   
00148   // Save the position after orienting as starting position.
00149   start_pose_ = odom_.pose.pose;
00150 
00151   image_handler_ = it_.subscribe("camera/image_raw", 1, &NJockey::callback_image, this);
00152 
00153   ROS_DEBUG("Starting going forward");
00154 
00155   ros::Rate r(100);
00156   while (ros::ok())
00157   {
00158     ros::spinOnce();
00159     if (server_.isPreemptRequested())
00160     {
00161       ROS_INFO("preempted");
00162       // set the action state to preempted
00163       // TODO: should the server be preempted?
00164       server_.setPreempted();
00165       break;
00166     }
00167 
00168     if (distance_from_start() > segment_.distance)
00169     {
00170       image_handler_.shutdown();
00171       odom_handler_.shutdown();
00172       twist_publisher_.publish(geometry_msgs::Twist());
00173       result_.final_state = result_.DONE;
00174       result_.completion_time = getCompletionDuration();
00175       server_.setSucceeded(result_);
00176       break;
00177     }
00178     r.sleep();
00179   }
00180   reset();
00181   ROS_DEBUG("Exiting onTraverse");
00182 }
00183 
00184 void NJockey::onStop()
00185 {
00186   reset();
00187   image_handler_.shutdown();
00188   odom_handler_.shutdown();
00189   twist_publisher_.publish(geometry_msgs::Twist());
00190   result_.final_state = result_.DONE;
00191   result_.completion_time = ros::Duration(0);
00192   server_.setSucceeded(result_);
00193 }
00194 
00195 void NJockey::onInterrupt()
00196 {
00197   image_handler_.shutdown();
00198   odom_handler_.shutdown();
00199 }
00200 
00201 void NJockey::onContinue()
00202 {
00203   image_handler_ = it_.subscribe("camera/image_raw", 1, &NJockey::callback_image, this);
00204   odom_handler_ = private_nh_.subscribe("odom", 1, &NJockey::callback_odom, this);
00205 }
00206 
00207 void NJockey::callback_odom(const nav_msgs::OdometryConstPtr& msg)
00208 {
00209   if ((ros::Time::now() - msg->header.stamp) < max_odom_age_)
00210   {
00211     odom_ = *msg;
00212     has_odom_ = true;
00213     return;
00214   }
00215   ROS_WARN("Odometry received but too old (%.3f s)", (ros::Time::now() - msg->header.stamp).toSec());
00216 }
00217 
00218 void NJockey::callback_image(const sensor_msgs::ImageConstPtr& msg)
00219 {
00220   ros::Time start_time = ros::Time::now();
00221 
00222   if (!has_odom_)
00223   {
00224     ROS_WARN("No Odometry received, ignoring image");
00225     return;
00226   }
00227 
00228   if ((odom_.header.stamp < msg->header.stamp) && (msg->header.stamp - odom_.header.stamp) > max_odom_age_)
00229   {
00230     ROS_WARN("Odometry is too old, ignoring image");
00231     return;
00232   }
00233 
00234   geometry_msgs::Twist twist;
00235   twist.linear.x = forward_velocity_;
00236   double dtheta;
00237   // TODO: adapt the velocity to the number of matches.
00238   processImage(msg, dtheta);
00239 
00240   twist.angular.z = saturate(kp_ * dtheta);
00241   twist_publisher_.publish(twist);
00242 
00243   ROS_DEBUG("Computation time: %.3f", (ros::Time::now().toSec() - start_time.toSec()));
00244 }
00245 
00251 geometry_msgs::Twist NJockey::turnToAngle(double direction)
00252 {
00253   geometry_msgs::Twist twist;
00254   if (start_angle_reached_)
00255   {
00256     return twist;
00257   }
00258 
00259   const double yaw_now = tf::getYaw(odom_.pose.pose.orientation);
00260   const double dtheta = angles::shortest_angular_distance(yaw_now, direction);
00261   ROS_DEBUG("dtheta to goal: %.3f", dtheta);
00262 
00263   twist.angular.z = saturate(kp_ * dtheta);
00264   start_angle_reached_ = (std::abs(dtheta) < reach_angular_distance_);
00265   return twist;
00266 }
00267 
00268 double NJockey::saturate(double w) const
00269 {
00270   if (w > max_angular_velocity_)
00271   {
00272     w = max_angular_velocity_;
00273   }
00274   else if (w < -max_angular_velocity_)
00275   {
00276     w = -max_angular_velocity_;
00277   }
00278 
00279   // Dead-zone management. Always used if not start_angle_reached_. If
00280   // start_angle_reached_, only used if forward_velocity_ is 0.
00281   double v = forward_velocity_;
00282   if (!start_angle_reached_)
00283   {
00284     v = 0;
00285   }
00286   if ((std::abs(v) < 1e-10) && (0 < w) && (w < min_angular_velocity_))
00287   {
00288     w = min_angular_velocity_;
00289   }
00290   else if ((std::abs(v) < 1e-10) && (-min_angular_velocity_ < w) && (w < 0))
00291   {
00292     w = -min_angular_velocity_;
00293   }
00294   return w;
00295 }
00296 
00299 size_t NJockey::processImage(const sensor_msgs::ImageConstPtr& image, double& dtheta)
00300 {
00301   vector<KeyPoint> keypoints;
00302   vector<Feature> descriptors;
00303   try
00304   {
00305     extract_features_(image, keypoints, descriptors);
00306   }
00307   catch (std::exception)
00308   {
00309     ROS_ERROR("Error caught on extract_features, ignoring image");
00310     dtheta = 0;
00311     return 0;
00312   }
00313   ROS_DEBUG("Number of detected features: %zu", keypoints.size());
00314 
00315   const double d = distance_from_start();
00316   vector<Landmark> landmarks = select_tracked_landmarks(d);
00317   ROS_DEBUG("Number of tracked landmarks: %zu", landmarks.size());
00318 
00319   if (landmarks.empty())
00320   {
00321     dtheta = 0;
00322     return 0;
00323   }
00324   
00325   vector<double> dthetas = compute_horizontal_differences(landmarks, keypoints, descriptors, d);
00326 
00327   if (dthetas.empty())
00328   {
00329     dtheta = 0;
00330     return 0;
00331   }
00332   
00333   double min_value = *std::min_element(dthetas.begin(), dthetas.end());
00334   double max_value = *std::max_element(dthetas.begin(), dthetas.end());
00335   int num_bins = std::max(1, (int)(max_value - min_value) / histogram_bin_size_);
00336   accumulator_type accumulator(density::num_bins = num_bins, density::cache_size = dthetas.size());
00337   accumulator = std::for_each(dthetas.begin(), dthetas.end(), accumulator);
00338   histogram_type histogram = boost::accumulators::density(accumulator);
00339 
00340   // TODO: more histograms (shifted by the pixel) and find maximum in multi-array histogram
00341   
00342   int hist_max_i = -1;
00343   double max_density = -1;
00344   for (size_t i = 0; i < histogram.size(); ++i)
00345   {
00346     ROS_DEBUG("histogram: (%.3f, %.3f)", histogram[i].first, histogram[i].second);
00347     if (isnan(histogram[i].first || isinf(histogram[i].first)))
00348     {
00349       break;
00350     }
00351     if (histogram[i].second > max_density)
00352     {
00353       hist_max_i = i;
00354       max_density = histogram[i].second;
00355     }
00356   }
00357   if (hist_max_i == -1)
00358   {
00359     dtheta = histogram.back().first + histogram_bin_size_ / 2.0;
00360   }
00361   else
00362   {
00363     dtheta = histogram[hist_max_i].first + histogram_bin_size_ / 2.0;
00364   }
00365 
00366   ROS_DEBUG("Highest bin density: %.1f %%", histogram[hist_max_i].second * 100.0);
00367   ROS_DEBUG("Position difference with highest density: %.3f pixels", dtheta);
00368   ROS_DEBUG("Traveled distance %.3f m / %.3f m", d, segment_.distance);
00369 
00370   return (size_t) std::ceil(histogram[hist_max_i].second * dthetas.size());
00371 }
00372 
00373 vector<Landmark> NJockey::select_tracked_landmarks(double d) const
00374 {
00375   vector<Landmark> landmarks;
00376   for (size_t i = 0; i < segment_.landmarks.size(); ++i)
00377   {
00378     if ((segment_.landmarks[i].du <= d) && (d <= segment_.landmarks[i].dv))
00379     {
00380       landmarks.push_back(segment_.landmarks[i]);
00381     }
00382   }
00383   return landmarks;
00384 }
00385 
00386 vector<double> NJockey::compute_horizontal_differences(const vector<Landmark>& landmarks,
00387     const vector<cv::KeyPoint>& keypoints, const vector<Feature>& descriptors, double d)
00388 {
00389   vector<double> dthetas;
00390 
00391   vector<vector<cv::DMatch> > matches;
00392 
00393   // TODO: compute all matches at once.
00394   for (size_t i = 0; i < landmarks.size(); ++i)
00395   {
00396     matches.clear();
00397     vector<Feature> query_descriptors(1, landmarks[i].descriptor);
00398     try
00399     {
00400       match_descriptors_(query_descriptors, descriptors, matches);
00401     }
00402     catch (std::exception)
00403     {
00404       ROS_ERROR("Error caught on match_descriptors, ignoring image");
00405       return dthetas;
00406     }
00407 
00408     if (matches.size() < 1)
00409     {
00410       continue;
00411     }
00412     if (matches[0].size() < 2)
00413     {
00414       ROS_DEBUG("Not enough matches, found %zu", matches[0].size());
00415       continue;
00416     }
00417     // Add to the horizontal differences.
00418     if (matches[0][0].distance < matcher_max_relative_distance_ * matches[0][1].distance)
00419     {
00420       const double dx = (landmarks[i].v.x - landmarks[i].u.x) * (d - landmarks[i].du) / (landmarks[i].dv -
00421           landmarks[i].du) +
00422         landmarks[i].u.x - keypoints[matches[0][0].trainIdx].pt.x;
00423       if (!isnan(dx))
00424       {
00425         dthetas.push_back(dx);
00426       }
00427     }
00428   }
00429   return dthetas;
00430 }
00431 
00432 } // namespace featurenav_base
00433 
00434 


featurenav_base
Author(s): Gaƫl Ecorchard
autogenerated on Sat Jun 8 2019 19:52:22