00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
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
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
00079 ros::NodeHandle nh_;
00080 ros::NodeHandle pnh_;
00081
00082
00083 std::string gripper_tag_frame_id_;
00084
00085
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
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
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
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
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
00148
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
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
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
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
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
00324 return false;
00325 }
00326 got_cloud_ = false;
00327
00328 ROS_INFO("Pole Extractor - %s - Running segmentation...", __FUNCTION__);
00329
00330
00331 ne_.setSearchMethod(tree_);
00332 ne_.setInputCloud(cloud_filtered_);
00333 ne_.setKSearch(50);
00334 ne_.compute(*cloud_normals_);
00335
00336
00337
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
00349 seg_.segment(*inliers_cylinder_, *coefficients_cylinder_);
00350
00351
00352
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
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
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
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
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
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
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
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
00425 marker_pub_.publish(marker);
00426
00427 if(run_)
00428 {
00429
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
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
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
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
00499