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;
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
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
00108 ROS_DEBUG("Getting the segment");
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
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
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
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
00163
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
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
00280
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
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
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
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 }
00433
00434