00001
00002
00003
00004
00005
00006
00007
00008 #include <sstream>
00009 #include <boost/foreach.hpp>
00010 #define foreach BOOST_FOREACH
00011 #include <boost/format.hpp>
00012
00013 #include "ros/ros.h"
00014 #include "pcl16_ros/transforms.h"
00015 #include "pcl16/filters/passthrough.h"
00016 #include "pcl16/filters/voxel_grid.h"
00017 #include "pcl16/filters/project_inliers.h"
00018 #include "pcl16/filters/extract_indices.h"
00019 #include "pcl16/filters/radius_outlier_removal.h"
00020 #include "pcl16/segmentation/extract_clusters.h"
00021 #include "pcl16/segmentation/extract_polygonal_prism_data.h"
00022 #include "pcl16/kdtree/kdtree.h"
00023 #include "pcl16/surface/convex_hull.h"
00024 #include "pcl16/ros/conversions.h"
00025
00026 #include "semanticmodel/Plane.h"
00027 #include "semanticmodel/centroid.hh"
00028 #include "semanticmodel/segmenter.hh"
00029 #include "semanticmodel/Blobs.h"
00030 #include "semanticmodel/PlaneExchange.h"
00031
00032 typedef pcl16::PointCloud<pcl16::Normal> NormalCloud;
00033
00034
00035 std::string CloudsToString(
00036 const std::vector< pcl16::PointCloud<pcl16::PointXYZRGB>::Ptr >& vec)
00037 {
00038 std::stringstream ss;
00039 ss << vec.size() << ": [ ";
00040 for (size_t i = 0 ; i < vec.size() ; ++i)
00041 {
00042 ss << vec[i]->size() << " ";
00043 }
00044 ss << "]";
00045 return ss.str();
00046 }
00047
00048 double degrees(double radians)
00049 {
00050 return radians * 180.0 / M_PI;
00051 }
00052
00053
00054 class ScopedTickTock
00055 {
00056 public:
00057 std::string start;
00058 std::string end;
00059 ros::Time starttime;
00060 ScopedTickTock(const std::string& _start, const std::string& _end)
00061 : start(_start), end(_end), starttime(ros::Time::now())
00062 {
00063 ROS_INFO_STREAM("TickTock Start: " << start);
00064 }
00065 ~ScopedTickTock()
00066 {
00067 ROS_INFO_STREAM("TickTock Stop: " <<
00068 end <<
00069 " (" <<
00070 (ros::Time::now() - starttime).toSec() <<
00071 ") sec");
00072
00073 }
00074 };
00075
00076 namespace semanticmodel
00077 {
00078
00079 Segmenter::Segmenter(const std::string& name_space,
00080 const std::string& cloud_input_topic,
00081 const std::string& camera_input_topic,
00082 const std::string& depth_input_topic,
00083 const std::string& planecloud_output_topic,
00084 const std::string& plane_output_topic,
00085 const std::string& object_output_topic,
00086 const std::string& plane_service_name)
00087 : handle(name_space),
00088 config(),
00089 dynparam_srv(),
00090 dynparam_func(),
00091 config_mutex(),
00092 listener(NULL),
00093 broadcaster(NULL),
00094 sync(NULL),
00095 cloud_sub(),
00096 rgb_transport(handle),
00097 depth_transport(handle),
00098 cam_sub(),
00099 depth_sub(),
00100 map_points_pub(),
00101 planecloud_pub(),
00102 plane_pub(),
00103 object_pub(),
00104 planetracker_srv(),
00105 latest_planes_(10),
00106 short_circuit(false),
00107 processed(0),
00108 skipped(0)
00109 {
00110
00111 ROS_FATAL_COND(
00112 cloud_input_topic == "" || camera_input_topic == "",
00113 "You must provide both cloud_input_topic and image_input_topic!");
00114 ROS_ASSERT(cloud_input_topic != "" && camera_input_topic != "");
00115
00116
00117 dynparam_func =
00118 boost::bind(&Segmenter::dynamic_reconfigure_callback, this, _1, _2);
00119 dynparam_srv.setCallback(dynparam_func);
00120
00121 listener.reset(new tf::TransformListener(ros::Duration(600)));
00122 broadcaster.reset(new tf::TransformBroadcaster());
00123
00124
00125 normal_estimator_.setNormalEstimationMethod(
00126 normal_estimator_.COVARIANCE_MATRIX);
00127 normal_estimator_.setMaxDepthChangeFactor(0.02f);
00128 normal_estimator_.setNormalSmoothingSize(20.0f);
00129
00130
00131 plane_segmenter_.setMinInliers(1000);
00132 plane_segmenter_.setAngularThreshold(0.05);
00133 plane_segmenter_.setDistanceThreshold(0.02);
00134 comp_.reset(new pcl16::EdgeAwarePlaneComparator<Point, pcl16::Normal>());
00135 plane_segmenter_.setComparator(comp_);
00136
00137
00138 if (!handle.getParam("/short_circuit", short_circuit))
00139 ROS_DEBUG("short_circuit parameter is missing; using false.");
00140 else
00141 ROS_DEBUG_STREAM("Setting short_circuit to " << short_circuit);
00142
00143
00144 sync.reset(new message_filters::Synchronizer<Sync>(Sync(QUEUE_SIZE)));
00145 sync->registerCallback(
00146 boost::bind(&Segmenter::synchronized_pipeline_callback,
00147 this, _1, _2, _3, _4, _5));
00148
00149
00150 cloud_sub = handle.subscribe(cloud_input_topic,
00151 QUEUE_SIZE,
00152 &Segmenter::cloud_cb,
00153 this);
00154 cam_sub = rgb_transport.subscribeCamera(camera_input_topic,
00155 QUEUE_SIZE,
00156 &Segmenter::camera_cb,
00157 this);
00158 depth_sub = depth_transport.subscribeCamera(depth_input_topic,
00159 QUEUE_SIZE,
00160 &Segmenter::depth_cb,
00161 this);
00162
00163 map_points_pub =
00164 handle.advertise<PointCloud>("/map_points", QUEUE_SIZE);
00165 planecloud_pub =
00166 handle.advertise<PointCloud>(planecloud_output_topic, QUEUE_SIZE);
00167 plane_pub =
00168 handle.advertise<Plane>(plane_output_topic, QUEUE_SIZE);
00169 object_pub = handle.advertise<Blobs>(object_output_topic, QUEUE_SIZE);
00170 latest_plane_pub_ =
00171 handle.advertise<PointCloud>("latest_planes", 10);
00172 latest_plane_timer_ = handle.createWallTimer(ros::WallDuration(1.0),
00173 &Segmenter::publishLatestPlanes,
00174 this);
00175
00176 ros::service::waitForService(handle.resolveName(plane_service_name));
00177 planetracker_srv =
00178 handle.serviceClient<semanticmodel::PlaneExchange>(plane_service_name);
00179 ROS_INFO_NAMED("segmenter", "Segmenter initialized");
00180 }
00181
00182 void Segmenter::passthrough(PointCloud::Ptr& in, PointCloud::Ptr& out)
00183 {
00184 pcl16::PassThrough<Point> passfilter;
00185 passfilter.setFilterFieldName("z");
00186 passfilter.setFilterLimits(config.min_z, config.max_z);
00187 passfilter.setInputCloud(in);
00188 passfilter.filter(*out);
00189 }
00190
00191 bool Segmenter::isHorizontal(const pcl16::ModelCoefficients& coeffs) const
00192 {
00193 return fabs(coeffs.values[2]) >= config.horizontal_dot;
00194 }
00195
00196 bool Segmenter::isGroundPlane (const pcl16::ModelCoefficients& coeffs) const
00197 {
00198 return (isHorizontal(coeffs) &&
00199 fabs(coeffs.values[3]) < config.ground_plane_threshold);
00200 }
00201
00202 pcl16::ModelCoefficients
00203 Segmenter::transformCoeffs (const btTransform& trans,
00204 const pcl16::ModelCoefficients& in)
00205 {
00206 ROS_ASSERT(in.values.size()==4);
00207 btVector3 normal(in.values[0], in.values[1], in.values[2]);
00208 btTransform inv = trans.inverse();
00209 const btVector3& origin = inv.getOrigin();
00210 const btQuaternion rot = inv.getRotation();
00211 btVector3 new_normal = inv.getBasis().transpose()*normal;
00212 pcl16::ModelCoefficients trans_coeffs;
00213 trans_coeffs.values.resize(4);
00214 trans_coeffs.values[0] = new_normal.x();
00215 trans_coeffs.values[1] = new_normal.y();
00216 trans_coeffs.values[2] = new_normal.z();
00217 trans_coeffs.values[3] = in.values[3] + normal.dot(origin);
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230 return trans_coeffs;
00231 }
00232
00233 void Segmenter::fit_planes(PointCloud::ConstPtr in,
00234 NormalCloud::ConstPtr normals,
00235 const tf::Transform& trans,
00236 vector<PointCloud::Ptr>& planes,
00237 vector<pcl16::ModelCoefficients>& good_coeffs,
00238 vector<pcl16::ModelCoefficients>& bad_coeffs)
00239 {
00240 ROS_DEBUG_NAMED("planes", "In plane callback");
00241 typedef pcl16::PointCloud<pcl16::Normal> NormalCloud;
00242 typedef pcl16::PointCloud<pcl16::Label> LabelCloud;
00243
00244
00245 vector<pcl16::PlanarRegion<Point> > regions;
00246 vector<pcl16::ModelCoefficients> coeffs;
00247 vector<pcl16::PointIndices> inliers, label_inds, boundary_inds;
00248 LabelCloud::Ptr labels(new LabelCloud());
00249 plane_segmenter_.setInputNormals(normals);
00250 plane_segmenter_.setInputCloud(in);
00251 planes.clear();
00252 good_coeffs.clear();
00253 bad_coeffs.clear();
00254 pcl16::ExtractIndices<Point> extract;
00255 extract.setInputCloud(in);
00256 extract.setNegative(false);
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267 std::vector<Eigen::Vector4f, Eigen::aligned_allocator<Eigen::Vector4f> > centroids;
00268 std::vector <Eigen::Matrix3f, Eigen::aligned_allocator<Eigen::Matrix3f> > covariances;
00269 plane_segmenter_.segment(coeffs, inliers, centroids, covariances,
00270 *labels, label_inds);
00271 ROS_DEBUG_NAMED("planes", "Found %zu segments", regions.size());
00272
00273
00274 for (size_t i=0; i<coeffs.size(); i++)
00275 {
00276 pcl16::ModelCoefficients transformed_coeffs =
00277 transformCoeffs(trans.asBt(), coeffs[i]);
00278
00279 if (isHorizontal(transformed_coeffs))
00280 {
00281 PointCloud::Ptr plane_cloud(new PointCloud());
00282 PointCloud::Ptr transformed(new PointCloud());
00283 pcl16::PointIndices::Ptr inds(new pcl16::PointIndices(inliers[i]));
00284 extract.setIndices(inds);
00285 extract.filter(*plane_cloud);
00286 pcl16_ros::transformPointCloud(*plane_cloud, *transformed, trans);
00287 transformed->header.frame_id = config.canonical_frame;
00288
00289
00290
00291
00292
00293
00294
00295
00296 float sum=0.0;
00297 BOOST_FOREACH (const Point& p, transformed->points)
00298 sum += p.z;
00299 transformed_coeffs.values[0] = 0;
00300 transformed_coeffs.values[1] = 0;
00301 transformed_coeffs.values[2] = 1;
00302 transformed_coeffs.values[3] = -sum/transformed->points.size();
00303
00304 if (isGroundPlane(transformed_coeffs))
00305 {
00306
00307 bad_coeffs.push_back(transformed_coeffs);
00308 continue;
00309 }
00310 planes.push_back(transformed);
00311 good_coeffs.push_back(transformed_coeffs);
00312
00313
00314
00315
00316
00317
00318
00319 }
00320 else
00321 {
00322 bad_coeffs.push_back(transformed_coeffs);
00323 }
00324 }
00325
00326
00327 }
00328
00329 void Segmenter::convex_hulls(const std::vector<PointCloud::Ptr>& planes,
00330 std::vector<PointCloud::Ptr>& hulls)
00331 {
00332 pcl16::ConvexHull<Point> hull;
00333 hull.setDimension(2);
00334 foreach(const PointCloud::Ptr& plane, planes)
00335 {
00336 PointCloud::Ptr out(new PointCloud());
00337 hull.setInputCloud(plane);
00338 hull.reconstruct(*out);
00339 hulls.push_back(out);
00340 }
00341 }
00342
00343 void Segmenter::publish_planes(
00344 const std::vector<PointCloud::Ptr>& planes,
00345 const std::vector<PointCloud::Ptr>& hulls,
00346 const std::vector<pcl16::ModelCoefficients>& coeffs,
00347 std::vector<Plane>& outputs)
00348 {
00349 ROS_ASSERT(planes.size() == hulls.size());
00350 ROS_ASSERT(hulls.size() == coeffs.size());
00351
00352
00353
00354
00355
00356
00357 if (planes.size() == 0)
00358 return;
00359
00360 size_t largest = planes[0]->points.size();
00361 size_t largest_idx = 0;
00362 for (size_t i = 0 ; i < planes.size() ; ++i)
00363 {
00364 if (planes[i]->points.size() > largest)
00365 {
00366 largest = planes[i]->points.size();
00367 largest_idx = i;
00368 }
00369 }
00370
00371
00372 PlaneExchange exchange;
00373 exchange.request.in_planes.planes.resize(planes.size());
00374 for (size_t i = 0 ; i < planes.size() ; ++i)
00375 {
00376 Plane planemsg;
00377 planemsg.header = planes[i]->header;
00378
00379 planemsg.a = coeffs[i].values[0];
00380 planemsg.b = coeffs[i].values[1];
00381 planemsg.c = coeffs[i].values[2];
00382 planemsg.d = coeffs[i].values[3];
00383
00384 Eigen::Vector4f center = centroid(planes[i]);
00385 planemsg.center.x = center[0];
00386 planemsg.center.y = center[1];
00387 planemsg.center.z = center[2];
00388
00389 pcl16::toROSMsg(*hulls[i], planemsg.hull);
00390 pcl16::toROSMsg(*planes[i], planemsg.cloud);
00391
00392 exchange.request.in_planes.planes[i] = planemsg;
00393
00394
00395 tf::Transform transform;
00396 transform.setOrigin(tf::Vector3(center[0], center[1], center[2]));
00397 transform.setRotation(tf::Quaternion(0, 0, 1.0, 0.0));
00398 std::string name = (boost::format("plane_%02d") % i).str();
00399 tf::StampedTransform trans(transform,
00400 planes[i]->header.stamp,
00401 config.canonical_frame,
00402 name);
00403 broadcaster->sendTransform(trans);
00404
00405
00406 if (i == largest_idx)
00407 {
00408 tf::StampedTransform trans2(transform,
00409 planes[i]->header.stamp,
00410 config.canonical_frame,
00411 "best_plane");
00412 broadcaster->sendTransform(trans2);
00413 }
00414 }
00415
00416 planetracker_srv.call(exchange);
00417 outputs = exchange.response.out_planes.planes;
00418 }
00419
00420 void Segmenter::get_aboves(
00421 const std::vector<PointCloud::Ptr>& hulls,
00422 const std::vector<pcl16::ModelCoefficients>& coeffs,
00423 const PointCloud::Ptr& densecloud,
00424 PointCloud::Ptr& out)
00425 {
00426 out->header = densecloud->header;
00427 foreach(Point& p, *densecloud)
00428 {
00429
00430
00431 bool add = false;
00432 for (size_t i = 0 ; i < hulls.size() ; ++i)
00433 {
00434
00435 double ppdist = coeffs[i].values[0] * p.x +
00436 coeffs[i].values[1] * p.y +
00437 coeffs[i].values[2] * p.z +
00438 coeffs[i].values[3];
00439 if (hulls[i]->points.size()>3 && pcl16::isXYPointIn2DXYPolygon(p, *hulls[i]))
00440 {
00441 if (ppdist > config.plane_distance)
00442 {
00443 add = true;
00444 }
00445 else
00446 {
00447 add = false;
00448 break;
00449 }
00450 }
00451 }
00452 if (add)
00453 {
00454 out->points.push_back(p);
00455 }
00456 }
00457 }
00458
00459 void Segmenter::lose_bads(const PointCloud::ConstPtr& aboves,
00460 PointCloud::Ptr good_aboves,
00461 const vector<pcl16::ModelCoefficients>& bad_coeffs)
00462 {
00463 good_aboves->header = aboves->header;
00464 foreach(const Point& p, *aboves)
00465 {
00466 bool add = true;
00467 foreach(pcl16::ModelCoefficients model, bad_coeffs)
00468 {
00469 double ppdist = model.values[0] * p.x +
00470 model.values[1] * p.y +
00471 model.values[2] * p.z +
00472 model.values[3];
00473 if (fabs(ppdist) <= config.plane_tolerance)
00474 {
00475 add = false;
00476 break;
00477 }
00478 }
00479 if (add)
00480 good_aboves->points.push_back(p);
00481 }
00482 }
00483
00484 void Segmenter::publishLatestPlanes (const ros::WallTimerEvent& e)
00485 {
00486 static size_t i=0;
00487 boost::mutex::scoped_lock lock(config_mutex);
00488
00489 if (latest_planes_.size()>0)
00490 {
00491 i = (i+1)%latest_planes_.size();
00492 latest_plane_pub_.publish(*latest_planes_[i]);
00493 }
00494 }
00495
00496 void Segmenter::synchronized_pipeline_callback(
00497 const sensor_msgs::ImageConstPtr& rgb_img,
00498 const sensor_msgs::CameraInfoConstPtr& rgb_info,
00499 const sensor_msgs::ImageConstPtr& depth_img,
00500 const sensor_msgs::CameraInfoConstPtr& depth_info,
00501 const PointCloud::ConstPtr& cloud)
00502 {
00503
00504 boost::mutex::scoped_lock lock(config_mutex);
00505
00506 if (processed + skipped > 0)
00507 {
00508 ROS_DEBUG_NAMED("score",
00509 "Processed %d frames. Skipped %d frames. (%f processed)",
00510 processed,
00511 skipped,
00512 ((float)processed) / (processed + skipped));
00513 }
00514
00515 ROS_DEBUG_STREAM_NAMED("timer",
00516 "We are behind by "
00517 << ros::Time::now() - rgb_img->header.stamp);
00518
00519 if (rgb_img->header.stamp + ros::Duration(config.filter_lag_cutoff) <
00520 ros::Time::now())
00521 {
00522 ROS_WARN_STREAM_NAMED("timer",
00523 "Too old! (" <<
00524 ros::Time::now() - rgb_img->header.stamp << ")");
00525 skipped++;
00526 return;
00527 }
00528 else
00529 {
00530 ROS_DEBUG_STREAM_NAMED(
00531 "filter",
00532 "Processing filter input at " << rgb_img->header.stamp);
00533 processed++;
00534 }
00535
00536
00537
00538 tf::StampedTransform trans;
00539 bool found =
00540 listener->waitForTransform(config.canonical_frame, cloud->header.frame_id,
00541 cloud->header.stamp, ros::Duration(1.0));
00542 try
00543 {
00544 if (found)
00545 listener->lookupTransform(config.canonical_frame, cloud->header.frame_id,
00546 cloud->header.stamp, trans);
00547 }
00548 catch (tf::TransformException& e)
00549 {
00550 found = false;
00551 }
00552 if (!found)
00553 {
00554 ROS_WARN_STREAM("Couldn't find transform between " <<
00555 cloud->header.frame_id <<
00556 " and " <<
00557 config.canonical_frame <<
00558 " at "
00559 <<
00560 cloud->header.stamp << "; skipping");
00561 return;
00562 }
00563 PointCloud::Ptr transformed(new PointCloud());
00564 pcl16_ros::transformPointCloud(*cloud, *transformed, trans);
00565 transformed->header.frame_id = config.canonical_frame;
00566
00567
00568
00569
00570
00571
00572 NormalCloud::Ptr normals(new NormalCloud());
00573 normal_estimator_.setInputCloud(cloud);
00574 normal_estimator_.compute(*normals);
00575
00576
00577
00578 std::vector<PointCloud::Ptr> good_planes;
00579 std::vector<pcl16::ModelCoefficients> good_coeffs, bad_coeffs;
00580 float* distance_map = normal_estimator_.getDistanceMap();
00581 comp_->setDistanceMap(distance_map);
00582 fit_planes(cloud, normals, trans, good_planes, good_coeffs, bad_coeffs);
00583
00584
00585
00586 std::vector<PointCloud::Ptr> hulls;
00587 convex_hulls(good_planes, hulls);
00588 if (!ros::ok())
00589 return;
00590
00591
00592 std::vector<Plane> returnedplanes;
00593 publish_planes(good_planes, hulls, good_coeffs, returnedplanes);
00594 BOOST_FOREACH (PointCloud::Ptr plane, good_planes)
00595 latest_planes_.push_back(plane);
00596 if (short_circuit || !ros::ok())
00597 return;
00598
00599
00600 hulls.clear();
00601 good_coeffs.clear();
00602 foreach(Plane& p, returnedplanes)
00603 {
00604 hulls.push_back(PointCloud::Ptr(new PointCloud()));
00605 pcl16::fromROSMsg(p.hull, *hulls.back());
00606 good_coeffs.push_back(pcl16::ModelCoefficients());
00607 good_coeffs.back().values.resize(4);
00608 good_coeffs.back().values[0] = p.a;
00609 good_coeffs.back().values[1] = p.b;
00610 good_coeffs.back().values[2] = p.c;
00611 good_coeffs.back().values[3] = p.d;
00612 }
00613
00614
00615 map_points_pub.publish(transformed);
00616 if (transformed->points.size() == 0 || !ros::ok())
00617 {
00618 ROS_ERROR("No transformed points in filter");
00619 return;
00620 }
00621
00622 PointCloud::Ptr aboves(new PointCloud());
00623 get_aboves(hulls, good_coeffs, transformed, aboves);
00624 if (aboves->points.size() == 0 || !ros::ok())
00625 {
00626 ROS_DEBUG_NAMED("aboves", "No above points; skipping");
00627 return;
00628 }
00629
00630
00631
00632
00633
00634 PointCloud::Ptr good_aboves(new PointCloud());
00635 lose_bads(aboves, good_aboves, bad_coeffs);
00636
00637 aboves.reset();
00638
00639 if (good_aboves->points.size() == 0 || !ros::ok())
00640 {
00641 ROS_WARN("No good above points; skipping.");
00642 return;
00643 }
00644
00645
00646
00647
00648
00649
00650
00651
00652 Blobs msg;
00653 msg.blobs.resize(1);
00654 msg.rgb_info = *rgb_info;
00655 msg.rgb_image = *rgb_img;
00656 msg.depth_info = *depth_info;
00657 msg.depth_image = *depth_img;
00658 pcl16::toROSMsg(*good_aboves, msg.blobs.at(0));
00659 object_pub.publish(msg);
00660 }
00661
00662 void Segmenter::dynamic_reconfigure_callback(const FilterConfig& config,
00663 uint32_t level)
00664 {
00665
00666 boost::mutex::scoped_lock lock(config_mutex);
00667 this->config = config;
00668 }
00669
00670
00671
00672 void Segmenter::cloud_cb(const PointCloud::ConstPtr& cloud)
00673 {
00674 sync->add<4>(cloud);
00675 }
00676
00677 void Segmenter::camera_cb(const sensor_msgs::ImageConstPtr& img,
00678 const sensor_msgs::CameraInfoConstPtr& info)
00679 {
00680 sync->add<0>(img);
00681 sync->add<1>(info);
00682 }
00683
00684 void Segmenter::depth_cb(const sensor_msgs::ImageConstPtr& img,
00685 const sensor_msgs::CameraInfoConstPtr& info)
00686 {
00687 sync->add<2>(img);
00688 sync->add<3>(info);
00689 }
00690
00691 }