00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034 #include <ros/ros.h>
00035 #include <nodelet/nodelet.h>
00036 #include <message_filters/subscriber.h>
00037 #include <message_filters/synchronizer.h>
00038 #include <message_filters/sync_policies/exact_time.h>
00039 #include <pluginlib/class_list_macros.h>
00040 #include <nav_msgs/Odometry.h>
00041 #include <pcl/ModelCoefficients.h>
00042 #include <pcl/point_types.h>
00043 #include <pcl/ros/conversions.h>
00044 #include <tf/tf.h>
00045 #include <starmac_kinect/Debug.h>
00046 #include <starmac_kinect/Obstacle.h>
00047 #include <sensor_msgs/PointCloud2.h>
00048 #include <pcl/pcl_base.h>
00049 #include "pcl/PointIndices.h"
00050 #include <algorithm>
00051 #include <math.h>
00052 #include <visualization_msgs/Marker.h>
00053
00054 using message_filters::Synchronizer;
00055 using boost::make_shared;
00056 using message_filters::sync_policies::ExactTime;
00057 using std::min;
00058 using pcl::PointIndices;
00059 using pcl::ModelCoefficients;
00060 using std::vector;
00061
00062 namespace starmac_kinect_estimator
00063 {
00064
00065 class KinectEstimator : public nodelet::Nodelet
00066 {
00067 public:
00068 typedef PointIndices::ConstPtr PointIndicesConstPtr;
00069 typedef sensor_msgs::PointCloud2 PointCloud2;
00070 private:
00071 ros::NodeHandle nh_priv;
00072 ros::NodeHandle nh;
00073
00074 bool use_backup_estimator_alt;
00075 double imu_to_kinect_offset;
00076 double max_est_kinect_delta_alt;
00077 double obstacle_height_threshold;
00078 double debug_throttle_rate;
00079 double z_vel_filt_a;
00080 double z_vel_filt_b;
00081
00082 ros::Publisher odom_pub;
00083 ros::Publisher obstacle_pub;
00084 ros::Publisher debug_pub;
00085 ros::Publisher marker_pub;
00086 ros::Publisher mask_indices_pub;
00087 ros::Publisher findplane_indices_pub;
00088
00089 boost::shared_ptr<Synchronizer<ExactTime<PointCloud2, PointIndices, ModelCoefficients> > > sync_input_indices_e;
00090 message_filters::Subscriber<PointCloud2> sub_input_filter;
00091 message_filters::Subscriber<PointIndices> sub_indices_filter;
00092 message_filters::Subscriber<ModelCoefficients> sub_model_filter;
00093
00094
00095
00096 ros::Subscriber est_odom_sub;
00097
00098 nav_msgs::Odometry odom_msg_output;
00099 nav_msgs::Odometry latest_est_odom_msg;
00100 double kinect_z;
00101 double kinect_vz;
00102 int max_queue_size;
00103 ros::Time prev_cloud_time;
00104 bool first;
00105 boost::mutex callback_mutex;
00106
00107
00108 public:
00109 KinectEstimator() :
00110 use_backup_estimator_alt(true), imu_to_kinect_offset(0.08), max_est_kinect_delta_alt(0.05),
00111 obstacle_height_threshold(0.10), debug_throttle_rate(0), z_vel_filt_a(0.9), z_vel_filt_b(0.1),
00112 kinect_z(0), kinect_vz(0), max_queue_size(30), prev_cloud_time(0), first(true)
00113 {
00114 }
00115 private:
00116 void onInit()
00117 {
00118 nh = getMTNodeHandle();
00119 nh_priv = getMTPrivateNodeHandle();
00120
00121
00122 nh_priv.param("use_backup_estimator_alt", use_backup_estimator_alt, use_backup_estimator_alt);
00123 nh_priv.param("imu_to_kinect_offset", imu_to_kinect_offset, imu_to_kinect_offset);
00124 nh_priv.param("max_est_kinect_delta_alt", max_est_kinect_delta_alt, max_est_kinect_delta_alt);
00125 nh_priv.param("obstacle_height_threshold", obstacle_height_threshold, obstacle_height_threshold);
00126 nh_priv.param("debug_throttle_rate", debug_throttle_rate, debug_throttle_rate);
00127 nh_priv.param("z_vel_filt_a", z_vel_filt_a, z_vel_filt_a);
00128 nh_priv.param("z_vel_filt_b", z_vel_filt_b, z_vel_filt_b);
00129
00130 odom_pub = nh_priv.advertise<nav_msgs::Odometry> ("output", 10);
00131 obstacle_pub = nh_priv.advertise<starmac_kinect::Obstacle> ("obstacle", 10);
00132 debug_pub = nh_priv.advertise<starmac_kinect::Debug> ("debug", 10);
00133 marker_pub = nh_priv.advertise<visualization_msgs::Marker> ("marker", 10);
00134 mask_indices_pub = nh.advertise<pcl::PointIndices> ("mask_indices", 10, true);
00135 findplane_indices_pub = nh.advertise<pcl::PointIndices> ("findplane_indices", 10, true);
00136
00137 sub_input_filter.subscribe(nh_priv, "input", max_queue_size);
00138 sub_indices_filter.subscribe(nh_priv, "indices", max_queue_size);
00139 sub_model_filter.subscribe(nh_priv, "model", max_queue_size);
00140 sync_input_indices_e
00141 = make_shared<Synchronizer<ExactTime<PointCloud2, PointIndices, ModelCoefficients> > > (max_queue_size);
00142 sync_input_indices_e->connectInput(sub_input_filter, sub_indices_filter, sub_model_filter);
00143 sync_input_indices_e->registerCallback(bind(&KinectEstimator::cloudIndicesModelCallback, this, _1, _2, _3));
00144
00145
00146 odom_msg_output.child_frame_id = "imu";
00147 odom_msg_output.header.frame_id = "ned";
00148 odom_msg_output.pose.pose.orientation.x = 0;
00149 odom_msg_output.pose.pose.orientation.y = 0;
00150 odom_msg_output.pose.pose.orientation.z = 0;
00151 odom_msg_output.pose.pose.orientation.w = 1;
00152 odom_msg_output.pose.pose.position.x = 0;
00153 odom_msg_output.pose.pose.position.y = 0;
00154
00155 if (use_backup_estimator_alt)
00156 {
00157 est_odom_sub = nh.subscribe("estimator/output", 1, &KinectEstimator::estOdomCallback, this,
00158 ros::TransportHints().tcpNoDelay());
00159 }
00160
00161 updateMask();
00162
00163 }
00164 private:
00165
00166
00167 void cloudIndicesModelCallback(const sensor_msgs::PointCloud2ConstPtr& cloud_msg,
00168 const PointIndicesConstPtr& indices, const pcl::ModelCoefficientsConstPtr& model)
00169 {
00170 boost::mutex::scoped_lock lock (callback_mutex);
00171 NODELET_DEBUG_STREAM("Got cloud with timestamp " << cloud_msg->header.stamp << " + indices with timestamp "
00172 << indices->header.stamp << " + model with timestamp " << model->header.stamp);
00173
00174 double dt;
00175 if (first)
00176 {
00177 first = false;
00178 dt = 0;
00179 }
00180 else
00181 {
00182 dt = (cloud_msg->header.stamp - prev_cloud_time).toSec();
00183 prev_cloud_time = cloud_msg->header.stamp;
00184 }
00185 if (model->values.size() > 0)
00186 {
00187
00188 kinect_z = reject_outliers(-fabs(model->values[3])) - imu_to_kinect_offset;
00189 calcVelocity(kinect_z, dt, kinect_vz);
00190
00191 pcl::PointXYZ obstacle_location;
00192 bool obstacle_found = detectObstacle(cloud_msg, indices, model, obstacle_location);
00193 if (obstacle_found)
00194 {
00195 publishObstacleMarker(obstacle_location);
00196 NODELET_DEBUG("Detected obstacle at: [%f, %f, %f]", obstacle_location.x, obstacle_location.y,
00197 obstacle_location.z);
00198 }
00199 publishObstacle(obstacle_found, obstacle_location);
00200 }
00201 else
00202 {
00203 NODELET_WARN("No planar model found -- cannot determine altitude or obstacles");
00204 }
00205
00206 updateMask();
00207
00208 if (not use_backup_estimator_alt)
00209 {
00210 publishOdom();
00211 }
00212
00213 if (debug_throttle_rate > 0)
00214 {
00215 ros::Duration(1 / debug_throttle_rate).sleep();
00216 }
00217 }
00218
00219 bool detectObstacle(const sensor_msgs::PointCloud2ConstPtr& cloud_msg, const PointIndicesConstPtr& indices,
00220 const pcl::ModelCoefficientsConstPtr& model, pcl::PointXYZ& obstacle_location)
00221 {
00222 bool obstacle_found = false;
00223 int nPoints = cloud_msg->width * cloud_msg->height;
00224 int nIndices = int(indices->indices.size());
00225
00226
00227 static pcl::PointCloud<pcl::PointXYZ> cloud;
00228 pcl::fromROSMsg(*cloud_msg, cloud);
00229 int j = 0;
00230 int i = 0;
00231 float D;
00232 pcl::PointXYZ closest_outlier(0, 0, 9999);
00233
00234
00235 float n = sqrt(pow(model->values[0], 2) + pow(model->values[1], 2) + pow(model->values[2], 2));
00236 float a = model->values[0] / n;
00237 float b = model->values[1] / n;
00238 float c = model->values[2] / n;
00239 for (j = 0; j <= nIndices; j++)
00240 {
00241 while (i < (j < nIndices ? indices->indices[j] : nPoints))
00242 {
00243
00244
00245
00246 if (not (isnan(cloud.points[i].x) or isnan(cloud.points[i].y) or isnan(cloud.points[i].z)))
00247 {
00248 D = (a * cloud.points[i].x + b * cloud.points[i].y + c * cloud.points[i].z + model->values[3]);
00249 if (isnan(D))
00250 {
00251 NODELET_INFO("Candidate point has distance %f to plane", D);
00252 }
00253 else
00254 {
00255 if (fabs(D) > obstacle_height_threshold)
00256 if (cloud.points[i].z < closest_outlier.z)
00257 {
00258 closest_outlier = cloud.points[i];
00259 }
00260 }
00261 }
00262 i++;
00263 }
00264 i++;
00265 }
00266 obstacle_found = (closest_outlier.z < 9999);
00267 if (obstacle_found)
00268 {
00269 obstacle_location = closest_outlier;
00270 }
00271 return obstacle_found;
00272 }
00273
00274 void publishObstacleMarker(const pcl::PointXYZ& obstacle_location)
00275 {
00276 if (marker_pub.getNumSubscribers())
00277 {
00278 visualization_msgs::Marker obstacle;
00279 obstacle.header.frame_id = nh.getNamespace() + "/openni_depth_optical_frame";
00280 obstacle.header.stamp = ros::Time::now();
00281 obstacle.ns = "obstacle";
00282 obstacle.action = visualization_msgs::Marker::ADD;
00283 obstacle.pose.position.x = obstacle_location.x;
00284 obstacle.pose.position.y = obstacle_location.y;
00285 obstacle.pose.position.z = obstacle_location.z;
00286 obstacle.pose.orientation.w = 1.0;
00287 obstacle.id = 0;
00288 obstacle.type = visualization_msgs::Marker::SPHERE;
00289 obstacle.scale.x = 0.1;
00290 obstacle.scale.y = 0.1;
00291 obstacle.scale.z = 0.1;
00292 obstacle.color.r = 1.0;
00293 obstacle.color.g = 0.0;
00294 obstacle.color.b = 0.0;
00295 obstacle.color.a = 0.8;
00296 obstacle.lifetime = ros::Duration(1.0);
00297 marker_pub.publish(obstacle);
00298 }
00299 }
00300
00301 void updateMask()
00302 {
00303 pcl::PointIndices pi;
00304 static pcl::PointIndices findplane_indices;
00305 static bool sent_findplane_indices = false;
00306 int mask_centerx = 640 / 2;
00307 int mask_width = 200;
00308 int mask_topy = 120;
00309 int mask_height = 50;
00310 int mask_count = mask_width * mask_height;
00311 int mask_size = 0;
00312
00313
00314 mask_centerx = 640 / 2;
00315 mask_width = 160;
00316 mask_topy = mask_topy + mask_height;
00317 mask_height = 200;
00318 mask_count += (mask_width / 2) * (mask_height / 2);
00319 mask_size += appendToMask(pi.indices, mask_centerx - mask_width / 2, mask_topy, mask_width, mask_height, 8, 8);
00320 int num_findplane_indices = pi.indices.size();
00321
00322 static int sweep_y = 0;
00323 mask_width = 640;
00324 mask_height = 100;
00325
00326 mask_size += appendToMask(pi.indices, 0, sweep_y, mask_width, mask_height, 4, 20);
00327 mask_count += (mask_width / 4) * (mask_height / 20);
00328
00329 sweep_y = (sweep_y + 1) % 20;
00330 mask_indices_pub.publish(pi);
00331 if (not sent_findplane_indices)
00332 {
00333
00334 for (int i=0; i<num_findplane_indices;i++)
00335 {
00336 findplane_indices.indices.push_back(i);
00337 }
00338 findplane_indices.header.stamp = ros::Time::now();
00339 findplane_indices_pub.publish(findplane_indices);
00340 sent_findplane_indices = true;
00341 }
00342 }
00343
00344 double reject_outliers(double x)
00345 {
00346 const int window_size = 10;
00347 const double tolerance = 0.1;
00348 static vector<double> window;
00349 if (window.size() == 0)
00350 {
00351 window.resize(window_size);
00352 }
00353 double avg = 0;
00354 for (int i = 0; i < window_size; i++)
00355 {
00356 avg += window[i];
00357 }
00358 avg = avg / window_size;
00359 window.erase(window.begin());
00360 window.push_back(x);
00361 if (fabs(x - avg) > tolerance)
00362 {
00363 return avg;
00364 }
00365 else
00366 {
00367 return x;
00368 }
00369 }
00370
00371 int appendToMask(vector<int32_t>& indices, int x, int y, int w, int h, int x_incr = 1, int y_incr = 1)
00372 {
00373 int old_size = indices.size();
00374 indices.resize(old_size + w*h);
00375
00376 int p = old_size;
00377 for (int i = y; i < (y + h); i += y_incr)
00378 {
00379 for (int j = x; j < (x + w); j += x_incr)
00380 {
00381 indices[p] = 640 * i + j;
00382
00383 ++p;
00384 }
00385 }
00386
00387 indices.resize(p);
00388 return p - old_size;
00389 }
00390
00391
00392
00393
00394
00395
00396
00397 void estOdomCallback(const nav_msgs::OdometryConstPtr& odom_msg)
00398 {
00399 latest_est_odom_msg = *odom_msg;
00400
00401 double est_z = latest_est_odom_msg.pose.pose.position.z;
00402 double est_vz = latest_est_odom_msg.twist.twist.linear.z;
00403 double output_z;
00404 double output_vz;
00405 double est_kinect_delta = est_z - kinect_z;
00406 bool using_est = false;
00407 if (kinect_z > est_z + max_est_kinect_delta_alt)
00408 {
00409 output_z = est_z + max_est_kinect_delta_alt;
00410 output_vz = est_vz;
00411 ROS_DEBUG_STREAM("Kinect reading ABOVE estimator by " << fabs(est_kinect_delta) << "m, clipping.");
00412 using_est = true;
00413 }
00414 else if (kinect_z < est_z - max_est_kinect_delta_alt)
00415 {
00416 output_z = est_z - max_est_kinect_delta_alt;
00417 output_vz = est_vz;
00418 ROS_DEBUG_STREAM("Kinect reading BELOW estimator by " << fabs(est_kinect_delta) << "m, clipping.");
00419 using_est = true;
00420 }
00421 else
00422 {
00423 output_z = kinect_z;
00424 output_vz = kinect_vz;
00425 }
00426 odom_msg_output = *odom_msg;
00427 odom_msg_output.pose.pose.position.z = output_z;
00428 odom_msg_output.twist.twist.linear.z = output_vz;
00429
00430 odom_pub.publish(odom_msg_output);
00431 publishDebug(est_z, output_z, using_est);
00432 }
00433
00434 void calcVelocity(const double& current_z, const double dt, double& new_vz)
00435 {
00436 static double prev_z;
00437 static double prev_vz = 0;
00438 static bool first = true;
00439 if (first)
00440 {
00441 new_vz = 0;
00442 first = false;
00443 }
00444 else
00445 {
00446 if (dt > 0.0001)
00447 {
00448 new_vz = z_vel_filt_a * (current_z - prev_z) / dt + z_vel_filt_b * prev_vz;
00449 }
00450 else
00451 {
00452 ROS_WARN_STREAM("dt too small: " << dt);
00453 new_vz = prev_vz;
00454 }
00455 }
00456 prev_z = current_z;
00457 prev_vz = new_vz;
00458 }
00459
00460 void publishOdom()
00461 {
00462 odom_msg_output = nav_msgs::Odometry();
00463 odom_msg_output.pose.pose.position.z = kinect_z;
00464 odom_msg_output.twist.twist.linear.z = kinect_vz;
00465 odom_msg_output.pose.pose.orientation.x = 0.0;
00466 odom_msg_output.pose.pose.orientation.y = 0.0;
00467 odom_msg_output.pose.pose.orientation.z = 0.0;
00468 odom_msg_output.pose.pose.orientation.w = 1.0;
00469 odom_msg_output.header.stamp = ros::Time::now();
00470 odom_pub.publish(odom_msg_output);
00471 publishDebug(0, kinect_z, false);
00472 }
00473
00474 void publishObstacle(const bool obstacle_found, const pcl::PointXYZ& location)
00475 {
00476 starmac_kinect::Obstacle obs_msg;
00477 obs_msg.header.stamp = ros::Time::now();
00478 obs_msg.header.frame_id = nh.getNamespace() + "/kinect_depth";
00479 obs_msg.obstacle_found = obstacle_found;
00480 obs_msg.location.x = location.x;
00481 obs_msg.location.y = location.y;
00482 obs_msg.location.z = location.z;
00483 obstacle_pub.publish(obs_msg);
00484 }
00485
00486 void publishDebug(const double est_z, const double output_z, const bool using_est)
00487 {
00488 starmac_kinect::Debug debug;
00489 debug.est_z = est_z;
00490 debug.kinect_z = kinect_z;
00491 debug.out_z = output_z;
00492 debug.using_est = using_est;
00493 debug.header.stamp = odom_msg_output.header.stamp;
00494 debug_pub.publish(debug);
00495 }
00496 };
00497 PLUGINLIB_DECLARE_CLASS(starmac_kinect_estimator, KinectEstimator, starmac_kinect_estimator::KinectEstimator, nodelet::Nodelet)
00498 ;
00499 }