tabletop_segmentation.cpp
Go to the documentation of this file.
00001 
00002 /*********************************************************************
00003 *
00004 *  Copyright (c) 2009, Willow Garage, Inc.
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 Willow Garage 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   
00035 // Author(s): Marius Muja and Matei Ciocarlie
00036 
00037 #include <string>
00038 
00039 #include <ros/ros.h>
00040 
00041 #include <sensor_msgs/PointCloud.h>
00042 #include <sensor_msgs/PointCloud2.h>
00043 #include <sensor_msgs/point_cloud_conversion.h>
00044 #include <visualization_msgs/Marker.h>
00045 
00046 #include <tf/transform_listener.h>
00047 #include <tf/transform_broadcaster.h>
00048 #include <tf/transform_datatypes.h>
00049 
00050 #include <pcl/point_cloud.h>
00051 #include <pcl/point_types.h>
00052 #include <pcl/io/io.h>
00053 #include <pcl/filters/voxel_grid.h>
00054 #include <pcl/filters/passthrough.h>
00055 #include <pcl/filters/extract_indices.h>
00056 #include <pcl/features/normal_3d.h>
00057 #include <pcl/sample_consensus/method_types.h>
00058 #include <pcl/sample_consensus/model_types.h>
00059 #include <pcl/segmentation/sac_segmentation.h>
00060 #include <pcl/filters/project_inliers.h>
00061 #include <pcl/surface/convex_hull.h>
00062 #include <pcl/search/kdtree.h>
00063 #include <pcl/segmentation/extract_polygonal_prism_data.h>
00064 #include <pcl/segmentation/extract_clusters.h>
00065 #include <pcl_ros/transforms.h>
00066 
00067 #include "tabletop_object_detector/marker_generator.h"
00068 #include "tabletop_object_detector/TabletopSegmentation.h"
00069 
00070 namespace tabletop_object_detector {
00071 
00072 class TabletopSegmentor 
00073 {
00074   typedef pcl::PointXYZRGB    Point;
00075   typedef pcl::search::KdTree<Point>::Ptr KdTreePtr;
00076   
00077 private:
00079   ros::NodeHandle nh_;
00081   ros::NodeHandle priv_nh_;
00083   ros::Publisher marker_pub_;
00085   ros::ServiceServer segmentation_srv_;
00086 
00088   int num_markers_published_;
00090   int current_marker_id_;
00091 
00093   int inlier_threshold_;
00095   double plane_detection_voxel_size_;
00097   double clustering_voxel_size_;
00099   double z_filter_min_, z_filter_max_;
00100   double y_filter_min_, y_filter_max_;
00101   double x_filter_min_, x_filter_max_;
00103   double table_z_filter_min_, table_z_filter_max_;
00105   double cluster_distance_;
00107   int min_cluster_size_;
00110   std::string processing_frame_;
00112   double up_direction_;
00113   bool flatten_table_;
00115   double table_padding_;
00116 
00118   tf::TransformListener listener_;
00119 
00120   //------------------ Callbacks -------------------
00121 
00123   bool serviceCallback(TabletopSegmentation::Request &request, TabletopSegmentation::Response &response);
00124 
00125   //------------------ Individual processing steps -------
00126 
00128   template <class PointCloudType>
00129   Table getTable(std_msgs::Header cloud_header, const tf::Transform &table_plane_trans,
00130                  const PointCloudType &table_points);
00131 
00133   template <class PointCloudType>
00134   void addConvexHullTable(Table &table, const PointCloudType &convex_hull, bool flatten_table);
00135 
00137   template <class PointCloudType>
00138   void publishClusterMarkers(const std::vector<PointCloudType> &clusters, std_msgs::Header cloud_header);
00139   
00140   //------------------- Complete processing -----
00141 
00143   void processCloud(const sensor_msgs::PointCloud2 &cloud,
00144                     TabletopSegmentation::Response &response, 
00145                     Table table);
00146   
00148   void clearOldMarkers(std::string frame_id);
00149 
00151   template <class PointCloudType>
00152   bool tableMsgToPointCloud (Table &table, std::string frame_id, PointCloudType &table_hull);
00153 
00154 public:
00156 
00157   TabletopSegmentor(ros::NodeHandle nh) : nh_(nh), priv_nh_("~")
00158   {
00159     num_markers_published_ = 1;
00160     current_marker_id_ = 1;
00161 
00162     marker_pub_ = nh_.advertise<visualization_msgs::Marker>(nh_.resolveName("markers_out"), 10);
00163 
00164     segmentation_srv_ = nh_.advertiseService(nh_.resolveName("segmentation_srv"), 
00165                                              &TabletopSegmentor::serviceCallback, this);
00166 
00167     //initialize operational flags
00168     priv_nh_.param<int>("inlier_threshold", inlier_threshold_, 300);
00169     priv_nh_.param<double>("plane_detection_voxel_size", plane_detection_voxel_size_, 0.01);
00170     priv_nh_.param<double>("clustering_voxel_size", clustering_voxel_size_, 0.003);
00171     priv_nh_.param<double>("z_filter_min", z_filter_min_, 0.4);
00172     priv_nh_.param<double>("z_filter_max", z_filter_max_, 1.25);
00173     priv_nh_.param<double>("y_filter_min", y_filter_min_, -1.0);
00174     priv_nh_.param<double>("y_filter_max", y_filter_max_, 1.0);
00175     priv_nh_.param<double>("x_filter_min", x_filter_min_, -1.0);
00176     priv_nh_.param<double>("x_filter_max", x_filter_max_, 1.0);
00177     priv_nh_.param<double>("table_z_filter_min", table_z_filter_min_, 0.01);
00178     priv_nh_.param<double>("table_z_filter_max", table_z_filter_max_, 0.50);
00179     priv_nh_.param<double>("cluster_distance", cluster_distance_, 0.03);
00180     priv_nh_.param<int>("min_cluster_size", min_cluster_size_, 300);
00181     priv_nh_.param<std::string>("processing_frame", processing_frame_, "");
00182     priv_nh_.param<double>("up_direction", up_direction_, -1.0);   
00183     priv_nh_.param<bool>("flatten_table", flatten_table_, false);   
00184     priv_nh_.param<double>("table_padding", table_padding_, 0.0);   
00185     if(flatten_table_) ROS_DEBUG("flatten_table is true");
00186     else ROS_DEBUG("flatten_table is false");
00187 
00188   }
00189 
00191   ~TabletopSegmentor() {}
00192 };
00193 
00196 bool TabletopSegmentor::serviceCallback(TabletopSegmentation::Request &request, 
00197                                         TabletopSegmentation::Response &response)
00198 {
00199   ros::Time start_time = ros::Time::now();
00200   std::string topic = nh_.resolveName("cloud_in");
00201   ROS_INFO("Tabletop detection service called; waiting for a point_cloud2 on topic %s", topic.c_str());
00202 
00203   sensor_msgs::PointCloud2::ConstPtr recent_cloud = 
00204     ros::topic::waitForMessage<sensor_msgs::PointCloud2>(topic, nh_, ros::Duration(3.0));
00205               
00206   if (!recent_cloud)
00207   {
00208     ROS_ERROR("Tabletop object detector: no point_cloud2 has been received");
00209     response.result = response.NO_CLOUD_RECEIVED;
00210     return true;
00211   }
00212 
00213   pcl::PointCloud<Point>::Ptr table_hull (new pcl::PointCloud<Point>);
00214   ROS_INFO_STREAM("Point cloud received after " << ros::Time::now() - start_time << " seconds; processing");
00215   if (!processing_frame_.empty())
00216   {
00217     //convert cloud to processing_frame_ (usually base_link)
00218     sensor_msgs::PointCloud old_cloud;  
00219     sensor_msgs::convertPointCloud2ToPointCloud (*recent_cloud, old_cloud);
00220     int current_try=0, max_tries = 3;
00221     while (1)
00222     {
00223       bool transform_success = true;
00224       try
00225       {
00226         listener_.transformPointCloud(processing_frame_, old_cloud, old_cloud);    
00227       }
00228       catch (tf::TransformException ex)
00229       {
00230         transform_success = false;
00231         if (++current_try >= max_tries)
00232         {
00233           ROS_ERROR("Failed to transform cloud from frame %s into frame %s in %d attempt(s)", old_cloud.header.frame_id.c_str(), 
00234                     processing_frame_.c_str(), current_try);
00235           response.result = response.OTHER_ERROR;
00236           return true;
00237         }
00238         ROS_DEBUG("Failed to transform point cloud, attempt %d out of %d, exception: %s", current_try, max_tries, ex.what());
00239         //sleep a bit to give the listener a chance to get a new transform
00240         ros::Duration(0.1).sleep();
00241       }
00242       if (transform_success) break;
00243     }
00244     sensor_msgs::PointCloud2 converted_cloud;
00245     sensor_msgs::convertPointCloudToPointCloud2 (old_cloud, converted_cloud);
00246     ROS_INFO_STREAM("Input cloud converted to " << processing_frame_ << " frame after " <<
00247                     ros::Time::now() - start_time << " seconds");
00248     processCloud(converted_cloud, response, request.table);
00249     clearOldMarkers(converted_cloud.header.frame_id);
00250   }
00251   else
00252   {
00253     processCloud(*recent_cloud, response, request.table);
00254     clearOldMarkers(recent_cloud->header.frame_id);
00255   }
00256 
00257   //add the timestamp from the original cloud
00258   response.table.pose.header.stamp = recent_cloud->header.stamp;
00259   for(size_t i; i<response.clusters.size(); i++)
00260   {
00261     response.clusters[i].header.stamp = recent_cloud->header.stamp;
00262   }
00263 
00264   ROS_INFO_STREAM("In total, segmentation took " << ros::Time::now() - start_time << " seconds");
00265   return true;
00266 }
00267 
00268 template <class PointCloudType>
00269 void TabletopSegmentor::addConvexHullTable(Table &table,
00270                                            const PointCloudType &convex_hull,
00271                                            bool flatten_table)
00272 {
00273   if (convex_hull.points.empty())
00274   {
00275     ROS_ERROR("Trying to add convex hull, but it contains no points");
00276     return;
00277   }
00278   //compute centroid
00279   geometry_msgs::Point centroid;
00280   centroid.x = centroid.y = centroid.z = 0.0;
00281   for (size_t i=0; i<convex_hull.points.size(); i++)
00282   {
00283     centroid.x += convex_hull.points[i].x;
00284     centroid.y += convex_hull.points[i].y;
00285     centroid.z += convex_hull.points[i].z;
00286   }
00287   centroid.x /= convex_hull.points.size();
00288   centroid.y /= convex_hull.points.size();
00289   centroid.z /= convex_hull.points.size();
00290 
00291   //create a triangle mesh out of the convex hull points and add it to the table message
00292   table.convex_hull.type = table.convex_hull.MESH;
00293   for (size_t i=0; i<convex_hull.points.size(); i++)
00294   {
00295     geometry_msgs::Point vertex;
00296     vertex.x = convex_hull.points[i].x;
00297     vertex.y = convex_hull.points[i].y;
00298     if (table_padding_ > 0.0)
00299     {
00300       double dx = vertex.x - centroid.x;
00301       double dy = vertex.y - centroid.y;
00302       double l = sqrt(dx*dx + dy*dy);
00303       dx /= l; dy /= l;
00304       vertex.x += table_padding_ * dx;
00305       vertex.y += table_padding_ * dy;
00306     }
00307     if(flatten_table) vertex.z = 0;
00308     else vertex.z = convex_hull.points[i].z;
00309     table.convex_hull.vertices.push_back(vertex);
00310       
00311     if(i==0 || i==convex_hull.points.size()-1) continue;
00312     table.convex_hull.triangles.push_back(0);
00313     table.convex_hull.triangles.push_back(i);
00314     table.convex_hull.triangles.push_back(i+1);
00315   }
00316   visualization_msgs::Marker tableMarker = MarkerGenerator::getConvexHullTableMarker(table.convex_hull);
00317   tableMarker.header = table.pose.header;
00318   tableMarker.pose = table.pose.pose;
00319   tableMarker.ns = "tabletop_node";
00320   tableMarker.id = current_marker_id_++;
00321   marker_pub_.publish(tableMarker);
00322 
00323   visualization_msgs::Marker originMarker = 
00324     MarkerGenerator::createMarker(table.pose.header.frame_id, 0, .0025, .0025, .01, 0, 1, 1, 
00325                                   visualization_msgs::Marker::CUBE, current_marker_id_++, 
00326                                   "tabletop_node", table.pose.pose);
00327   marker_pub_.publish(originMarker);
00328 }
00329 
00330 template <class PointCloudType>
00331 Table TabletopSegmentor::getTable(std_msgs::Header cloud_header,
00332                                   const tf::Transform &table_plane_trans, 
00333                                   const PointCloudType &table_points)
00334 {
00335   Table table;
00336  
00337   //get the extents of the table
00338   if (!table_points.points.empty()) 
00339   {
00340     table.x_min = table_points.points[0].x;
00341     table.x_max = table_points.points[0].x;
00342     table.y_min = table_points.points[0].y;
00343     table.y_max = table_points.points[0].y;
00344   }  
00345   for (size_t i=1; i<table_points.points.size(); ++i) 
00346   {
00347     if (table_points.points[i].x<table.x_min && table_points.points[i].x>-3.0) table.x_min = table_points.points[i].x;
00348     if (table_points.points[i].x>table.x_max && table_points.points[i].x< 3.0) table.x_max = table_points.points[i].x;
00349     if (table_points.points[i].y<table.y_min && table_points.points[i].y>-3.0) table.y_min = table_points.points[i].y;
00350     if (table_points.points[i].y>table.y_max && table_points.points[i].y< 3.0) table.y_max = table_points.points[i].y;
00351   }
00352 
00353   geometry_msgs::Pose table_pose;
00354   tf::poseTFToMsg(table_plane_trans, table_pose);
00355   table.pose.pose = table_pose;
00356   table.pose.header = cloud_header;
00357 
00358   
00359   visualization_msgs::Marker tableMarker = MarkerGenerator::getTableMarker(table.x_min, table.x_max,
00360                                                                            table.y_min, table.y_max);
00361   tableMarker.header = cloud_header;
00362   tableMarker.pose = table_pose;
00363   tableMarker.ns = "tabletop_node";
00364   tableMarker.id = current_marker_id_++;
00365   marker_pub_.publish(tableMarker);
00366   
00367   return table;
00368 }
00369 
00370 template <class PointCloudType>
00371 void TabletopSegmentor::publishClusterMarkers(const std::vector<PointCloudType> &clusters, std_msgs::Header cloud_header)
00372 {
00373   for (size_t i=0; i<clusters.size(); i++) 
00374   {
00375     visualization_msgs::Marker cloud_marker =  MarkerGenerator::getCloudMarker(clusters[i]);
00376     cloud_marker.header = cloud_header;
00377     cloud_marker.pose.orientation.w = 1;
00378     cloud_marker.ns = "tabletop_node";
00379     cloud_marker.id = current_marker_id_++;
00380     marker_pub_.publish(cloud_marker);
00381   }
00382 }
00383 
00384 void TabletopSegmentor::clearOldMarkers(std::string frame_id)
00385 {
00386   for (int id=current_marker_id_; id < num_markers_published_; id++)
00387     {
00388       visualization_msgs::Marker delete_marker;
00389       delete_marker.header.stamp = ros::Time::now();
00390       delete_marker.header.frame_id = frame_id;
00391       delete_marker.id = id;
00392       delete_marker.action = visualization_msgs::Marker::DELETE;
00393       delete_marker.ns = "tabletop_node";
00394       marker_pub_.publish(delete_marker);
00395     }
00396   num_markers_published_ = current_marker_id_;
00397   current_marker_id_ = 0;
00398 }
00399 
00401 tf::Transform getPlaneTransform (pcl::ModelCoefficients coeffs, double up_direction, bool flatten_plane)
00402 {
00403   ROS_ASSERT(coeffs.values.size() > 3);
00404   double a = coeffs.values[0], b = coeffs.values[1], c = coeffs.values[2], d = coeffs.values[3];
00405   //asume plane coefficients are normalized
00406   tf::Vector3 position(-a*d, -b*d, -c*d);
00407   tf::Vector3 z(a, b, c);
00408 
00409   //if we are flattening the plane, make z just be (0,0,up_direction)
00410   if(flatten_plane)
00411   {
00412     ROS_INFO("flattening plane");
00413     z[0] = z[1] = 0;
00414     z[2] = up_direction;
00415   }
00416   else
00417   {
00418     //make sure z points "up"
00419     ROS_DEBUG("in getPlaneTransform, z: %0.3f, %0.3f, %0.3f", z[0], z[1], z[2]);
00420     if ( z.dot( tf::Vector3(0, 0, up_direction) ) < 0)
00421     {
00422       z = -1.0 * z;
00423       ROS_INFO("flipped z");
00424     }
00425   }
00426     
00427   //try to align the x axis with the x axis of the original frame
00428   //or the y axis if z and x are too close too each other
00429   tf::Vector3 x(1, 0, 0);
00430   if ( fabs(z.dot(x)) > 1.0 - 1.0e-4) x = tf::Vector3(0, 1, 0);
00431   tf::Vector3 y = z.cross(x).normalized();
00432   x = y.cross(z).normalized();
00433 
00434   tf::Matrix3x3 rotation;
00435   rotation[0] = x;      // x
00436   rotation[1] = y;      // y
00437   rotation[2] = z;      // z
00438   rotation = rotation.transpose();
00439   tf::Quaternion orientation;
00440   rotation.getRotation(orientation);
00441   ROS_DEBUG("in getPlaneTransform, x: %0.3f, %0.3f, %0.3f", x[0], x[1], x[2]);
00442   ROS_DEBUG("in getPlaneTransform, y: %0.3f, %0.3f, %0.3f", y[0], y[1], y[2]);
00443   ROS_DEBUG("in getPlaneTransform, z: %0.3f, %0.3f, %0.3f", z[0], z[1], z[2]);
00444   return tf::Transform(orientation, position);
00445 }
00446 
00447 template <class PointCloudType>
00448 bool TabletopSegmentor::tableMsgToPointCloud (Table &table, std::string frame_id, PointCloudType &table_hull)
00449 {
00450   //use table.pose (PoseStamped) to transform table.convex_hull.vertices (Point[]) into a pcl::PointCloud in frame_id
00451   ros::Time now = ros::Time::now();
00452   PointCloudType table_frame_points;
00453   table_frame_points.header.stamp = now;
00454   table_frame_points.header.frame_id = "table_frame";
00455   table_frame_points.points.resize(table.convex_hull.vertices.size());
00456   for(size_t i=0; i < table.convex_hull.vertices.size(); i++)
00457   {
00458     table_frame_points.points[i].x = table.convex_hull.vertices[i].x;
00459     table_frame_points.points[i].y = table.convex_hull.vertices[i].y;
00460     table_frame_points.points[i].z = table.convex_hull.vertices[i].z;
00461   }
00462 
00463   //add the table frame transform to the tf listener
00464   tf::Transform table_trans;
00465   tf::poseMsgToTF(table.pose.pose, table_trans);
00466   tf::StampedTransform table_pose_frame(table_trans, now, table.pose.header.frame_id, "table_frame");
00467   listener_.setTransform(table_pose_frame);
00468   ROS_INFO("transforming point cloud from table frame to frame %s", frame_id.c_str());
00469 
00470   //make sure we can transform
00471   std::string error_msg;
00472   if (!listener_.waitForTransform(frame_id, "table_frame", now, ros::Duration(2.0), ros::Duration(0.01), &error_msg))
00473   {
00474     ROS_ERROR("Can not transform point cloud from table frame to frame %s; error %s", 
00475               frame_id.c_str(), error_msg.c_str());
00476     return false;
00477   }
00478 
00479   //transform the points
00480   int current_try=0, max_tries = 3;
00481   while (1)
00482   {
00483     bool transform_success = true;
00484     try
00485     {
00486       pcl_ros::transformPointCloud<Point>(frame_id, table_frame_points, table_hull, listener_); 
00487     }
00488     catch (tf::TransformException ex)
00489     {
00490       transform_success = false;
00491       if ( ++current_try >= max_tries )
00492       {
00493         ROS_ERROR("Failed to transform point cloud from table frame into frame %s; error %s", 
00494                   frame_id.c_str(), ex.what());
00495         return false;
00496       }
00497       //sleep a bit to give the listener a chance to get a new transform
00498       ros::Duration(0.1).sleep();
00499     }
00500     if (transform_success) break;
00501   }
00502   table_hull.header.frame_id = frame_id;
00503   table_hull.header.stamp = now;
00504 
00505   //copy the transformed points back into the Table message and set the pose to identity in the cloud frame
00506   for(size_t i=0; i < table.convex_hull.vertices.size(); i++)
00507   {
00508     table.convex_hull.vertices[i].x = table_hull.points[i].x;
00509     table.convex_hull.vertices[i].y = table_hull.points[i].y;
00510     table.convex_hull.vertices[i].z = table_hull.points[i].z;
00511   }
00512   geometry_msgs::PoseStamped iden;
00513   iden.pose.orientation.w = 1;
00514   table.pose = iden;
00515   table.pose.header.frame_id = frame_id;
00516 
00517   //make a new Shape for drawing
00518   arm_navigation_msgs::Shape mesh;
00519   mesh.vertices.resize(table_hull.points.size());
00520   for(size_t i = 0; i < table_hull.points.size(); i++)
00521   {
00522     mesh.vertices[i].x = table_hull.points[i].x;
00523     mesh.vertices[i].y = table_hull.points[i].y;
00524     mesh.vertices[i].z = table_hull.points[i].z;
00525   }
00526   mesh.triangles = table.convex_hull.triangles;
00527   visualization_msgs::Marker tableMarker = MarkerGenerator::getConvexHullTableMarker(mesh);
00528   tableMarker.header = table_hull.header;
00529   tableMarker.pose.orientation.w = 1.0;
00530   tableMarker.ns = "tabletop_node";
00531   tableMarker.id = current_marker_id_++;
00532   marker_pub_.publish(tableMarker);
00533 
00534   return true;
00535 }
00536 
00537 
00538 template <typename PointT> 
00539 bool getPlanePoints (const pcl::PointCloud<PointT> &table, 
00540                      const tf::Transform& table_plane_trans,
00541                      sensor_msgs::PointCloud &table_points)
00542 {
00543   // Prepare the output
00544   table_points.header = table.header;
00545   table_points.points.resize (table.points.size ());
00546   for (size_t i = 0; i < table.points.size (); ++i)
00547   {
00548     table_points.points[i].x = table.points[i].x;
00549     table_points.points[i].y = table.points[i].y;
00550     table_points.points[i].z = table.points[i].z;
00551   }
00552 
00553   // Transform the data
00554   tf::TransformListener listener;
00555   tf::StampedTransform table_pose_frame(table_plane_trans, table.header.stamp, 
00556                                         table.header.frame_id, "table_frame");
00557   listener.setTransform(table_pose_frame);
00558   std::string error_msg;
00559   if (!listener.canTransform("table_frame", table_points.header.frame_id, table_points.header.stamp, &error_msg))
00560   {
00561     ROS_ERROR("Can not transform point cloud from frame %s to table frame; error %s", 
00562               table_points.header.frame_id.c_str(), error_msg.c_str());
00563     return false;
00564   }
00565   int current_try=0, max_tries = 3;
00566   while (1)
00567   {
00568     bool transform_success = true;
00569     try
00570     {
00571       listener.transformPointCloud("table_frame", table_points, table_points);
00572     }
00573     catch (tf::TransformException ex)
00574     {
00575       transform_success = false;
00576       if ( ++current_try >= max_tries )
00577       {
00578         ROS_ERROR("Failed to transform point cloud from frame %s into table_frame; error %s", 
00579                   table_points.header.frame_id.c_str(), ex.what());
00580         return false;
00581       }
00582       //sleep a bit to give the listener a chance to get a new transform
00583       ros::Duration(0.1).sleep();
00584     }
00585     if (transform_success) break;
00586   }
00587   table_points.header.stamp = table.header.stamp;
00588   table_points.header.frame_id = "table_frame";
00589   return true;
00590 }
00591 
00592  
00593 template <class PointCloudType>
00594 void straightenPoints(PointCloudType &points, const tf::Transform& table_plane_trans, 
00595                       const tf::Transform& table_plane_trans_flat)
00596 {
00597   tf::Transform trans = table_plane_trans_flat * table_plane_trans.inverse();
00598   pcl_ros::transformPointCloud(points, points, trans);
00599 }
00600 
00601 
00602 template <typename PointT> void
00603 getClustersFromPointCloud2 (const pcl::PointCloud<PointT> &cloud_objects,                           
00604                             const std::vector<pcl::PointIndices> &clusters2, 
00605                             std::vector<sensor_msgs::PointCloud> &clusters)
00606 {
00607   clusters.resize (clusters2.size ());
00608   for (size_t i = 0; i < clusters2.size (); ++i)
00609   {
00610     pcl::PointCloud<PointT> cloud_cluster;
00611     pcl::copyPointCloud(cloud_objects, clusters2[i], cloud_cluster);
00612     sensor_msgs::PointCloud2 pc2;
00613     pcl::toROSMsg( cloud_cluster, pc2 ); 
00614     sensor_msgs::convertPointCloud2ToPointCloud (pc2, clusters[i]);    
00615   }
00616 }
00617 
00618 void TabletopSegmentor::processCloud(const sensor_msgs::PointCloud2 &cloud,
00619                                      TabletopSegmentation::Response &response,
00620                                      Table input_table)
00621 {
00622   ROS_INFO("Starting process on new cloud in frame %s", cloud.header.frame_id.c_str());
00623 
00624   // PCL objects
00625   KdTreePtr normals_tree_, clusters_tree_;
00626   pcl::VoxelGrid<Point> grid_, grid_objects_;
00627   pcl::PassThrough<Point> pass_;
00628   pcl::NormalEstimation<Point, pcl::Normal> n3d_;
00629   pcl::SACSegmentationFromNormals<Point, pcl::Normal> seg_;
00630   pcl::ProjectInliers<Point> proj_;
00631   pcl::ConvexHull<Point> hull_;
00632   pcl::ExtractPolygonalPrismData<Point> prism_;
00633   pcl::EuclideanClusterExtraction<Point> pcl_cluster_;
00634   pcl::PointCloud<Point>::Ptr table_hull_ptr (new pcl::PointCloud<Point>); 
00635 
00636   // Filtering parameters
00637   grid_.setLeafSize (plane_detection_voxel_size_, plane_detection_voxel_size_, plane_detection_voxel_size_);
00638   grid_objects_.setLeafSize (clustering_voxel_size_, clustering_voxel_size_, clustering_voxel_size_);
00639   grid_.setFilterFieldName ("z");
00640   grid_.setFilterLimits (z_filter_min_, z_filter_max_);
00641   grid_.setDownsampleAllData (false);
00642   grid_objects_.setDownsampleAllData (true);
00643 
00644   normals_tree_ = boost::make_shared<pcl::search::KdTree<Point> > ();
00645   clusters_tree_ = boost::make_shared<pcl::search::KdTree<Point> > ();
00646 
00647   // Normal estimation parameters
00648   n3d_.setKSearch (10);  
00649   n3d_.setSearchMethod (normals_tree_);
00650   // Table model fitting parameters
00651   seg_.setDistanceThreshold (0.05); 
00652   seg_.setMaxIterations (10000);
00653   seg_.setNormalDistanceWeight (0.1);
00654   seg_.setOptimizeCoefficients (true);
00655   seg_.setModelType (pcl::SACMODEL_NORMAL_PLANE);
00656   seg_.setMethodType (pcl::SAC_RANSAC);
00657   seg_.setProbability (0.99);
00658 
00659   proj_.setModelType (pcl::SACMODEL_PLANE);
00660 
00661   // Clustering parameters
00662   pcl_cluster_.setClusterTolerance (cluster_distance_);
00663   pcl_cluster_.setMinClusterSize (min_cluster_size_);
00664   pcl_cluster_.setSearchMethod (clusters_tree_);
00665 
00666   // Step 1 : Filter, remove NaNs and downsample
00667   pcl::PointCloud<Point>::Ptr cloud_ptr (new pcl::PointCloud<Point>); 
00668   try
00669   {
00670     pcl::fromROSMsg (cloud, *cloud_ptr);
00671   }
00672   catch (...)
00673   {
00674     ROS_ERROR("Failure while converting the ROS Message to PCL point cloud.  Tabletop segmentation now requires the input cloud to have color info, so you must input a cloud with XYZRGB points, not just XYZ.");
00675     throw;
00676   }
00677   pass_.setInputCloud (cloud_ptr);
00678   pass_.setFilterFieldName ("z");
00679   pass_.setFilterLimits (z_filter_min_, z_filter_max_);
00680   pcl::PointCloud<Point>::Ptr z_cloud_filtered_ptr (new pcl::PointCloud<Point>); 
00681   pass_.filter (*z_cloud_filtered_ptr);
00682 
00683   pass_.setInputCloud (z_cloud_filtered_ptr);
00684   pass_.setFilterFieldName ("y");
00685   pass_.setFilterLimits (y_filter_min_, y_filter_max_);
00686   pcl::PointCloud<Point>::Ptr y_cloud_filtered_ptr (new pcl::PointCloud<Point>); 
00687   pass_.filter (*y_cloud_filtered_ptr);
00688 
00689   pass_.setInputCloud (y_cloud_filtered_ptr);
00690   pass_.setFilterFieldName ("x");
00691   pass_.setFilterLimits (x_filter_min_, x_filter_max_);
00692   pcl::PointCloud<Point>::Ptr cloud_filtered_ptr (new pcl::PointCloud<Point>); 
00693   pass_.filter (*cloud_filtered_ptr);
00694   
00695   ROS_INFO("Step 1 done");
00696   if (cloud_filtered_ptr->points.size() < (unsigned int)min_cluster_size_)
00697   {
00698     ROS_INFO("Filtered cloud only has %d points", (int)cloud_filtered_ptr->points.size());
00699     response.result = response.NO_TABLE;
00700     return;
00701   }
00702 
00703   pcl::PointCloud<Point>::Ptr cloud_downsampled_ptr (new pcl::PointCloud<Point>); 
00704   grid_.setInputCloud (cloud_filtered_ptr);
00705   grid_.filter (*cloud_downsampled_ptr);
00706   if (cloud_downsampled_ptr->points.size() < (unsigned int)min_cluster_size_)
00707   {
00708     ROS_INFO("Downsampled cloud only has %d points", (int)cloud_downsampled_ptr->points.size());
00709     response.result = response.NO_TABLE;    
00710     return;
00711   }
00712 
00713   // Step 2 : Estimate normals
00714   pcl::PointCloud<pcl::Normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<pcl::Normal>); 
00715   n3d_.setInputCloud (cloud_downsampled_ptr);
00716   n3d_.compute (*cloud_normals_ptr);
00717   ROS_INFO("Step 2 done");
00718 
00719 
00720   // Step 3 : Perform planar segmentation, if table is not given, otherwise use given table
00721   tf::Transform table_plane_trans; 
00722   tf::Transform table_plane_trans_flat;
00723   if(input_table.convex_hull.vertices.size() != 0)
00724   {  
00725     ROS_INFO("Table input, skipping Step 3");
00726     bool success = tableMsgToPointCloud<pcl::PointCloud<Point> >(input_table, cloud.header.frame_id, *table_hull_ptr);
00727     if(!success)
00728     {
00729       ROS_ERROR("Failure in converting table convex hull!");
00730       return;
00731     }
00732     response.table = input_table;
00733     if(flatten_table_)
00734     {
00735       ROS_ERROR("flatten_table mode is disabled if table is given!");
00736       flatten_table_ = false;
00737     }
00738   }
00739   else
00740   {
00741     pcl::PointIndices::Ptr table_inliers_ptr (new pcl::PointIndices); 
00742     pcl::ModelCoefficients::Ptr table_coefficients_ptr (new pcl::ModelCoefficients); 
00743     seg_.setInputCloud (cloud_downsampled_ptr);
00744     seg_.setInputNormals (cloud_normals_ptr);
00745     seg_.segment (*table_inliers_ptr, *table_coefficients_ptr);
00746  
00747     if (table_coefficients_ptr->values.size () <=3)
00748     {
00749       ROS_INFO("Failed to detect table in scan");
00750       response.result = response.NO_TABLE;    
00751       return;
00752     }
00753 
00754     if ( table_inliers_ptr->indices.size() < (unsigned int)inlier_threshold_)
00755     {
00756       ROS_INFO("Plane detection has %d inliers, below min threshold of %d", (int)table_inliers_ptr->indices.size(),
00757                inlier_threshold_);
00758       response.result = response.NO_TABLE;
00759       return;
00760     }
00761 
00762     ROS_INFO ("[TableObjectDetector::input_callback] Model found with %d inliers: [%f %f %f %f].", 
00763               (int)table_inliers_ptr->indices.size (),
00764               table_coefficients_ptr->values[0], table_coefficients_ptr->values[1], 
00765               table_coefficients_ptr->values[2], table_coefficients_ptr->values[3]);
00766     ROS_INFO("Step 3 done");
00767 
00768     // Step 4 : Project the table inliers on the table
00769     pcl::PointCloud<Point>::Ptr table_projected_ptr (new pcl::PointCloud<Point>); 
00770     proj_.setInputCloud (cloud_downsampled_ptr);
00771     proj_.setIndices (table_inliers_ptr);
00772     proj_.setModelCoefficients (table_coefficients_ptr);
00773     proj_.filter (*table_projected_ptr);
00774     ROS_INFO("Step 4 done");
00775   
00776     sensor_msgs::PointCloud table_points;
00777     sensor_msgs::PointCloud table_hull_points;
00778     table_plane_trans = getPlaneTransform (*table_coefficients_ptr, up_direction_, false);
00779 
00780     // ---[ Estimate the convex hull (not in table frame)
00781     hull_.setInputCloud (table_projected_ptr);
00782     hull_.reconstruct (*table_hull_ptr);
00783 
00784     if(!flatten_table_)
00785     {
00786       // --- [ Take the points projected on the table and transform them into the PointCloud message
00787       //  while also transforming them into the table's coordinate system
00788       if (!getPlanePoints<Point> (*table_projected_ptr, table_plane_trans, table_points))
00789       {
00790         response.result = response.OTHER_ERROR;
00791         return;
00792       }
00793 
00794       // ---[ Create the table message
00795       response.table = getTable<sensor_msgs::PointCloud>(cloud.header, table_plane_trans, table_points);
00796 
00797       // ---[ Convert the convex hull points to table frame
00798       if (!getPlanePoints<Point> (*table_hull_ptr, table_plane_trans, table_hull_points))
00799       {
00800         response.result = response.OTHER_ERROR;
00801         return;
00802       }
00803     }
00804     if(flatten_table_)
00805     {
00806       // if flattening the table, find the center of the convex hull and move the table frame there
00807       table_plane_trans_flat = getPlaneTransform (*table_coefficients_ptr, up_direction_, flatten_table_);
00808       tf::Vector3 flat_table_pos;
00809       double avg_x, avg_y, avg_z;
00810       avg_x = avg_y = avg_z = 0;
00811       for (size_t i=0; i<table_projected_ptr->points.size(); i++)
00812       {
00813         avg_x += table_projected_ptr->points[i].x;
00814         avg_y += table_projected_ptr->points[i].y;
00815         avg_z += table_projected_ptr->points[i].z;
00816       }
00817       avg_x /= table_projected_ptr->points.size();
00818       avg_y /= table_projected_ptr->points.size();
00819       avg_z /= table_projected_ptr->points.size();
00820       ROS_INFO("average x,y,z = (%.5f, %.5f, %.5f)", avg_x, avg_y, avg_z);
00821 
00822       // place the new table frame in the center of the convex hull
00823       flat_table_pos[0] = avg_x;
00824       flat_table_pos[1] = avg_y;
00825       flat_table_pos[2] = avg_z;
00826       table_plane_trans_flat.setOrigin(flat_table_pos);
00827 
00828       // shift the non-flat table frame to the center of the convex hull as well
00829       table_plane_trans.setOrigin(flat_table_pos);
00830 
00831       // --- [ Take the points projected on the table and transform them into the PointCloud message
00832       //  while also transforming them into the flat table's coordinate system
00833       sensor_msgs::PointCloud flat_table_points;
00834       if (!getPlanePoints<Point> (*table_projected_ptr, table_plane_trans_flat, flat_table_points))
00835       {
00836         response.result = response.OTHER_ERROR;
00837         return;
00838       }
00839 
00840       // ---[ Create the table message
00841       response.table = getTable<sensor_msgs::PointCloud>(cloud.header, table_plane_trans_flat, flat_table_points);
00842 
00843       // ---[ Convert the convex hull points to flat table frame
00844       if (!getPlanePoints<Point> (*table_hull_ptr, table_plane_trans_flat, table_hull_points))
00845       {
00846         response.result = response.OTHER_ERROR;
00847         return;
00848       }
00849     }
00850 
00851     ROS_INFO("Table computed");  
00852     // ---[ Add the convex hull as a triangle mesh to the Table message
00853     addConvexHullTable<sensor_msgs::PointCloud>(response.table, table_hull_points, flatten_table_);
00854   }
00855 
00856   // Step 5: Get the objects on top of the (non-flat) table
00857   pcl::PointIndices cloud_object_indices;
00858   //prism_.setInputCloud (cloud_all_minus_table_ptr);
00859   prism_.setInputCloud (cloud_filtered_ptr);
00860   prism_.setInputPlanarHull (table_hull_ptr);
00861   ROS_INFO("Using table prism: %f to %f", table_z_filter_min_, table_z_filter_max_);
00862   prism_.setHeightLimits (table_z_filter_min_, table_z_filter_max_);  
00863   prism_.segment (cloud_object_indices);
00864 
00865   pcl::PointCloud<Point>::Ptr cloud_objects_ptr (new pcl::PointCloud<Point>); 
00866   pcl::ExtractIndices<Point> extract_object_indices;
00867   extract_object_indices.setInputCloud (cloud_filtered_ptr);
00868   extract_object_indices.setIndices (boost::make_shared<const pcl::PointIndices> (cloud_object_indices));
00869   extract_object_indices.filter (*cloud_objects_ptr);
00870 
00871   ROS_INFO (" Number of object point candidates: %d.", (int)cloud_objects_ptr->points.size ());
00872   response.result = response.SUCCESS;
00873 
00874   if (cloud_objects_ptr->points.empty ()) 
00875   {
00876     ROS_INFO("No objects on table");
00877     return;
00878   }
00879 
00880   // ---[ Downsample the points
00881   pcl::PointCloud<Point>::Ptr cloud_objects_downsampled_ptr (new pcl::PointCloud<Point>); 
00882   grid_objects_.setInputCloud (cloud_objects_ptr);
00883   grid_objects_.filter (*cloud_objects_downsampled_ptr);
00884 
00885   // ---[ If flattening the table, adjust the points on the table to be straight also
00886   if(flatten_table_) straightenPoints<pcl::PointCloud<Point> >(*cloud_objects_downsampled_ptr, 
00887                                                                table_plane_trans, table_plane_trans_flat);
00888 
00889   // Step 6: Split the objects into Euclidean clusters
00890   std::vector<pcl::PointIndices> clusters2;
00891   //pcl_cluster_.setInputCloud (cloud_objects_ptr);
00892   pcl_cluster_.setInputCloud (cloud_objects_downsampled_ptr);
00893   pcl_cluster_.extract (clusters2);
00894   ROS_INFO ("Number of clusters found matching the given constraints: %d.", (int)clusters2.size ());
00895 
00896   // ---[ Convert clusters into the PointCloud message
00897   std::vector<sensor_msgs::PointCloud> clusters;
00898   getClustersFromPointCloud2<Point> (*cloud_objects_downsampled_ptr, clusters2, clusters);
00899   ROS_INFO("Clusters converted");
00900   response.clusters = clusters;  
00901 
00902   publishClusterMarkers(clusters, cloud.header);
00903 }
00904 
00905 
00906 } //namespace tabletop_object_detector
00907 
00908 int main(int argc, char **argv) 
00909 {
00910   ros::init(argc, argv, "tabletop_segmentation_node");
00911   ros::NodeHandle nh;
00912 
00913   tabletop_object_detector::TabletopSegmentor node(nh);
00914   ros::spin();
00915   return 0;
00916 }


tabletop_object_detector
Author(s): Marius Muja and Matei Ciocarlie
autogenerated on Fri Jan 3 2014 11:48:47