kinect_estimation.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2010, UC Regents
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of the University of California nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
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   // Params
00074   bool use_backup_estimator_alt; // use a backup source of altitude estimation?
00075   double imu_to_kinect_offset; // [m] distance from IMU to kinect sensor along Z-axis
00076   double max_est_kinect_delta_alt; // [m] maximum difference between backup altitude estimator and kinect altitude
00077   double obstacle_height_threshold; // [m] possible obstacle must be at least this far above identified plane
00078   double debug_throttle_rate; // [Hz] for debugging purposes, sleep in loop for this amount of time
00079   double z_vel_filt_a;
00080   double z_vel_filt_b;
00081   // Publishers
00082   ros::Publisher odom_pub; // nav_msgs/Odometry - primary output
00083   ros::Publisher obstacle_pub; // starmac_kinect/Obstacle - obstacle info
00084   ros::Publisher debug_pub; // starmac_kinect/Debug - debugging info
00085   ros::Publisher marker_pub; // visualization_msgs/Marker - obstacle marker for rviz
00086   ros::Publisher mask_indices_pub; // pcl/pointIndices - indices defining which points driver should output
00087   ros::Publisher findplane_indices_pub; // pcl/pointIndices - indices defining which points should be included in SACSegmentation
00088   // Message Filter stuff
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   // Subscribers
00094   //ros::Subscriber cloud_sub;
00095   //ros::Subscriber model_sub;
00096   ros::Subscriber est_odom_sub;
00097   // Members
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     // Parameters
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     // Publishers
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     // Subscribers
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     // Fill out most of the output Odometry message (we'll only be filling in the z pose value)
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(); // force once to start things
00162 
00163   }
00164 private:
00165   // What we want to do here is to look at all the points that are *not* included in the indices list,
00166   // (i.e. these are the outliers, the points not on the floor) and determine which are the closest to the camera
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       // Determine altitude:
00188       kinect_z = reject_outliers(-fabs(model->values[3])) - imu_to_kinect_offset;
00189       calcVelocity(kinect_z, dt, kinect_vz);
00190       // Detect obstacles:
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     //    NODELET_INFO("Cloud has %d points", nPoints);
00226     //    NODELET_INFO("There are %d inliers", nIndices);
00227     static pcl::PointCloud<pcl::PointXYZ> cloud;
00228     pcl::fromROSMsg(*cloud_msg, cloud); // expensive?
00229     int j = 0;
00230     int i = 0;
00231     float D;
00232     pcl::PointXYZ closest_outlier(0, 0, 9999);
00233     //    NODELET_INFO("Model: a=%f, b=%f, c=%f, d=%f", model->values[0], model->values[1], model->values[2],
00234     //                 model->values[3]);
00235     float n = sqrt(pow(model->values[0], 2) + pow(model->values[1], 2) + pow(model->values[2], 2)); // probably already normalized but what the hey
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         //        NODELET_INFO("Checking point %d: [%f, %f, %f], next skip at %d", i, cloud.points[i].x, cloud.points[i].y,
00244         //                     cloud.points[i].z, j);
00245         // First, see how far above the plane the candidate point is:
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     //mask_size += appendToMask(pi.indices, mask_centerx-mask_width/2, mask_topy, mask_width, mask_height, 4, 4); // to find ground plane
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); // add a bit more
00320     int num_findplane_indices = pi.indices.size();
00321 
00322     static int sweep_y = 0;
00323     mask_width = 640;
00324     mask_height = 100;
00325     //mask_count += mask_width * mask_height;
00326     mask_size += appendToMask(pi.indices, 0, sweep_y, mask_width, mask_height, 4, 20); // to watch out for obstacles
00327     mask_count += (mask_width / 4) * (mask_height / 20);
00328     //NODELET_INFO("Mask should be %d points, is %d points", mask_count, (int)pi.indices.size());
00329     sweep_y = (sweep_y + 1) % 20;
00330     mask_indices_pub.publish(pi); // send mask to driver
00331     if (not sent_findplane_indices)
00332     {
00333       // send indices for SACSegmentation
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; // [m]
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); //(w/x_incr * h/y_incr));
00375     // TODO: make this efficient again
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         //indices.push_back(640 * i + j);
00383         ++p;
00384       }
00385     }
00386     //NODELET_INFO("Added %d points to mask, and size was changed by %d", p - old_size, indices.size()-old_size);
00387     indices.resize(p);
00388     return p - old_size;
00389   }
00390 
00391   //  void modelCallback(const pcl::ModelCoefficientsConstPtr& model)
00392   //  {
00393   //    odom_msg_output.header.stamp = model->header.stamp;
00394   //    kinect_z = -fabs(model->values[3]) - imu_to_kinect_offset;
00395   //  }
00396 
00397   void estOdomCallback(const nav_msgs::OdometryConstPtr& odom_msg)
00398   {
00399     latest_est_odom_msg = *odom_msg; // do I really need to store this?
00400     // Just for clarity:
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; // start with VICON estimation
00427     odom_msg_output.pose.pose.position.z = output_z; // replace the z-value
00428     odom_msg_output.twist.twist.linear.z = output_vz;
00429     //odom_msg_output.header.stamp = odom_msg->header.stamp;
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 }


starmac_kinect
Author(s): bouffard
autogenerated on Sun Jan 5 2014 11:37:44