Esempio n. 1
0
bool TabletopSegmentor<PointT>::processCloud(const PointCloudConstPtr &_cloud ) {

  // PCL objects
  KdTreePtr normals_tree_, clusters_tree_;
  pcl::VoxelGrid<PointT> grid_, grid_objects_;
  pcl::PassThrough<PointT> pass_;
  pcl::NormalEstimation<PointT, pcl::Normal> n3d_;
  pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg_;
  pcl::ProjectInliers<PointT> proj_;
  pcl::ConvexHull<PointT> hull_;
  pcl::ExtractPolygonalPrismData<PointT> prism_;
  pcl::EuclideanClusterExtraction<PointT> pcl_cluster_;
  PointCloudPtr table_hull_ptr (new PointCloud);

  /************ STEP 0: Parameter Settings **************/
  // Filtering parameters
  grid_.setLeafSize (plane_detection_voxel_size_, plane_detection_voxel_size_, plane_detection_voxel_size_);
  grid_objects_.setLeafSize (clustering_voxel_size_, clustering_voxel_size_, clustering_voxel_size_);
  grid_.setFilterFieldName ("z");
  grid_.setFilterLimits (z_filter_min_, z_filter_max_);
  grid_.setDownsampleAllData (false);
  grid_objects_.setDownsampleAllData (true);
  normals_tree_ = boost::make_shared<pcl::search::KdTree<PointT> > ();
  clusters_tree_ = boost::make_shared<pcl::search::KdTree<PointT> > ();
  
  // Normal estimation parameters
  n3d_.setKSearch (10);
  n3d_.setSearchMethod (normals_tree_);

  // Table model fitting parameters
  seg_.setDistanceThreshold (0.01);
  seg_.setMaxIterations (10000);
  seg_.setNormalDistanceWeight (0.1);
  seg_.setOptimizeCoefficients (true);
  seg_.setModelType (pcl::SACMODEL_PLANE);
  //seg_.setModelType (pcl::SACMODEL_NORMAL_PLANE);
  //seg_.setModelType( pcl::SACMODEL_PERPENDICULAR_PLANE );
  //seg_.setEpsAngle( 45*M_PI/180.0 );
  //seg_.setAxis( Eigen::Vector3f(0,1,0) );
  
  seg_.setMethodType (pcl::SAC_RANSAC);
  seg_.setProbability (0.99);
  
  //proj_.setModelType (pcl::SACMODEL_NORMAL_PLANE);
  proj_.setModelType (pcl::SACMODEL_PERPENDICULAR_PLANE);
  
  
  // Clustering parameters
  pcl_cluster_.setClusterTolerance (cluster_distance_);
  pcl_cluster_.setMinClusterSize (min_cluster_size_);
  pcl_cluster_.setSearchMethod (clusters_tree_);

  /******** Step 1 : Filter, remove NaNs and downsample ***********/
  // Filter in X, Y and Z directions: output= cloud_filtered_ptr
  pass_.setInputCloud (_cloud);
  pass_.setFilterFieldName ("z");
  pass_.setFilterLimits (z_filter_min_, z_filter_max_);
  PointCloudPtr z_cloud_filtered_ptr (new PointCloud);
  pass_.filter (*z_cloud_filtered_ptr);
  pass_.setInputCloud (z_cloud_filtered_ptr);
  pass_.setFilterFieldName ("y");
  pass_.setFilterLimits (y_filter_min_, y_filter_max_);
  PointCloudPtr y_cloud_filtered_ptr (new PointCloud);
  pass_.filter (*y_cloud_filtered_ptr);
  pass_.setInputCloud (y_cloud_filtered_ptr);
  pass_.setFilterFieldName ("x");
  pass_.setFilterLimits (x_filter_min_, x_filter_max_);
  PointCloudPtr cloud_filtered_ptr (new PointCloud);
  pass_.filter (*cloud_filtered_ptr);
  // Check that the points filtered at least are of a minimum size
  if (cloud_filtered_ptr->points.size() < (unsigned int)min_cluster_size_) {
    printf("\t [ERROR] Filtered cloud only has %d points. \n",
	   (int)cloud_filtered_ptr->points.size() );
    return false;
  }

  pcl::io::savePCDFile( "filtered_cloud.pcd", *cloud_filtered_ptr, true );
  
  // Downsample the filtered cloud: output = cloud_downsampled_ptr
  PointCloudPtr cloud_downsampled_ptr (new PointCloud);
  grid_.setInputCloud (cloud_filtered_ptr);
  grid_.filter (*cloud_downsampled_ptr);
  if (cloud_downsampled_ptr->points.size() < (unsigned int)min_cluster_size_) {
    printf( "\t [ERROR] Downsampled cloud only has %d points \n",
	    (int)cloud_downsampled_ptr->points.size() );
    return false;
  }

  printf( "\t Downsampled cloud only has %d points / %d. \n",
	  (int)cloud_downsampled_ptr->points.size(), (int)cloud_filtered_ptr->points.size() );


  /***************** Step 2 : Estimate normals ******************/
  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<pcl::Normal>);
  n3d_.setInputCloud (cloud_downsampled_ptr);
  n3d_.compute (*cloud_normals_ptr);

  /************* Step 3 : Perform planar segmentation, **********/
  /** if table is not given, otherwise use given table */
  Eigen::Matrix4d table_plane_trans;
  Eigen::Matrix4d table_plane_trans_flat;
  pcl::PointIndices::Ptr table_inliers_ptr (new pcl::PointIndices);
  pcl::ModelCoefficients::Ptr table_coefficients_ptr (new pcl::ModelCoefficients);
  seg_.setInputCloud (cloud_downsampled_ptr);
  seg_.setInputNormals (cloud_normals_ptr);
  seg_.segment( *table_inliers_ptr,
		*table_coefficients_ptr );
  // Check the coefficients and inliers are above threshold values
  if (table_coefficients_ptr->values.size () <=3 ) {
    printf( "\t [ERROR] Failed to detect table in scan \n" );
    return false;
  }
  if ( table_inliers_ptr->indices.size() < (unsigned int)inlier_threshold_) {
    printf( "\t [ERROR] Plane detection has %d below min thresh of %d points \n",
	    (int)table_inliers_ptr->indices.size(),
	    inlier_threshold_ );
    return false;
  }

  pcl::ExtractIndices<PointT> extract;
  PointCloudPtr tablePointsPtr( new PointCloud );
  extract.setInputCloud( cloud_downsampled_ptr );
  extract.setIndices( table_inliers_ptr );
  extract.setNegative(false);
  extract.filter( *tablePointsPtr );
  int na = tablePointsPtr->points.size();
  if( na > 0 ) {
    for( int a = 0; a < na; ++a ) {
      tablePointsPtr->points[a].r = 255;
      tablePointsPtr->points[a].g = 255;
      tablePointsPtr->points[a].b = 255;
      tablePointsPtr->points[a].a = 255;
    }
    pcl::io::savePCDFileASCII( "table_points.pcd", *tablePointsPtr );

  }
  

  /********** Step 4 : Project the table inliers on the table *********/
  PointCloudPtr table_projected_ptr (new PointCloud);
  proj_.setInputCloud (cloud_downsampled_ptr);
  proj_.setIndices (table_inliers_ptr);
  proj_.setModelCoefficients (table_coefficients_ptr);
  proj_.filter (*table_projected_ptr);
  // Get the table transformation w.r.t. camera
  table_plane_trans = getPlaneTransform (*table_coefficients_ptr, up_direction_, false);
  mTableTf.matrix() = table_plane_trans;
  // ---[ Estimate the convex hull (not in table frame)
  hull_.setInputCloud (table_projected_ptr);
  hull_.reconstruct (*table_hull_ptr);

  // Save points in the hull with some points down
  mTable_Points.points.resize(0);
  for( int i = 0; i < table_hull_ptr->points.size(); ++i ) {
    mTable_Points.points.push_back(table_hull_ptr->points[i]);
  }
  mTable_Points.width = 1; mTable_Points.height = mTable_Points.points.size();
  
  // Store table's plane coefficients (after calling getPlaneTransform! because here we check if a sign change is needed)
  mTableCoeffs.resize(4);
  for( int i = 0; i < 4; ++i ) { mTableCoeffs[i] = table_coefficients_ptr->values[i]; }

  /******* Step 5 : Get the objects on top of the table ******/
  pcl::PointIndices cloud_object_indices;
  prism_.setInputCloud (cloud_filtered_ptr);
  prism_.setInputPlanarHull (table_hull_ptr);
  prism_.setHeightLimits (table_obj_height_filter_min_, table_obj_height_filter_max_);
  prism_.segment (cloud_object_indices);
  PointCloudPtr cloud_objects_ptr (new PointCloud);
  pcl::ExtractIndices<PointT> extract_object_indices;
  extract_object_indices.setInputCloud (cloud_filtered_ptr);
  extract_object_indices.setIndices (boost::make_shared<const pcl::PointIndices> (cloud_object_indices));
  extract_object_indices.filter (*cloud_objects_ptr);
  if (cloud_objects_ptr->points.empty ()) {
    std::cout<<"\t [ERROR] No object points on table" << std::endl;
    return false;
  }

  pcl::io::savePCDFile( "cloudObjects.pcd", *cloud_objects_ptr, true );    

  // Downsample the points
  PointCloudPtr cloud_objects_downsampled_ptr (new PointCloud);
  grid_objects_.setInputCloud (cloud_objects_ptr);
  grid_objects_.filter (*cloud_objects_downsampled_ptr);
  printf("Object clouds from %ld to %ld \n", cloud_objects_ptr->points.size(),
	 cloud_objects_downsampled_ptr->points.size() );
  // Step 6: Split the objects into Euclidean clusters
  std::vector<pcl::PointIndices> clusters2;
  pcl_cluster_.setInputCloud (cloud_objects_downsampled_ptr);
  pcl_cluster_.extract (clusters2);
  mClusterInds.resize( clusters2.size() );
  for( int i = 0; i < clusters2.size(); ++i ) {
    mClusterInds[i] = clusters2[i];
    printf("Cluster %d size: %ld \n", i, clusters2[i].indices.size()   );
  }

  mClusters.resize( clusters2.size() );
  int i = 0;
  for( std::vector<pcl::PointIndices>::const_iterator it = 
	 clusters2.begin (); it != clusters2.end (); ++it ) {
    PointCloud cloud_cluster;
    for (std::vector<int>::const_iterator pit = it->indices.begin (); pit != it->indices.end (); ++pit) {
      cloud_cluster.points.push_back (cloud_objects_downsampled_ptr->points[*pit]); 
    }
    cloud_cluster.width = cloud_cluster.points.size ();
    cloud_cluster.height = 1;
    cloud_cluster.is_dense = true;
    
    mClusters[i] = cloud_cluster;
    i++;
  }
  
  return true;
}
void TabletopSegmentor::processCloud(pcl::PointCloud<Point>::Ptr &cloud_ptr,
                                     TabletopSegmentation::Response &response)
{
  //ROS_INFO("Starting process on new cloud in frame %s", cloud_ptr->header.frame_id.c_str());

  // PCL objects
  KdTreePtr normals_tree_, clusters_tree_;
  pcl::VoxelGrid<Point> grid_, grid_objects_;
  pcl::PassThrough<Point> pass_;
  pcl::NormalEstimation<Point, pcl::Normal> n3d_;
  pcl::SACSegmentationFromNormals<Point, pcl::Normal> seg_;
  pcl::ProjectInliers<Point> proj_;
  pcl::ConvexHull<Point> hull_;
  pcl::ExtractPolygonalPrismData<Point> prism_;
  pcl::EuclideanClusterExtraction<Point> pcl_cluster_;

  // Filtering parameters
  grid_.setLeafSize (plane_detection_voxel_size_, plane_detection_voxel_size_, plane_detection_voxel_size_);
  grid_objects_.setLeafSize (clustering_voxel_size_, clustering_voxel_size_, clustering_voxel_size_);
  grid_.setFilterFieldName ("z");
  grid_.setFilterLimits (z_filter_min_, z_filter_max_);
  grid_.setDownsampleAllData (false);
  grid_objects_.setDownsampleAllData (false);

  normals_tree_ = boost::make_shared<pcl::search::KdTree<Point> > ();
  clusters_tree_ = boost::make_shared<pcl::search::KdTree<Point> > ();

  // Normal estimation parameters
  n3d_.setKSearch (10);  
  n3d_.setSearchMethod (normals_tree_);
  // Table model fitting parameters
  seg_.setDistanceThreshold (0.05); 
  seg_.setMaxIterations (10000);
  seg_.setNormalDistanceWeight (0.1);
  seg_.setOptimizeCoefficients (true);
  seg_.setModelType (pcl::SACMODEL_NORMAL_PLANE);
  seg_.setMethodType (pcl::SAC_RANSAC);
  seg_.setProbability (0.99);

  proj_.setModelType (pcl::SACMODEL_PLANE);

  // Clustering parameters
  pcl_cluster_.setClusterTolerance (cluster_distance_);
  pcl_cluster_.setMinClusterSize (min_cluster_size_);
  pcl_cluster_.setSearchMethod (clusters_tree_);

  // Step 1 : Filter, remove NaNs and downsample
  pass_.setInputCloud (cloud_ptr);
  pass_.setFilterFieldName ("z");
  pass_.setFilterLimits (z_filter_min_, z_filter_max_);
  pcl::PointCloud<Point>::Ptr z_cloud_filtered_ptr (new pcl::PointCloud<Point>); 
  pass_.filter (*z_cloud_filtered_ptr);

  pass_.setInputCloud (z_cloud_filtered_ptr);
  pass_.setFilterFieldName ("y");
  pass_.setFilterLimits (y_filter_min_, y_filter_max_);
  pcl::PointCloud<Point>::Ptr y_cloud_filtered_ptr (new pcl::PointCloud<Point>); 
  pass_.filter (*y_cloud_filtered_ptr);

  pass_.setInputCloud (y_cloud_filtered_ptr);
  pass_.setFilterFieldName ("x");
  pass_.setFilterLimits (x_filter_min_, x_filter_max_);
  pcl::PointCloud<Point>::Ptr cloud_filtered_ptr (new pcl::PointCloud<Point>); 
  pass_.filter (*cloud_filtered_ptr);
  
  //ROS_INFO("Step 1 done");
  if (cloud_filtered_ptr->points.size() < (unsigned int)min_cluster_size_)
  {
    //ROS_INFO("Filtered cloud only has %d points", (int)cloud_filtered_ptr->points.size());
    response.result = response.NO_TABLE;
    return;
  }

  pcl::PointCloud<Point>::Ptr cloud_downsampled_ptr (new pcl::PointCloud<Point>); 
  grid_.setInputCloud (cloud_filtered_ptr);
  grid_.filter (*cloud_downsampled_ptr);
  if (cloud_downsampled_ptr->points.size() < (unsigned int)min_cluster_size_)
  {
    //ROS_INFO("Downsampled cloud only has %d points", (int)cloud_downsampled_ptr->points.size());
    response.result = response.NO_TABLE;    
    return;
  }

  // Step 2 : Estimate normals
  pcl::PointCloud<pcl::Normal>::Ptr cloud_normals_ptr (new pcl::PointCloud<pcl::Normal>); 
  n3d_.setInputCloud (cloud_downsampled_ptr);
  n3d_.compute (*cloud_normals_ptr);
  //ROS_INFO("Step 2 done");

  // Step 3 : Perform planar segmentation
  pcl::PointIndices::Ptr table_inliers_ptr (new pcl::PointIndices); 
  pcl::ModelCoefficients::Ptr table_coefficients_ptr (new pcl::ModelCoefficients); 
  seg_.setInputCloud (cloud_downsampled_ptr);
  seg_.setInputNormals (cloud_normals_ptr);
  seg_.segment (*table_inliers_ptr, *table_coefficients_ptr);
 
  if (table_coefficients_ptr->values.size () <=3)
  {
    //ROS_INFO("Failed to detect table in scan");
    response.result = response.NO_TABLE;    
    return;
  }

  if ( table_inliers_ptr->indices.size() < (unsigned int)inlier_threshold_)
  {
    //ROS_INFO("Plane detection has %d inliers, below min threshold of %d", (int)table_inliers_ptr->indices.size(),inlier_threshold_);
    response.result = response.NO_TABLE;
    return;
  }

  //ROS_INFO ("[TableObjectDetector::input_callback] Model found with %d inliers: [%f %f %f %f].", 	    (int)table_inliers_ptr->indices.size (),	    table_coefficients_ptr->values[0], table_coefficients_ptr->values[1], 	    table_coefficients_ptr->values[2], table_coefficients_ptr->values[3]);
  //ROS_INFO("Step 3 done");

  // Step 4 : Project the table inliers on the table
  pcl::PointCloud<Point>::Ptr table_projected_ptr (new pcl::PointCloud<Point>); 
  proj_.setInputCloud (cloud_downsampled_ptr);
  proj_.setIndices (table_inliers_ptr);
  proj_.setModelCoefficients (table_coefficients_ptr);
  proj_.filter (*table_projected_ptr);
  //ROS_INFO("Step 4 done");
  
  sensor_msgs::PointCloud table_points;
  sensor_msgs::PointCloud table_hull_points;
  tf::Transform table_plane_trans = getPlaneTransform (*table_coefficients_ptr, up_direction_, false);
  tf::Transform table_plane_trans_flat;

  // ---[ Estimate the convex hull (not in table frame)
  pcl::PointCloud<Point>::Ptr table_hull_ptr (new pcl::PointCloud<Point>); 
  hull_.setInputCloud (table_projected_ptr);
  hull_.reconstruct (*table_hull_ptr);

  if(!flatten_table_)
  {
    // --- [ Take the points projected on the table and transform them into the PointCloud message
    //  while also transforming them into the table's coordinate system
    if (!getPlanePoints<Point> (*table_projected_ptr, table_plane_trans, table_points))
    {
      response.result = response.OTHER_ERROR;
      return;
    }

    // ---[ Create the table message
    response.table = getTable<sensor_msgs::PointCloud>(cloud_ptr->header, table_plane_trans, table_points);

    // ---[ Convert the convex hull points to table frame
    if (!getPlanePoints<Point> (*table_hull_ptr, table_plane_trans, table_hull_points))
    {
      response.result = response.OTHER_ERROR;
      return;
    }
  }
  if(flatten_table_)
  {
    // if flattening the table, find the center of the convex hull and move the table frame there
    table_plane_trans_flat = getPlaneTransform (*table_coefficients_ptr, up_direction_, flatten_table_);
    tf::Vector3 flat_table_pos;
    double avg_x, avg_y, avg_z;
    avg_x = avg_y = avg_z = 0;
    for (size_t i=0; i<table_projected_ptr->points.size(); i++)
    {
      avg_x += table_projected_ptr->points[i].x;
      avg_y += table_projected_ptr->points[i].y;
      avg_z += table_projected_ptr->points[i].z;
    }
    avg_x /= table_projected_ptr->points.size();
    avg_y /= table_projected_ptr->points.size();
    avg_z /= table_projected_ptr->points.size();
    //ROS_INFO("average x,y,z = (%.5f, %.5f, %.5f)", avg_x, avg_y, avg_z);

    // place the new table frame in the center of the convex hull
    flat_table_pos[0] = avg_x;
    flat_table_pos[1] = avg_y;
    flat_table_pos[2] = avg_z;
    table_plane_trans_flat.setOrigin(flat_table_pos);

    // shift the non-flat table frame to the center of the convex hull as well
    table_plane_trans.setOrigin(flat_table_pos);

    // --- [ Take the points projected on the table and transform them into the PointCloud message
    //  while also transforming them into the flat table's coordinate system
    sensor_msgs::PointCloud flat_table_points;
    if (!getPlanePoints<Point> (*table_projected_ptr, table_plane_trans_flat, flat_table_points))
    {
      response.result = response.OTHER_ERROR;
      return;
    }

    // ---[ Create the table message
    response.table = getTable<sensor_msgs::PointCloud>(cloud_ptr->header, table_plane_trans_flat, flat_table_points);

    // ---[ Convert the convex hull points to flat table frame
    if (!getPlanePoints<Point> (*table_hull_ptr, table_plane_trans_flat, table_hull_points))
    {
      response.result = response.OTHER_ERROR;
      return;
    }
  }

  //ROS_INFO("Table computed");
  response.result = response.SUCCESS;
  
  // ---[ Add the convex hull as a triangle mesh to the Table message
//  addConvexHullTable<sensor_msgs::PointCloud>(response.table, table_hull_points, flatten_table_);

  // ---[ Get the objects on top of the (non-flat) table
  pcl::PointIndices cloud_object_indices;
  prism_.setInputCloud (cloud_filtered_ptr);
  prism_.setInputPlanarHull (table_hull_ptr);
  //ROS_INFO("Using table prism: %f to %f", table_z_filter_min_, table_z_filter_max_);
  prism_.setHeightLimits (table_z_filter_min_, table_z_filter_max_);  
  prism_.segment (cloud_object_indices);

  pcl::PointCloud<Point>::Ptr cloud_objects_ptr (new pcl::PointCloud<Point>); 
  pcl::ExtractIndices<Point> extract_object_indices;
  extract_object_indices.setInputCloud (cloud_filtered_ptr);
  extract_object_indices.setIndices (boost::make_shared<const pcl::PointIndices> (cloud_object_indices));
  extract_object_indices.filter (*cloud_objects_ptr);

  //ROS_INFO (" Number of object point candidates: %d.", (int)cloud_objects_ptr->points.size ());

  if (cloud_objects_ptr->points.empty ()) 
  {
    //ROS_INFO("No objects on table");
    return;
  }

  // ---[ Downsample the points
  pcl::PointCloud<Point>::Ptr cloud_objects_downsampled_ptr (new pcl::PointCloud<Point>); 
  grid_objects_.setInputCloud (cloud_objects_ptr);
  grid_objects_.filter (*cloud_objects_downsampled_ptr);

  // ---[ Split the objects into Euclidean clusters
  std::vector<pcl::PointIndices> clusters2;
  pcl_cluster_.setInputCloud (cloud_objects_ptr);
  //pcl_cluster_.setInputCloud (cloud_objects_downsampled_ptr);
  pcl_cluster_.extract (clusters2);
  //:ROS_INFO ("Number of clusters found matching the given constraints: %d.", (int)clusters2.size ());

  // ---[ Convert clusters into the PointCloud message
  std::vector<sensor_msgs::PointCloud2> clusters;
  getClusters<Point> (*cloud_objects_ptr, clusters2, clusters);
  //ROS_INFO("Clusters converted");
  response.clusters = clusters;  

//  publishClusterMarkers(clusters, cloud.header);
}