pole_extractor.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002 *
00003 * Software License Agreement (BSD License)
00004 *
00005 *  Copyright (c) 2011, ISR University of Coimbra.
00006 *  All rights reserved.
00007 *
00008 *  Redistribution and use in source and binary forms, with or without
00009 *  modification, are permitted provided that the following conditions
00010 *  are met:
00011 *
00012 *   * Redistributions of source code must retain the above copyright
00013 *     notice, this list of conditions and the following disclaimer.
00014 *   * Redistributions in binary form must reproduce the above
00015 *     copyright notice, this list of conditions and the following
00016 *     disclaimer in the documentation and/or other materials provided
00017 *     with the distribution.
00018 *   * Neither the name of the ISR University of Coimbra nor the names of its
00019 *     contributors may be used to endorse or promote products derived
00020 *     from this software without specific prior written permission.
00021 *
00022 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 *  POSSIBILITY OF SUCH DAMAGE.
00034 *
00035 * Author: Gonçalo Cabrita on 05/03/2012
00036 *********************************************************************/
00037 
00038 #include <ros/ros.h>
00039 #include <tf/tf.h>
00040 #include <boost/foreach.hpp>
00041 #include <pcl_ros/point_cloud.h>
00042 #include <pcl/ModelCoefficients.h>
00043 #include <pcl/point_types.h>
00044 #include <pcl/features/normal_3d.h>
00045 #include <pcl/filters/extract_indices.h>
00046 #include <pcl/filters/passthrough.h>
00047 //#include <pcl/filters/crop_box.h>
00048 #include <pcl/sample_consensus/method_types.h>
00049 #include <pcl/sample_consensus/model_types.h>
00050 #include <pcl/segmentation/sac_segmentation.h>
00051 #include <visualization_msgs/Marker.h>
00052 #include <pole_structure_mapper/PoleSectionStamped.h>
00053 #include <std_msgs/Bool.h>
00054 
00055 #include <tf/transform_listener.h>
00056 #include <tf/message_filter.h>
00057 #include <message_filters/subscriber.h>
00058 
00059 #define SLICE_SAMPLES 3
00060 
00061 typedef pcl::PointXYZ PointT;
00062 typedef pcl::PointCloud<PointT> PointCloud;
00063 
00064 class PoleExtractor
00065 {
00066         public:
00067         PoleExtractor();
00068         ~PoleExtractor();
00069         
00070         bool cylinderSegmentation();
00071         
00072         private:
00073         void pointCloudCallback(const PointCloud::ConstPtr& msg);
00074         void controlCallback(const std_msgs::Bool::ConstPtr& msg);
00075     
00076     void getMinMax3DalongAxis(const PointCloud::ConstPtr& cloud, PointT * max_pt, PointT * min_pt, PointT * axis_point, tf::Vector3 * normal);
00077         
00078         // ROS stuff
00079         ros::NodeHandle nh_;
00080         ros::NodeHandle pnh_;
00081     
00082         // Global frame_id
00083         std::string gripper_tag_frame_id_;
00084 
00085         // Size of the crop box
00086         double filter_box_size_;
00087 
00088         double min_diameter_;
00089         double max_diameter_;
00090 
00091         double normal_distance_weight_;
00092         int max_iterations_;
00093         double distance_threshold_;
00094     
00095         //ros::Subscriber cloud_sub_;
00096         message_filters::Subscriber<PointCloud> cloud_sub_;
00097         tf::TransformListener tf_;
00098         tf::MessageFilter<PointCloud> * tf_filter_;
00099     
00100         ros::Publisher cylinder_pub_;
00101         ros::Publisher roi_cloud_pub_;
00102         ros::Publisher marker_pub_;
00103         ros::Publisher pole_pub_;
00104 
00105         ros::Subscriber control_sub_;
00106         
00107         // All the objects needed
00108         pcl::NormalEstimation<PointT, pcl::Normal> ne_;
00109         pcl::SACSegmentationFromNormals<PointT, pcl::Normal> seg_; 
00110         pcl::ExtractIndices<PointT> extract_;
00111         pcl::ExtractIndices<pcl::Normal> extract_normals_;
00112         pcl::KdTreeFLANN<PointT>::Ptr tree_;
00113         
00114         // Datasets
00115         pcl::PointCloud<PointT>::Ptr cloud_filtered_;
00116         pcl::PointCloud<pcl::Normal>::Ptr cloud_normals_;
00117         pcl::ModelCoefficients::Ptr coefficients_cylinder_;
00118         pcl::PointIndices::Ptr inliers_cylinder_;
00119         
00120         bool got_cloud_;
00121 
00122         double max(double a, double b);
00123         double min(double a, double b);
00124 
00125         bool run_;
00126 };
00127 
00128 PoleExtractor::PoleExtractor() : nh_(), pnh_("~")
00129 {
00130         got_cloud_ = false;
00131 
00132         // Alloc space...
00133         tree_ = pcl::KdTreeFLANN<PointT>::Ptr(new pcl::KdTreeFLANN<PointT>());
00134         cloud_filtered_ = pcl::PointCloud<PointT>::Ptr(new pcl::PointCloud<PointT>);
00135         cloud_normals_ = pcl::PointCloud<pcl::Normal>::Ptr(new pcl::PointCloud<pcl::Normal>);
00136         coefficients_cylinder_ = pcl::ModelCoefficients::Ptr(new pcl::ModelCoefficients);
00137         inliers_cylinder_ = pcl::PointIndices::Ptr(new pcl::PointIndices);
00138         
00139         pnh_.param<std::string>("gripper_tag_frame_id", gripper_tag_frame_id_, "ar_marker");
00140         pnh_.param("filter_box_size", filter_box_size_, 2.0);
00141         pnh_.param("min_diameter", min_diameter_, 0.10);
00142         pnh_.param("max_diameter", max_diameter_, 0.20);
00143         pnh_.param("normal_distance", normal_distance_weight_, 0.1);
00144         pnh_.param("max_iterations", max_iterations_, 10000);
00145         pnh_.param("distance_threshold", distance_threshold_, 0.05);
00146     
00147         // ROS stuff
00148         //cloud_sub_ = nh_.subscribe<PointCloud>("/chewed_cloud", 1, &PoleExtractor::pointCloudCallback, this);
00149         cloud_sub_.subscribe(nh_, "/chewed_cloud", 1);
00150         tf_filter_ = new tf::MessageFilter<PointCloud>(cloud_sub_, tf_, gripper_tag_frame_id_, 10);
00151         tf_filter_->registerCallback( boost::bind(&PoleExtractor::pointCloudCallback, this, _1) );
00152     
00153         cylinder_pub_ = nh_.advertise<PointCloud>("pole_cloud", 1);
00154         roi_cloud_pub_ = nh_.advertise<PointCloud>("roi_cloud", 1);
00155         marker_pub_ = nh_.advertise<visualization_msgs::Marker>("pole_extractor_markers", 1);
00156         pole_pub_ = nh_.advertise<pole_structure_mapper::PoleSectionStamped>("pole", 10);
00157 
00158         control_sub_ = nh_.subscribe("/extract_poles", 10, &PoleExtractor::controlCallback, this);
00159 
00160         run_ = true;
00161 }
00162 
00163 PoleExtractor::~PoleExtractor()
00164 {
00165         
00166 }
00167 
00168 double PoleExtractor::max(double a, double b)
00169 {
00170         if(a>b) return a;
00171         else return b;
00172 }
00173 
00174 double PoleExtractor::min(double a, double b)
00175 {
00176         if(a<b) return a;
00177         else return b;
00178 }
00179 
00180 void PoleExtractor::controlCallback(const std_msgs::Bool::ConstPtr& msg)
00181 {
00182         run_ = msg->data;
00183 }
00184 
00185 void PoleExtractor::pointCloudCallback(const PointCloud::ConstPtr& msg)
00186 {
00187         ROS_INFO("Pole Extractor - %s - Got a cloud msg...", __FUNCTION__);
00188 
00189     geometry_msgs::PointStamped corners[8];
00190     
00191     corners[0].point.x = filter_box_size_/2.0;
00192     corners[0].point.y = filter_box_size_/2.0;
00193     corners[0].point.z = filter_box_size_/2.0;
00194     
00195     corners[1].point.x = filter_box_size_/-2.0;
00196     corners[1].point.y = filter_box_size_/2.0;
00197     corners[1].point.z = filter_box_size_/2.0;
00198     
00199     corners[2].point.x = filter_box_size_/-2.0;
00200     corners[2].point.y = filter_box_size_/-2.0;
00201     corners[2].point.z = filter_box_size_/2.0;
00202     
00203     corners[3].point.x = filter_box_size_/2.0;
00204     corners[3].point.y = filter_box_size_/-2.0;
00205     corners[3].point.z = filter_box_size_/2.0;
00206     
00207     corners[4].point.x = filter_box_size_/2.0;
00208     corners[4].point.y = filter_box_size_/2.0;
00209     corners[4].point.z = filter_box_size_/-2.0;
00210     
00211     corners[5].point.x = filter_box_size_/-2.0;
00212     corners[5].point.y = filter_box_size_/2.0;
00213     corners[5].point.z = filter_box_size_/-2.0;
00214     
00215     corners[6].point.x = filter_box_size_/-2.0;
00216     corners[6].point.y = filter_box_size_/-2.0;
00217     corners[6].point.z = filter_box_size_/-2.0;
00218     
00219     corners[7].point.x = filter_box_size_/2.0;
00220     corners[7].point.y = filter_box_size_/-2.0;
00221     corners[7].point.z = filter_box_size_/-2.0;
00222     
00223     for(int i=0 ; i<8 ; i++)
00224     {
00225         corners[i].header.frame_id = gripper_tag_frame_id_;
00226         corners[i].header.stamp = msg->header.stamp;
00227         
00228         try { tf_.transformPoint(msg->header.frame_id, corners[i], corners[i]); }
00229         catch(tf::TransformException &ex) 
00230         {
00231             ROS_ERROR("Pole Extractor - %s - Error: %s", __FUNCTION__, ex.what());
00232             return;
00233         }
00234     }
00235 
00236     PointT max_p, min_p;
00237     max_p.x = min_p.x = corners[0].point.x;
00238     max_p.y = min_p.y = corners[0].point.y;
00239     max_p.z = min_p.z = corners[0].point.z;
00240     for(int i=1 ; i<8 ; i++)
00241     {
00242         if(corners[i].point.x > max_p.x) max_p.x = corners[i].point.x;
00243         if(corners[i].point.x < min_p.x) min_p.x = corners[i].point.x;
00244         
00245         if(corners[i].point.y > max_p.y) max_p.y = corners[i].point.y;
00246         if(corners[i].point.y < min_p.y) min_p.y = corners[i].point.y;
00247         
00248         if(corners[i].point.z > max_p.z) max_p.z = corners[i].point.z;
00249         if(corners[i].point.z < min_p.z) min_p.z = corners[i].point.z;
00250     }
00251     
00252         pcl::PassThrough<PointT> filter;
00253         filter.setInputCloud(msg);
00254         filter.setFilterFieldName("x");
00255         filter.setFilterLimits(min_p.x, max_p.x);
00256         filter.filter(*cloud_filtered_);
00257         filter.setInputCloud(cloud_filtered_);
00258         filter.setFilterFieldName("y");
00259         filter.setFilterLimits(min_p.y, max_p.y);
00260         filter.filter(*cloud_filtered_);
00261         filter.setFilterFieldName("z");
00262         filter.setFilterLimits(min_p.z, max_p.z);
00263         filter.filter(*cloud_filtered_);
00264 
00265         roi_cloud_pub_.publish(cloud_filtered_);
00266     
00267     /*pcl::CropBox<pcl::PointXYZ> filter;
00268      filter.setInputCloud(msg);
00269      
00270      Eigen::Vector4f min_pt(min.pose.position.x, min.pose.position.y, min.pose.position.z, 1);
00271      Eigen::Vector4f max_pt(max.pose.position.x, max.pose.position.y, max.pose.position.z, 1);
00272      
00273      filter.setMin(min_pt);
00274      filter.setMax(max_pt);
00275      
00276      filter.filter(*cloud_filtered_);*/
00277 
00278         //*cloud_filtered_ = *msg;
00279 
00280         //ROS_INFO("PoleExtractor - %s - Got a PointCloud with %d points, after filtering %d points remained!", __FUNCTION__, msg->points.size(), cloud_filtered_->points.size());
00281         
00282         got_cloud_ = true;
00283 }
00284 
00285 void PoleExtractor::getMinMax3DalongAxis(const PointCloud::ConstPtr& cloud, PointT * max_pt, PointT * min_pt, PointT * axis_point, tf::Vector3 * normal)
00286 {
00287     PointT max_p = *axis_point;
00288     double max_t = 0.0;
00289     PointT min_p = *axis_point;
00290     double min_t = 0.0;
00291     
00292     BOOST_FOREACH(const PointT& pt, cloud->points)
00293     {
00294         // First we convert pt into a point p along the axis on the same plane perpendicular to the axis
00295         double t = (normal->x()*(pt.x-axis_point->x) + normal->y()*(pt.y-axis_point->y) + normal->z()*(pt.z-axis_point->z))/(pow(normal->x(),2) + pow(normal->y(),2) + pow(normal->z(),2));
00296         
00297         PointT p;
00298         p.x = axis_point->x + normal->x() * t;
00299         p.y = axis_point->y + normal->y() * t;
00300         p.z = axis_point->z + normal->z() * t;
00301         
00302         // Now we check if the point is min or max
00303         if(t>max_t)
00304         {
00305             max_t = t;
00306             max_p = p;
00307         }
00308         if(t<min_t)
00309         {
00310             min_t = t;
00311             min_p = p;
00312         }
00313     }
00314     
00315     *max_pt = max_p;
00316     *min_pt = min_p;
00317 }
00318 
00319 bool PoleExtractor::cylinderSegmentation()
00320 {
00321         if(!got_cloud_)
00322     {
00323             //ROS_WARN("Pole Extractor - %s - No cloud to work on!", __FUNCTION__);
00324             return false;
00325     }
00326     got_cloud_ = false;
00327     
00328     ROS_INFO("Pole Extractor - %s - Running segmentation...", __FUNCTION__);
00329 
00330         // Estimate point normals
00331         ne_.setSearchMethod(tree_);
00332         ne_.setInputCloud(cloud_filtered_);
00333         ne_.setKSearch(50);
00334         ne_.compute(*cloud_normals_);
00335         //ROS_INFO("Normals - done");
00336 
00337         // Create the segmentation object for cylinder segmentation and set all the parameters
00338         seg_.setOptimizeCoefficients(true);
00339         seg_.setModelType(pcl::SACMODEL_CYLINDER);
00340         seg_.setMethodType(pcl::SAC_RANSAC);
00341         seg_.setNormalDistanceWeight(normal_distance_weight_);
00342         seg_.setMaxIterations(max_iterations_);
00343         seg_.setDistanceThreshold(distance_threshold_);
00344         seg_.setRadiusLimits(min_diameter_, max_diameter_);
00345         seg_.setInputCloud(cloud_filtered_);
00346         seg_.setInputNormals(cloud_normals_);
00347         
00348         // Obtain the cylinder inliers and coefficients
00349         seg_.segment(*inliers_cylinder_, *coefficients_cylinder_);
00350         //ROS_INFO("Segmentation - done");
00351 
00352         // Extract the cylinder inliers
00353         extract_.setInputCloud(cloud_filtered_);
00354         extract_.setIndices(inliers_cylinder_);
00355         extract_.setNegative(false);
00356         pcl::PointCloud<PointT>::Ptr cloud_cylinder (new pcl::PointCloud<PointT>());
00357         extract_.filter(*cloud_cylinder);
00358         //ROS_INFO("Inliers - done");
00359         
00360     if(cloud_cylinder->points.size() == 0)
00361     {
00362         ROS_ERROR("Pole Extractor - %s - The cylinder point cloud is empty!", __FUNCTION__);
00363         return false;
00364     }
00365     
00366         cylinder_pub_.publish(cloud_cylinder);
00367     
00368     // Calculate the parameters of the cylinder
00369     PointT point_in_axis;
00370     point_in_axis.x = coefficients_cylinder_->values[0];
00371     point_in_axis.y = coefficients_cylinder_->values[1];
00372     point_in_axis.z = coefficients_cylinder_->values[2];
00373     
00374     // Axis vector parameters
00375     tf::Vector3 axis_vector(coefficients_cylinder_->values[3], coefficients_cylinder_->values[4], coefficients_cylinder_->values[5]);
00376     
00377     double cylinder_diameter = coefficients_cylinder_->values[6]*2.0;
00378     
00379     PointT top_pt, base_pt;
00380     getMinMax3DalongAxis(cloud_cylinder, &top_pt, &base_pt, &point_in_axis, &axis_vector);
00381     //ROS_INFO("MinMax3D - done");
00382     
00383     double cylinder_length = sqrt(pow(top_pt.x - base_pt.x, 2) + pow(top_pt.y - base_pt.y, 2) + pow(top_pt.z - base_pt.z, 2));
00384     
00385     //ROS_INFO("Detected a pipe section with %lfm in length and a diameter of %lfm", cylinder_length, cylinder_diameter);
00386 
00387     tf::Vector3 up_vector(0.0, 0.0, 1.0);
00388     tf::Vector3 right_vector = axis_vector.cross(up_vector);
00389     right_vector.normalize();
00390     tf::Quaternion q(right_vector, -1.0*acos(axis_vector.dot(up_vector)));
00391     q.normalize();
00392     geometry_msgs::Quaternion cylinder_orientation;
00393     tf::quaternionTFToMsg(q, cylinder_orientation);
00394 
00395     // Display the cylinder
00396     visualization_msgs::Marker marker;
00397     marker.header.frame_id = cloud_cylinder->header.frame_id;
00398     marker.header.stamp = ros::Time::now();
00399 
00400     marker.ns = gripper_tag_frame_id_;
00401     marker.id = 0;
00402     
00403     marker.type = visualization_msgs::Marker::CYLINDER;
00404     
00405     marker.action = visualization_msgs::Marker::ADD;
00406     
00407     // For the marker the position is the middle point between the bottom and the top
00408     marker.pose.position.x = (top_pt.x+base_pt.x)/2.0;
00409     marker.pose.position.y = (top_pt.y+base_pt.y)/2.0;
00410     marker.pose.position.z = (top_pt.z+base_pt.z)/2.0;
00411     marker.pose.orientation = cylinder_orientation;
00412 
00413     marker.scale.x = cylinder_diameter;
00414     marker.scale.y = cylinder_diameter;
00415     marker.scale.z = cylinder_length;
00416     
00417     marker.color.r = 1.0f;
00418     marker.color.g = 1.0f;
00419     marker.color.b = 0.0f;
00420     marker.color.a = 0.8;
00421     
00422     marker.lifetime = ros::Duration();
00423     
00424     // Publish the marker
00425     marker_pub_.publish(marker);
00426     
00427     if(run_)
00428     {
00429         // Publish the pole section
00430         pole_structure_mapper::PoleSectionStamped pole;
00431         pole.header.frame_id = cloud_cylinder->header.frame_id;
00432         pole.header.stamp = ros::Time::now();
00433         pole.pole.base.x = base_pt.x;
00434         pole.pole.base.y = base_pt.y;
00435         pole.pole.base.z = base_pt.z;
00436         pole.pole.axis.x = axis_vector.x();
00437         pole.pole.axis.y = axis_vector.y();
00438         pole.pole.axis.z = axis_vector.z();
00439         pole.pole.diameter = cylinder_diameter;
00440         pole.pole.length = cylinder_length;
00441         pole_pub_.publish(pole);
00442     }
00443 
00444     // Display roi
00445     visualization_msgs::Marker roi;
00446     roi.header.frame_id = gripper_tag_frame_id_;
00447     roi.header.stamp = ros::Time::now();
00448 
00449     roi.ns = "roi";
00450     roi.id = 0;
00451     
00452     roi.type = visualization_msgs::Marker::CUBE;
00453     
00454     roi.action = visualization_msgs::Marker::ADD;
00455     
00456     // For the marker the position is the middle point between the bottom and the top
00457     roi.pose.position.x = 0.0;
00458     roi.pose.position.y = 0.0;
00459     roi.pose.position.z = 0.0;
00460 
00461     roi.scale.x = filter_box_size_;
00462     roi.scale.y = filter_box_size_;
00463     roi.scale.z = filter_box_size_;
00464     
00465     roi.color.r = 0.0f;
00466     roi.color.g = 1.0f;
00467     roi.color.b = 0.0f;
00468     roi.color.a = 0.2;
00469     
00470     roi.lifetime = ros::Duration();
00471     
00472     // Publish the marker
00473     marker_pub_.publish(roi);
00474 
00475     ROS_INFO("Pole Extractor - %s - Done!", __FUNCTION__);
00476     
00477     return true;
00478 }
00479 
00480 
00481 int main(int argc, char** argv)
00482 {
00483         ros::init(argc, argv, "pole_extractor_node");
00484     
00485     ROS_INFO("Pole Extractor for ROS v0.1");
00486         
00487         PoleExtractor pe;
00488         
00489         while(ros::ok())
00490         {
00491                 pe.cylinderSegmentation();
00492         
00493                 ros::spinOnce();
00494                 ros::Duration(0.1).sleep();
00495         }
00496 }
00497 
00498 // EOF
00499 


pole_structure_mapper
Author(s): Gonçalo Cabrita and Mahmoud Tavakoli
autogenerated on Mon Jan 6 2014 11:26:24