laser_height_estimation.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (BSD License)
00003  *
00004  * Copyright (c) 2010,
00005  *  Ivan Dryanovski <ivan.dryanovski@gmail.com>
00006  *  William Morris <morris@ee.ccny.cuny.edu>
00007  * All rights reserved.
00008  *
00009  * Redistribution and use in source and binary forms, with or without
00010  * modification, are permitted provided that the following conditions
00011  * are met:
00012  *
00013  *  * Redistributions of source code must retain the above copyright
00014  *    notice, this list of conditions and the following disclaimer.
00015  *  * Redistributions in binary form must reproduce the above
00016  *    copyright notice, this list of conditions and the following
00017  *    disclaimer in the documentation and/or other materials provided
00018  *    with the distribution.
00019  *  * Neither the name of CCNY Robotics Lab nor the names of its
00020  *    contributors may be used to endorse or promote products derived
00021  *    from this software without specific prior written permission.
00022  *
00023  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00024  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00025  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00026  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00027  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00028  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00029  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00030  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00032  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00033  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00034  * POSSIBILITY OF SUCH DAMAGE.
00035 */
00036 
00037 #include "laser_height_estimation/laser_height_estimation.h"
00038 
00039 namespace mav
00040 {
00041 
00042 LaserHeightEstimation::LaserHeightEstimation(ros::NodeHandle nh, ros::NodeHandle nh_private):
00043   nh_(nh),
00044   nh_private_(nh_private),
00045   first_time_(true)
00046 {
00047   ROS_INFO("Starting LaserHeightEstimation");
00048 
00049   initialized_  = false;
00050   floor_height_ = 0.0;
00051   prev_height_  = 0.0;
00052 
00053   world_to_base_.setIdentity();
00054 
00055   ros::NodeHandle nh_mav (nh_, "mav");
00056 
00057   // **** parameters
00058 
00059   if (!nh_private_.getParam ("fixed_frame", world_frame_))
00060       world_frame_ = "/world";
00061   if (!nh_private_.getParam ("base_frame", base_frame_))
00062     base_frame_ = "base_link";
00063   if (!nh_private_.getParam ("footprint_frame", footprint_frame_))
00064     footprint_frame_ = "base_footprint";
00065   if (!nh_private_.getParam ("min_values", min_values_))
00066     min_values_ = 5;
00067   if (!nh_private_.getParam ("max_stdev", max_stdev_))
00068     max_stdev_ = 0.10;
00069   if (!nh_private_.getParam ("max_height_jump", max_height_jump_))
00070     max_height_jump_ = 0.25;
00071   if (!nh_private_.getParam ("use_imu", use_imu_))
00072     use_imu_ = true;
00073   if (!nh_private_.getParam ("use_segmentation", use_segmentation_))
00074     use_segmentation_ = true;
00075   if (!nh_private_.getParam ("bin_size", bin_size_))
00076     bin_size_ = 0.02;
00077 
00078   // **** publishers
00079 
00080   height_to_base_publisher_ = nh_mav.advertise<mav_msgs::Height>(
00081     "height_to_base", 5);
00082   height_to_footprint_publisher_ = nh_mav.advertise<mav_msgs::Height>(
00083     "height_to_footprint", 5);
00084   floor_publisher_ = nh_mav.advertise<mav_msgs::Height>(
00085     "floor_height", 5);
00086 
00087   // **** subscribers
00088 
00089   scan_subscriber_ = nh_.subscribe(
00090     "scan", 5, &LaserHeightEstimation::scanCallback, this);
00091   if (use_imu_)
00092   {
00093     imu_subscriber_ = nh_.subscribe(
00094       "imu/data", 5, &LaserHeightEstimation::imuCallback, this);
00095   }
00096 }
00097 
00098 LaserHeightEstimation::~LaserHeightEstimation()
00099 {
00100   ROS_INFO("Destroying LaserHeightEstimation");
00101 }
00102 
00103 void LaserHeightEstimation::imuCallback (const sensor_msgs::ImuPtr& imu_msg)
00104 {
00105   mutex_.lock();
00106 
00107   tf::Quaternion q;
00108   tf::quaternionMsgToTF(imu_msg->orientation, q);   
00109   world_to_base_.setRotation(q);
00110 
00111   mutex_.unlock();
00112 }
00113 
00114 void LaserHeightEstimation::scanCallback (const sensor_msgs::LaserScanPtr& scan_msg)
00115 {
00116   if (!initialized_)
00117   {
00118     // if this is the first scan, lookup the static base to laser tf
00119     // if tf is not available yet, skip this scan
00120     if (!setBaseToLaserTf(scan_msg)) return;
00121 
00122     initialized_ = true;
00123     last_update_time_ = scan_msg->header.stamp;
00124 
00125     return;
00126   }
00127 
00128   // **** get required transforms
00129 
00130   if(!use_imu_)
00131   {
00132     tf::StampedTransform world_to_base_tf;
00133 
00134     try
00135     {
00136       tf_listener_.waitForTransform (
00137         world_frame_, base_frame_, scan_msg->header.stamp, ros::Duration(0.5));
00138       tf_listener_.lookupTransform (
00139         world_frame_, base_frame_, scan_msg->header.stamp, world_to_base_tf);
00140     }
00141     catch (tf::TransformException ex)
00142     {
00143       // transform unavailable - skip scan
00144       ROS_WARN ("TF unavailable, skipping scan (%s)", ex.what());
00145       return;
00146     }
00147     world_to_base_.setRotation( world_to_base_tf.getRotation());
00148   }
00149 
00150   mutex_.lock();
00151 
00152   tf::Transform rotated_laser     = world_to_base_ * base_to_laser_;
00153   tf::Transform rotated_footprint = world_to_base_ * base_to_footprint_;
00154 
00155   mutex_.unlock();
00156 
00157   // **** get vector of height values
00158 
00159   std::vector<double> values;
00160   for(unsigned int i = 0; i < scan_msg->ranges.size(); i++)
00161   {
00162     if (scan_msg->ranges[i] > scan_msg->range_min && scan_msg->ranges[i] < scan_msg->range_max)
00163     {
00164       double angle = scan_msg->angle_min + i * scan_msg->angle_increment;
00165       tf::Vector3 v(cos(angle)*scan_msg->ranges[i], sin(angle)*scan_msg->ranges[i], 0.0);
00166       tf::Vector3 p = rotated_laser * v;
00167       values.push_back(p.getZ());
00168     }
00169   }
00170 
00171   // **** segment if required
00172 
00173   if (!values.empty() && use_segmentation_)
00174   {
00175     std::vector<double> values_seg;
00176     //kMeansSegmentation(values, values_seg);
00177     histogramSegmentation(values, values_seg);
00178     values = values_seg;
00179   }
00180 
00181   // **** check if we have enough values present
00182 
00183   if ((int)values.size() < min_values_)
00184   {
00185     ROS_WARN("Not enough valid values to determine height, skipping (%d collected, %d needed)",
00186       values.size(), min_values_);
00187     return;
00188   }
00189 
00190   // **** get mean and standard dev
00191 
00192   double mean_value, stdev_value;
00193   getStats(values, mean_value, stdev_value);
00194 
00195   if (stdev_value > max_stdev_)
00196   {
00197     ROS_WARN("Stdev of height readings too big to determine height, skipping (stdev is %f, max is %f)",
00198       stdev_value, max_stdev_);
00199     return;
00200   }
00201 
00202   // **** estimate height (to base and to footprint)
00203 
00204   double height_to_base = 0.0 - mean_value + floor_height_;
00205   double height_to_footprint = rotated_footprint.getOrigin().getZ() - mean_value + floor_height_;
00206 
00207   // **** check for discontinuity
00208 
00209   // prevent first-time discontinuities
00210   if (first_time_)
00211   {
00212     first_time_ = false;
00213     prev_height_ = height_to_base;
00214   }
00215 
00216   double height_jump = prev_height_ - height_to_base;
00217 
00218   if (fabs(height_jump) > max_height_jump_)
00219   {
00220     ROS_WARN("Laser height estimation: floor discontinuity detected");
00221     floor_height_ += height_jump;
00222     height_to_base += height_jump;
00223     height_to_footprint += height_jump;
00224   }
00225 
00226   double dt = (scan_msg->header.stamp - last_update_time_).toSec();
00227 
00228   double climb = (height_to_base - prev_height_)/dt;
00229   prev_height_ = height_to_base;
00230   last_update_time_ = scan_msg->header.stamp;
00231 
00232   // **** publish height message
00233 
00234   mav_msgs::HeightPtr height_to_base_msg;
00235   height_to_base_msg = boost::make_shared<mav_msgs::Height>();
00236   height_to_base_msg->height = height_to_base;
00237   height_to_base_msg->height_variance = stdev_value;
00238   height_to_base_msg->climb = climb;
00239   height_to_base_msg->header.stamp = scan_msg->header.stamp;
00240   height_to_base_msg->header.frame_id = scan_msg->header.frame_id;  //TODO:wrong frame
00241   height_to_base_publisher_.publish(height_to_base_msg);
00242 
00243   mav_msgs::HeightPtr height_to_footprint_msg;
00244   height_to_footprint_msg = boost::make_shared<mav_msgs::Height>();
00245   height_to_footprint_msg->height = height_to_footprint;
00246   height_to_footprint_msg->height_variance = stdev_value;
00247   height_to_footprint_msg->climb = climb;
00248   height_to_footprint_msg->header.stamp = scan_msg->header.stamp;
00249   height_to_footprint_msg->header.frame_id = scan_msg->header.frame_id; //TODO:wrong frame
00250   height_to_footprint_publisher_.publish(height_to_footprint_msg);
00251 
00252   mav_msgs::HeightPtr floor_msg;
00253   floor_msg = boost::make_shared<mav_msgs::Height>();
00254   floor_msg->height = floor_height_;
00255   floor_msg->header.stamp = scan_msg->header.stamp;
00256   floor_msg->header.frame_id = scan_msg->header.frame_id; //TODO:wrong frame
00257   floor_publisher_.publish(floor_msg);
00258 }
00259 
00260 bool LaserHeightEstimation::setBaseToLaserTf(const sensor_msgs::LaserScanPtr& scan_msg)
00261 {
00262   ros::Time time = scan_msg->header.stamp;
00263 
00264   // **** get transform base to laser
00265 
00266   tf::StampedTransform base_to_laser_tf;
00267   try
00268   {
00269     tf_listener_.waitForTransform(
00270       base_frame_, scan_msg->header.frame_id, time, ros::Duration(3.0));
00271 
00272     tf_listener_.lookupTransform (
00273       base_frame_, scan_msg->header.frame_id, time, base_to_laser_tf);
00274   }
00275   catch (tf::TransformException ex)
00276   {
00277     // transform unavailable - skip scan
00278     ROS_WARN("%s: Transform unavailable, skipping scan (%s)", ros::this_node::getName().c_str(), ex.what());
00279     return false;
00280   }
00281 
00282   base_to_laser_ = base_to_laser_tf;
00283 
00284   // **** get transform base to base_footprint
00285 
00286   tf::StampedTransform base_to_footprint_tf;
00287   try
00288   {
00289     tf_listener_.waitForTransform(
00290       base_frame_, footprint_frame_, time, ros::Duration(3.0));
00291 
00292     tf_listener_.lookupTransform (
00293       base_frame_, footprint_frame_, time, base_to_footprint_tf);
00294   }
00295   catch (tf::TransformException ex)
00296   {
00297     // transform unavailable - skip scan
00298     ROS_WARN("%s: Transform unavailable, skipping scan (%s)", ros::this_node::getName().c_str(), ex.what());
00299     return false;
00300   }
00301 
00302   base_to_footprint_ = base_to_footprint_tf;
00303 
00304   return true;
00305 }
00306 
00307 void LaserHeightEstimation::getStats(const std::vector<double> values, double& ave, double& stdev)
00308 {
00309   double sum   = 0.0;
00310   double sumsq = 0.0;
00311 
00312   for (size_t i = 0; i < values.size(); ++i)
00313     sum += values[i];
00314 
00315   ave = sum/values.size();
00316 
00317   for (size_t i = 0; i < values.size(); ++i)
00318     sumsq += (values[i] - ave) * (values[i] - ave);
00319 
00320   stdev = sqrt(sumsq/values.size());
00321 }
00322 
00323 void LaserHeightEstimation::histogramSegmentation(
00324   const std::vector<double>& input,
00325   std::vector<double>& output)
00326 {
00327   // **** get min and max bounds
00328 
00329   double min_value = input[0];
00330   double max_value = input[0];
00331   for (unsigned int i = 0; i < input.size(); ++i)
00332   {
00333     if (input[i] < min_value) min_value = input[i];
00334     if (input[i] > max_value) max_value = input[i];
00335   }  
00336 
00337   // **** create histogram
00338 
00339   int n_bins = (max_value - min_value) / bin_size_ + 1;
00340   std::vector<int> bins;
00341   bins.resize(n_bins);
00342 
00343   for (unsigned int i = 0; i < bins.size(); ++i)
00344     bins[i] = 0; 
00345 
00346   for (unsigned int i = 0; i < input.size(); ++i)
00347   { 
00348     unsigned int bin_idx = (input[i] - min_value) / bin_size_;
00349     bins[bin_idx]++;
00350   } 
00351 
00352   // **** get max of histogram
00353 
00354   unsigned int best_bin_idx = 0;
00355   int best_bin_count = 0;
00356   for (unsigned int i = 0; i < bins.size(); ++i)
00357   { 
00358     if (bins[i] > best_bin_count)
00359     {
00360       best_bin_count = bins[i];
00361       best_bin_idx = i;
00362     }
00363   }
00364 
00365   // **** find inliers
00366  
00367   output.clear();
00368   for (unsigned int i = 0; i < input.size(); ++i)
00369   {
00370     unsigned int bin_idx = (input[i] - min_value) / bin_size_;
00371     if (bin_idx == best_bin_idx)
00372       output.push_back(input[i]);
00373   }
00374 }
00375 
00376 void LaserHeightEstimation::kMeansSegmentation(
00377   const std::vector<double>& input,
00378   std::vector<double>& output)
00379 {
00380   // **** initialize segmentation with endpoint values
00381 
00382   double mean_a = input[0];
00383   double mean_b = input[input.size() - 1];
00384   int count_a, count_b;
00385 
00386   std::vector<bool> mask;
00387   mask.resize(input.size());
00388   for(unsigned int i = 0; i < mask.size()-1; ++i) 
00389     mask[i] = true;
00390   mask[mask.size()-1] = false;
00391   
00392   // **** perform k-means until convergence
00393 
00394   bool converged = false;
00395   while (!converged)
00396   {
00397     converged = kMeansSegmentation(
00398       input, mask, mean_a, mean_b, count_a, count_b);
00399   }
00400 
00401   if (std::abs(mean_a - mean_b) > max_height_jump_)
00402     ROS_WARN("Floor discontinuity in clusters");
00403 
00404   // **** use whichever cluster has more elements
00405 
00406   if (count_a >= count_b)
00407   {
00408     for (unsigned int i = 0; i < input.size(); ++i)
00409       if (mask[i]) output.push_back(input[i]);
00410   }
00411   else
00412   {
00413     for (unsigned int i = 0; i < input.size(); ++i)
00414       if (!mask[i]) output.push_back(input[i]);
00415   }
00416 }
00417 
00418 bool LaserHeightEstimation::kMeansSegmentation(
00419   const std::vector<double>& input,
00420   std::vector<bool>& mask,
00421   double& mean_a, double& mean_b,
00422   int& count_a, int& count_b)
00423 {
00424   // **** assign each index to a cluster (A or B), and check 
00425   //      for mask changes (true = belongs to A)
00426   
00427   bool converged = true;
00428   double sum_a = 0.0;
00429   double sum_b = 0.0;
00430   count_a = 0;
00431   count_b = 0;
00432 
00433   for (unsigned int i = 0; i < input.size(); ++i)
00434   {
00435     double da2 = (input[i] - mean_a) * (input[i] - mean_a);
00436     double db2 = (input[i] - mean_b) * (input[i] - mean_b);
00437 
00438     if (da2 <= db2)
00439     {
00440       // closer to A
00441       if (!mask[i]) converged = false;
00442       mask[i] = true;
00443       sum_a += input[i];
00444       ++count_a;
00445     }
00446     else
00447     {
00448       // closer to B
00449       if (mask[i]) converged = false;
00450       mask[i] = false;
00451       sum_b += input[i];
00452       ++count_b;
00453     }
00454   }
00455 
00456   // **** recompute means
00457 
00458   if (count_a == 0 || count_b == 0) return true;
00459 
00460   mean_a = sum_a / (double)count_a;
00461   mean_b = sum_b / (double)count_b;
00462 
00463   return converged;
00464 }
00465 
00466 
00467 };// namespace mav


laser_height_estimation
Author(s): Ivan Dryanovski
autogenerated on Thu Jan 2 2014 11:28:33