box_detection_node.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  *
00003  *  Software License Agreement (BSD License)
00004  *
00005  *  Copyright (c) 2014, Alexander Buchegger
00006  *                      Institute for Software Technology,
00007  *                      Graz University of Technology
00008  *  All rights reserved.
00009  *
00010  *  Redistribution and use in source and binary forms, with or without
00011  *  modification, are permitted provided that the following conditions
00012  *  are met:
00013  *
00014  *   * Redistributions of source code must retain the above copyright
00015  *     notice, this list of conditions and the following disclaimer.
00016  *   * Redistributions in binary form must reproduce the above
00017  *     copyright notice, this list of conditions and the following
00018  *     disclaimer in the documentation and/or other materials provided
00019  *     with the distribution.
00020  *   * Neither the name of Graz University of Technology nor the names of
00021  *     its contributors may be used to endorse or promote products derived
00022  *     from this software without specific prior written permission.
00023  *
00024  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00025  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00026  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00027  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00028  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00029  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00030  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00031  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00032  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00033  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00034  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00035  *  POSSIBILITY OF SUCH DAMAGE.
00036  *
00037  *********************************************************************/
00038 #include <tedusar_box_detection/box_detection_node.h>
00039 #include <iostream>
00040 #include <sstream>
00041 #include <boost/algorithm/string.hpp>
00042 #include <boost/bind.hpp>
00043 #include <boost/make_shared.hpp>
00044 #include <pcl/common/common.h>
00045 #include <pcl/common/pca.h>
00046 #include <pcl/conversions.h>
00047 #include <pcl/filters/extract_indices.h>
00048 #include <pcl/filters/voxel_grid.h>
00049 #include <pcl/segmentation/sac_segmentation.h>
00050 #include <pcl/segmentation/extract_clusters.h>
00051 #include <pcl/surface/convex_hull.h>
00052 #include <pcl_conversions/pcl_conversions.h>
00053 #include <pcl_ros/transforms.h>
00054 #include <moveit_msgs/CollisionObject.h>
00055 #include <visualization_msgs/MarkerArray.h>
00056 
00057 
00058 
00059 static bool operator<=(const Eigen::Vector3f & v1, const Eigen::Vector3f & v2)
00060 {
00061     return v1(0) <= v2(0) && v1(1) <= v2(1) && v1(2) <= v2(2);
00062 }
00063 
00064 namespace tedusar_box_detection
00065 {
00066 
00067 BoxDetectionNode::BoxDetectionNode()
00068     : box_detection_as_(nh_, "detect_boxes", false), plane_intensity_(0), box_counter_(0)
00069 {
00070     loadParameters();
00071 
00072     pcl::console::setVerbosityLevel(pcl::console::L_ALWAYS);
00073 
00074     if (parameters_.have_collision_object_publisher_)
00075         planning_scene_publisher_ = nh_.advertise<moveit_msgs::PlanningScene>("planning_scene", 1);
00076 
00077     if (parameters_.have_visualization_marker_publisher_)
00078         visualization_marker_publisher_ = nh_.advertise<visualization_msgs::MarkerArray>("visualization_marker_array", 10);
00079 
00080     if (parameters_.have_plane_publisher_)
00081     {
00082         plane_cloud_publisher_ = nh_.advertise<sensor_msgs::PointCloud>("plane_cloud", 1);
00083         plane_publisher_timer_ = nh_.createTimer(ros::Rate(parameters_.plane_publishing_rate_), &BoxDetectionNode::planePublisherTimerCallback, this);
00084     }
00085 
00086     box_detection_as_.registerGoalCallback(boost::bind(&BoxDetectionNode::boxDetectionActionGoalCallback, this));
00087     box_detection_as_.registerPreemptCallback(boost::bind(&BoxDetectionNode::boxDetectionActionPreemptCallback, this));
00088     box_detection_as_.start();
00089 
00090     if (parameters_.have_action_server_debug_output_)
00091         ROS_INFO("Action server started, waiting for goal");
00092 }
00093 
00094 void BoxDetectionNode::loadParameters()
00095 {
00096     ros::NodeHandle private_nh("~");
00097 #define GOP(key, default_value) getOptionalParameter(private_nh, #key, parameters_.key##_, default_value)
00098 #define GRP(key) getRequiredParameter(private_nh, #key, parameters_.key##_)
00099     GOP(point_cloud_topic, std::string("/camera/depth/points"));
00100     GOP(target_frame_id, std::string("base_link"));
00101     GOP(have_action_server_debug_output, true);
00102     GOP(have_box_detection_debug_output, true);
00103 
00104     GOP(box_plane_points_min, 100);
00105     GRP(box_plane_size_min);
00106     GRP(box_plane_size_max);
00107     GOP(detection_timeout, 30.0);
00108     GOP(plane_fitting_distance_threshold, 0.02);
00109     GOP(plane_fitting_max_iterations, 50);
00110     GOP(downsampling_leaf_size, 0.005);
00111     GOP(clusterization_tolerance, 0.02);
00112 
00113     GOP(have_plane_publisher, true);
00114     GOP(plane_publishing_rate, 2.0);
00115 
00116     GOP(have_collision_object_publisher, true);
00117     GOP(collision_objects_basename, std::string("box"));
00118 
00119     GOP(have_visualization_marker_publisher, true);
00120     GOP(visualization_marker_namespace, std::string("tedusar_box_detection"));
00121 #undef GOP
00122 #undef GRP
00123 }
00124 
00125 template <typename T>
00126 void BoxDetectionNode::getRequiredParameter(ros::NodeHandle & private_nh, const std::string & key, T & value)
00127 {
00128     if (!getParameter(private_nh, key, value))
00129     {
00130         ROS_FATAL_STREAM("Missing required node parameter " << key);
00131         throw std::runtime_error("Missing required node parameter " + key);
00132     }
00133 }
00134 
00135 template <typename T>
00136 void BoxDetectionNode::getOptionalParameter(ros::NodeHandle & private_nh, const std::string & key, T & value, T default_value)
00137 {
00138     if (!getParameter(private_nh, key, value))
00139     {
00140         value = default_value;
00141         ROS_WARN_STREAM("Missing optional node parameter " << key << ", using default value " << default_value);
00142     }
00143 }
00144 
00145 template <typename T> bool BoxDetectionNode::getParameter(ros::NodeHandle & private_nh, const std::string & key, T & value)
00146 {
00147     return private_nh.getParam(key, value);
00148 }
00149 
00150 template <> bool BoxDetectionNode::getParameter(ros::NodeHandle & private_nh, const std::string & key, Eigen::Vector3f & value)
00151 {
00152     std::string value_as_string;
00153     if (private_nh.getParam(key, value_as_string))
00154     {
00155         std::vector<std::string> parts;
00156         boost::split(parts, value_as_string, boost::is_any_of(" \t"));
00157         if (parts.size() == 3)
00158         {
00159             for (size_t i = 0; i < parts.size(); ++i)
00160             {
00161                 std::istringstream ss(parts.at(i));
00162                 ss >> value(i);
00163                 if (ss.fail())
00164                     return false;
00165             }
00166             return true;
00167         }
00168     }
00169     return false;
00170 }
00171 
00172 void BoxDetectionNode::boxDetectionActionGoalCallback()
00173 {
00174     if (parameters_.have_action_server_debug_output_)
00175         ROS_INFO("BoxDetection action started");
00176 
00177     box_detection_as_.acceptNewGoal();
00178 
00179     point_cloud_subscriber_ = nh_.subscribe(parameters_.point_cloud_topic_, 1, &BoxDetectionNode::pointCloudCallback, this);
00180 
00181     detection_timeout_time_ = ros::Time::now() + ros::Duration(parameters_.detection_timeout_);
00182 
00183     plane_cloud_ = sensor_msgs::PointCloud();
00184     plane_cloud_.header.frame_id = parameters_.target_frame_id_;
00185     plane_cloud_.channels.resize(1);
00186     plane_cloud_.channels.at(0).name = "intensity";
00187     plane_intensity_ = 0;
00188 
00189     // Delete all visualization markers:
00190     if (visualization_marker_publisher_ && !boxes_.empty())
00191     {
00192         visualization_msgs::MarkerArrayPtr markers = boost::make_shared<visualization_msgs::MarkerArray>();
00193         markers->markers.resize(boxes_.size());
00194         for (size_t i = 0; i < boxes_.size(); ++i)
00195         {
00196             visualization_msgs::Marker & marker = markers->markers[i];
00197             marker.ns = parameters_.visualization_marker_namespace_;
00198             marker.id = boxes_[i].id_;
00199             marker.header.frame_id = boxes_[i].pose_.header.frame_id;
00200             marker.action = visualization_msgs::Marker::DELETE;
00201         }
00202         visualization_marker_publisher_.publish(markers);
00203     }
00204 
00205     boxes_.clear();
00206 }
00207 
00208 void BoxDetectionNode::boxDetectionActionPreemptCallback()
00209 {
00210     box_detection_as_.setPreempted();
00211     if (parameters_.have_action_server_debug_output_)
00212         ROS_INFO("BoxDetection action preempted");
00213 }
00214 
00215 void BoxDetectionNode::pointCloudCallback(const sensor_msgs::PointCloud2ConstPtr & msg)
00216 {
00217     point_cloud_subscriber_.shutdown();
00218 
00219     try
00220     {
00221       tf_listener_.waitForTransform(parameters_.target_frame_id_, msg->header.frame_id, ros::Time::now(), ros::Duration(10.0));
00222     }
00223     catch (tf::TransformException & ex)
00224     {
00225       ROS_ERROR_STREAM("TransformException: " << ex.what());
00226       box_detection_as_.setAborted(tedusar_box_detection_msgs::BoxDetectionResult(), "tf transformation failed");
00227       return;
00228     }
00229 
00230     PclPointCloud::Ptr cloud = boost::make_shared<PclPointCloud>();
00231     PclPointCloud::Ptr transformed_cloud = boost::make_shared<PclPointCloud>();
00232 
00233     pcl::fromROSMsg(*msg, *cloud);
00234     pcl_ros::transformPointCloud(parameters_.target_frame_id_, *cloud, *transformed_cloud, tf_listener_);
00235 
00236     do
00237     {
00238         PclPointCloud::Ptr plane_cloud = boost::make_shared<PclPointCloud>();
00239         PclPointCloud::Ptr remaining_cloud = boost::make_shared<PclPointCloud>();
00240         PclPointCloud::Ptr downsampled_plane_cloud = boost::make_shared<PclPointCloud>();
00241 
00242         pcl::PointIndices::Ptr inliers = boost::make_shared<pcl::PointIndices>();
00243         fitPlane(transformed_cloud, inliers);
00244 
00245         if (inliers->indices.size() < parameters_.box_plane_points_min_)
00246             break; // Plane isn't even big enough for one box => stop
00247 
00248         if (parameters_.have_box_detection_debug_output_)
00249             ROS_INFO("Found plane with %d points", inliers->indices.size());
00250 
00251         extractPlane(transformed_cloud, inliers, *plane_cloud, *remaining_cloud);
00252         downsampleCloud(plane_cloud, *downsampled_plane_cloud);
00253 
00254         if (parameters_.have_box_detection_debug_output_)
00255             ROS_INFO("Downsampled it to %d points", downsampled_plane_cloud->size());
00256 
00257         // Split into individual planes; may return empty list if clusters are too small:
00258         std::vector<pcl::PointIndices> cluster_indices;
00259         clusterizeCloud(downsampled_plane_cloud, cluster_indices);
00260 
00261         if (parameters_.have_box_detection_debug_output_)
00262             ROS_INFO("Clustered into %d clusters", cluster_indices.size());
00263 
00264         pcl::PointIndices::Ptr point_indices = boost::make_shared<pcl::PointIndices>();
00265         for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); ++it)
00266         {
00267             pcl::ExtractIndices<PclPoint> extractor;
00268             extractor.setInputCloud(downsampled_plane_cloud);
00269             *point_indices = *it;
00270             extractor.setIndices(point_indices); // Must be done after assigning new indices to point_indices!
00271 
00272             PclPointCloud::Ptr cloud_cluster = boost::make_shared<PclPointCloud>();
00273             extractor.filter(*cloud_cluster);
00274             if (parameters_.have_box_detection_debug_output_)
00275                 ROS_INFO("Checking cluster with %d points", cloud_cluster->size());
00276 
00277             publishClusterCloud(cloud_cluster);
00278 
00279             // Check if we found a box:
00280             Box box;
00281             if (fitBox(cloud_cluster, box))
00282             {
00283                 boxes_.push_back(box);
00284 
00285                 if (parameters_.have_collision_object_publisher_)
00286                     publishCollisionObject(box);
00287                 if (parameters_.have_visualization_marker_publisher_)
00288                     publishVisualizationMarker(box);
00289                 publishActionFeedback(box);
00290 
00291                 if (parameters_.have_box_detection_debug_output_)
00292                     ROS_INFO_STREAM("FOUND BOX! Name: " << box.name_ << "; pose: " << box.pose_.pose << "; size: " << box.size_);
00293             }
00294         }
00295 
00296         transformed_cloud = remaining_cloud;
00297     }
00298     while (box_detection_as_.isActive() && ros::Time::now() < detection_timeout_time_);
00299 
00300     if (box_detection_as_.isActive())
00301     {
00302         tedusar_box_detection_msgs::BoxDetectionResult result;
00303         for (std::vector<Box>::iterator box_it = boxes_.begin(); box_it != boxes_.end(); ++box_it)
00304         {
00305             result.box_names.push_back(box_it->name_);
00306             result.box_poses.push_back(box_it->pose_);
00307         }
00308         box_detection_as_.setSucceeded(result);
00309     }
00310     if (parameters_.have_action_server_debug_output_)
00311         ROS_INFO("BoxDetection action finished; found %d boxes", boxes_.size());
00312 }
00313 
00314 
00315 void BoxDetectionNode::planePublisherTimerCallback(const ros::TimerEvent &)
00316 {
00317     plane_cloud_publisher_.publish(plane_cloud_);
00318 }
00319 
00320 
00321 void BoxDetectionNode::fitPlane(const PclPointCloud::ConstPtr & cloud, pcl::PointIndices::Ptr & inliers)
00322 {
00323     pcl::SACSegmentation<PclPoint> segmentation;
00324 
00325     segmentation.setModelType(pcl::SACMODEL_PLANE);
00326     segmentation.setMethodType(pcl::SAC_RANSAC);
00327     segmentation.setMaxIterations(parameters_.plane_fitting_max_iterations_);
00328     segmentation.setDistanceThreshold(parameters_.plane_fitting_distance_threshold_);
00329     segmentation.setInputCloud(cloud);
00330 
00331     //seg.setOptimizeCoefficients(true);
00332 
00333     pcl::ModelCoefficients coefficients;
00334     segmentation.segment(*inliers, coefficients);
00335 }
00336 
00337 void BoxDetectionNode::extractPlane(const PclPointCloud::ConstPtr & cloud,
00338                                     const pcl::PointIndices::ConstPtr & inliers,
00339                                     PclPointCloud & cloud_plane,
00340                                     PclPointCloud & cloud_rest)
00341 {
00342     pcl::ExtractIndices<PclPoint> extract;
00343 
00344     extract.setInputCloud(cloud);
00345     extract.setIndices(inliers);
00346 
00347     extract.setNegative(false);
00348     extract.filter(cloud_plane);
00349 
00350     extract.setNegative(true);
00351     extract.filter(cloud_rest);
00352 }
00353 
00354 void BoxDetectionNode::downsampleCloud(const PclPointCloud::ConstPtr & cloud, PclPointCloud & cloud_filtered)
00355 {
00356     pcl::VoxelGrid<PclPoint> filter;
00357     filter.setInputCloud(cloud);
00358     filter.setLeafSize(parameters_.downsampling_leaf_size_, parameters_.downsampling_leaf_size_, parameters_.downsampling_leaf_size_);
00359     filter.filter(cloud_filtered);
00360 }
00361 
00362 void BoxDetectionNode::clusterizeCloud(const PclPointCloud::ConstPtr & cloud, std::vector<pcl::PointIndices> & cluster_indices)
00363 {
00364     pcl::search::KdTree<PclPoint>::Ptr tree = boost::make_shared<pcl::search::KdTree<PclPoint> >();
00365     tree->setInputCloud(cloud);
00366 
00367     pcl::EuclideanClusterExtraction<PclPoint> ece;
00368     ece.setInputCloud(cloud);
00369     ece.setSearchMethod(tree);
00370     ece.setClusterTolerance(parameters_.clusterization_tolerance_);
00371     ece.setMinClusterSize(parameters_.box_plane_points_min_);
00372 
00373     ece.extract(cluster_indices);
00374 }
00375 
00376 bool BoxDetectionNode::fitBox(const PclPointCloud::ConstPtr & cloud, Box & box)
00377 {
00378     // Compute eigen vectors of cloud, which is basically the orientation of the cloud (first eigen vector points in the direction of the largest extent):
00379     pcl::PCA<PclPoint> pca;
00380     pca.setInputCloud(cloud);
00381     Eigen::Matrix3f eigen_vectors = pca.getEigenVectors();
00382 
00383     // Transform cloud into eigen vector space:
00384     PclPointCloud projected_cloud;
00385     pca.project(*cloud, projected_cloud);
00386 
00387     // Get size of projected plane:
00388     Eigen::Vector4f min_pt, max_pt;
00389     pcl::getMinMax3D(projected_cloud, min_pt, max_pt);
00390     Eigen::Vector3f size = max_pt.topRows(3) - min_pt.topRows(3);
00391 
00392     // Fail if cloud size is outside sensible limits:
00393     if (!(parameters_.box_plane_size_min_ <= size && size <= parameters_.box_plane_size_max_))
00394     {
00395         if (parameters_.have_box_detection_debug_output_)
00396             ROS_INFO_STREAM("Cluster size is outside limits: " << size);
00397         return false;
00398     }
00399 
00400     size.z() = size.y(); // We don't get the box's height from the plane, so we simply assume a square base
00401 
00402     // Compute center = center of plane minus height/2:
00403     float cos_between_z_axes = eigen_vectors.col(2).dot(Eigen::Vector3f::UnitZ()); // We have to check whether z axis points up or down
00404     float z_orientation = (cos_between_z_axes > 0) - (cos_between_z_axes < 0); // Signum of cosine (don't ask...)
00405     Eigen::Vector3f center = pca.getMean().topRows(3) - z_orientation * eigen_vectors.transpose() * Eigen::Vector3f(0.0f, 0.0f, size.z() / 2.0f);
00406 
00407     // Compute orientation (this is a bit simplified, I didn't want to think about quaternion computations...):
00408     Eigen::Quaternion<float> orientation;
00409     orientation.setFromTwoVectors(Eigen::Vector3f::UnitX(), eigen_vectors.col(0));
00410 
00411     // Generate id and name and put data into Box structure:
00412     box.id_ = box_counter_++;
00413 
00414     std::ostringstream ss;
00415     ss << parameters_.collision_objects_basename_ << box.id_;
00416     box.name_ = ss.str();
00417 
00418     box.pose_.header.frame_id = parameters_.target_frame_id_;
00419     box.pose_.header.stamp = ros::Time::now();
00420     box.pose_.pose.position.x = center(0);
00421     box.pose_.pose.position.y = center(1);
00422     box.pose_.pose.position.z = center(2);
00423     box.pose_.pose.orientation.x = orientation.x();
00424     box.pose_.pose.orientation.y = orientation.y();
00425     box.pose_.pose.orientation.z = orientation.z();
00426     box.pose_.pose.orientation.w = orientation.w();
00427 
00428     box.size_.x = size.x();
00429     box.size_.y = size.y();
00430     box.size_.z = size.z();
00431 
00432     return true;
00433 }
00434 
00435 void BoxDetectionNode::publishClusterCloud(const PclPointCloud::ConstPtr & cloud)
00436 {
00437     // Add cluster to plane cloud:
00438     plane_intensity_ += 1.0f;
00439     for (PclPointCloud::const_iterator p_it = cloud->begin(); p_it != cloud->end(); ++p_it)
00440     {
00441         geometry_msgs::Point32 point;
00442         point.x = p_it->x;
00443         point.y = p_it->y;
00444         point.z = p_it->z;
00445         plane_cloud_.points.push_back(point);
00446         plane_cloud_.channels.at(0).values.push_back(plane_intensity_);
00447     }
00448 }
00449 
00450 void BoxDetectionNode::publishCollisionObject(const Box & box)
00451 {
00452     shape_msgs::SolidPrimitive primitive;
00453     primitive.type = shape_msgs::SolidPrimitive::BOX;
00454     primitive.dimensions.resize(3);
00455     primitive.dimensions[0] = box.size_.x;
00456     primitive.dimensions[1] = box.size_.y;
00457     primitive.dimensions[2] = box.size_.z;
00458 
00459     moveit_msgs::CollisionObject object;
00460     object.header = box.pose_.header;
00461     object.id = box.name_;
00462     object.primitives.push_back(primitive);
00463     object.primitive_poses.push_back(box.pose_.pose);
00464     object.operation = moveit_msgs::CollisionObject::ADD;
00465 
00466     moveit_msgs::PlanningScene planning_scene;
00467     planning_scene.is_diff = true;
00468     planning_scene.world.collision_objects.push_back(object);
00469     planning_scene_publisher_.publish(planning_scene);
00470 }
00471 
00472 void BoxDetectionNode::publishVisualizationMarker(const Box & box)
00473 {
00474     visualization_msgs::MarkerArrayPtr markers = boost::make_shared<visualization_msgs::MarkerArray>();
00475     markers->markers.resize(1);
00476     visualization_msgs::Marker & marker = markers->markers.at(0);
00477     marker.action = visualization_msgs::Marker::ADD;
00478     marker.type = visualization_msgs::Marker::CUBE;
00479     marker.ns = parameters_.visualization_marker_namespace_;
00480     marker.id = box.id_;
00481     marker.header.frame_id = box.pose_.header.frame_id;
00482     marker.pose = box.pose_.pose;
00483     marker.scale = box.size_;
00484     marker.color.r = 0.8f;
00485     marker.color.g = 0.8f;
00486     marker.color.b = 0.2f;
00487     marker.color.a = 0.5f;
00488     visualization_marker_publisher_.publish(markers);
00489 }
00490 
00491 void BoxDetectionNode::publishActionFeedback(const Box & box){
00492     tedusar_box_detection_msgs::BoxDetectionFeedback feedback;
00493     feedback.box_name = box.name_;
00494     feedback.box_pose = box.pose_;
00495     box_detection_as_.publishFeedback(feedback);
00496 }
00497 
00498 
00499 
00500 }
00501 
00502 int main(int argc, char** argv)
00503 {
00504     ros::init (argc, argv, "box_detection");
00505 
00506     try
00507     {
00508         tedusar_box_detection::BoxDetectionNode node;
00509         ros::spin();
00510         return 0;
00511     }
00512     catch (std::exception & ex)
00513     {
00514         std::cerr << "Unhandled exception: " << ex.what() << std::endl;
00515         return 1;
00516     }
00517 }
00518 
00519 


tedusar_box_detection
Author(s):
autogenerated on Wed Aug 26 2015 16:30:42