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 <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
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;
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
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);
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
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
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
00379 pcl::PCA<PclPoint> pca;
00380 pca.setInputCloud(cloud);
00381 Eigen::Matrix3f eigen_vectors = pca.getEigenVectors();
00382
00383
00384 PclPointCloud projected_cloud;
00385 pca.project(*cloud, projected_cloud);
00386
00387
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
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();
00401
00402
00403 float cos_between_z_axes = eigen_vectors.col(2).dot(Eigen::Vector3f::UnitZ());
00404 float z_orientation = (cos_between_z_axes > 0) - (cos_between_z_axes < 0);
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
00408 Eigen::Quaternion<float> orientation;
00409 orientation.setFromTwoVectors(Eigen::Vector3f::UnitX(), eigen_vectors.col(0));
00410
00411
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
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