block_detection_action_server.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011, Willow Garage, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Willow Garage, Inc. nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  *
29  * Author: Michael Ferguson, Helen Oleynikova
30  */
31 
32 #include <ros/ros.h>
33 #include <sensor_msgs/PointCloud2.h>
35 #include <turtlebot_arm_block_manipulation/BlockDetectionAction.h>
36 
37 #include <tf/transform_listener.h>
38 
39 #include <pcl/conversions.h>
40 #include <pcl/point_cloud.h>
41 #include <pcl/point_types.h>
42 #include <pcl/filters/passthrough.h>
43 #include <pcl/segmentation/extract_clusters.h>
44 #include <pcl/filters/extract_indices.h>
45 #include <pcl/kdtree/kdtree_flann.h>
46 #include <pcl/sample_consensus/method_types.h>
47 #include <pcl/sample_consensus/model_types.h>
48 #include <pcl/segmentation/sac_segmentation.h>
49 #include <pcl/segmentation/extract_clusters.h>
50 
51 #include <pcl_ros/point_cloud.h>
52 #include <pcl_ros/transforms.h>
53 
54 // MoveIt!
56 
57 #include <cmath>
58 #include <algorithm>
59 
61 {
62 
64 {
65 private:
66 
69  std::string action_name_;
70  turtlebot_arm_block_manipulation::BlockDetectionFeedback feedback_;
71  turtlebot_arm_block_manipulation::BlockDetectionResult result_;
72  turtlebot_arm_block_manipulation::BlockDetectionGoalConstPtr goal_;
75 
76  // We use the planning_scene_interface::PlanningSceneInterface to manipulate the world
78 
80 
81  // Parameters from goal
82  std::string arm_link_;
83  double block_size_;
84  double table_height_;
85 
88 
89  // Parameters from node
90  std::vector<double> table_pose_;
91 
92  // Constants
93  const double TABLE_SIZE_X = 0.5;
94  const double TABLE_SIZE_Y = 1.0;
95  const double TABLE_SIZE_Z = 0.05;
96 
97 
98 public:
99  BlockDetectionServer(const std::string name) :
100  nh_("~"), as_(name, false), action_name_(name)
101  {
102  // Load parameters from the server.
103  if ((nh_.getParam("table_pose", table_pose_) == true) && (table_pose_.size() != 3))
104  {
105  ROS_ERROR("Invalid table_pose vector size; must contain 3 values (x, y, z); ignoring");
106  table_pose_.clear();
107  }
108 
109  // Register the goal and feeback callbacks.
110  as_.registerGoalCallback(boost::bind(&BlockDetectionServer::goalCB, this));
112 
113  as_.start();
114 
115  // Subscribe to point cloud
116  sub_ = nh_.subscribe("/camera/depth_registered/points", 1, &BlockDetectionServer::cloudCb, this);
117 
118  // Publish the filtered point cloud for debug purposes
119  pub_ = nh_.advertise< pcl::PointCloud<pcl::PointXYZRGB> >("block_output", 1);
120 
121  // Public detected blocks poses
122  block_pub_ = nh_.advertise<geometry_msgs::PoseArray>("/turtlebot_blocks", 1, true);
123  }
124 
125  void goalCB()
126  {
127  ROS_INFO("[block detection] Received goal!");
128  // accept the new goal
129  result_.blocks.poses.clear();
130 
131  goal_ = as_.acceptNewGoal();
132 
133  block_size_ = goal_->block_size;
134  table_height_ = goal_->table_height;
135  arm_link_ = goal_->frame;
136 
137  result_.blocks.header.frame_id = arm_link_;
138 
139  // Add the table as a collision object, so it gets filtered out from MoveIt! octomap
140  // I let it optional, as I don't know if this will work in most cases, honesty speaking
141  if (table_pose_.size() == 3)
142  {
143  if (std::abs(table_height_ - table_pose_[2]) > 0.05)
144  ROS_WARN("The table height (%f) passed with goal and table_pose[2] parameter (%f) " \
145  "should be very similar", table_height_, table_pose_[2]);
146  addTable();
147  }
148  }
149 
150  void preemptCB()
151  {
152  ROS_INFO("%s: Preempted", action_name_.c_str());
153  // set the action state to preempted
154  as_.setPreempted();
155  }
156 
157  void cloudCb(const sensor_msgs::PointCloud2ConstPtr& msg)
158  {
159  // Only do this if we're actually actively working on a goal.
160  if (!as_.isActive())
161  return;
162 
163  result_.blocks.header.stamp = msg->header.stamp;
164 
165  // convert to PCL
166  pcl::PointCloud < pcl::PointXYZRGB > cloud;
167  pcl::fromROSMsg(*msg, cloud);
168 
169  // transform to whatever frame we're working in, probably the arm frame.
170  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_transformed(new pcl::PointCloud<pcl::PointXYZRGB>);
171 
172  tf_listener_.waitForTransform(std::string(arm_link_), cloud.header.frame_id,
173  ros::Time(cloud.header.stamp), ros::Duration(1.0));
174  if (!pcl_ros::transformPointCloud(std::string(arm_link_), cloud, *cloud_transformed, tf_listener_))
175  {
176  ROS_ERROR("Error converting to desired frame");
177  return;
178  }
179 
180  // Create the segmentation object for the planar model and set all the parameters
181  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZRGB>);
182  pcl::SACSegmentation<pcl::PointXYZRGB> seg;
183  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
184  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
185  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_plane(new pcl::PointCloud<pcl::PointXYZRGB>());
186  seg.setOptimizeCoefficients(true);
187  seg.setModelType(pcl::SACMODEL_PLANE);
188  seg.setMethodType(pcl::SAC_RANSAC);
189  seg.setMaxIterations(200);
190  seg.setDistanceThreshold(0.005);
191 
192  // Limit to things we think are roughly at the table height.
193  pcl::PassThrough<pcl::PointXYZRGB> pass;
194  pass.setInputCloud(cloud_transformed);
195  pass.setFilterFieldName("z");
196 
197  pass.setFilterLimits(table_height_ - 0.05, table_height_ + block_size_ + 0.05);
198  pass.filter(*cloud_filtered);
199  if (cloud_filtered->points.size() == 0)
200  {
201  ROS_ERROR("0 points left");
202  return;
203  }
204  else
205  ROS_INFO("Filtered, %d points left", (int ) cloud_filtered->points.size());
206 
207  int nr_points = cloud_filtered->points.size ();
208  while (cloud_filtered->points.size() > 0.3 * nr_points)
209  {
210  // Segment the largest planar component from the remaining cloud
211  seg.setInputCloud(cloud_filtered);
212  seg.segment(*inliers, *coefficients);
213  if (inliers->indices.size() == 0)
214  {
215  std::cout << "Could not estimate a planar model for the given dataset." << std::endl;
216  return;
217  }
218 
219  std::cout << "Inliers: " << (inliers->indices.size()) << std::endl;
220 
221  // Extract the planar inliers from the input cloud
222  pcl::ExtractIndices<pcl::PointXYZRGB> extract;
223  extract.setInputCloud(cloud_filtered);
224  extract.setIndices(inliers);
225  extract.setNegative(false);
226 
227  // Write the planar inliers to disk
228  extract.filter(*cloud_plane);
229  std::cout << "PointCloud representing the planar component: "
230  << cloud_plane->points.size() << " data points." << std::endl;
231 
232  // Remove the planar inliers, extract the rest
233  extract.setNegative(true);
234  extract.filter(*cloud_filtered);
235  }
236 
237  // Creating the KdTree object for the search method of the extraction
238  pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>);
239  tree->setInputCloud(cloud_filtered);
240 
241  std::vector<pcl::PointIndices> cluster_indices;
242  pcl::EuclideanClusterExtraction<pcl::PointXYZRGB> ec;
243  ec.setClusterTolerance(0.005);
244  ec.setMinClusterSize(100);
245  ec.setMaxClusterSize(25000);
246  ec.setSearchMethod(tree);
247  ec.setInputCloud(cloud_filtered);
248  ec.extract(cluster_indices);
249 
250  pub_.publish(cloud_filtered);
251 
252  // for each cluster, see if it is a block
253  for (size_t c = 0; c < cluster_indices.size (); ++c)
254  {
255  // find the outer dimensions of the cluster
256  float xmin = 0; float xmax = 0;
257  float ymin = 0; float ymax = 0;
258  float zmin = 0; float zmax = 0;
259 
260  for (size_t i = 0; i < cluster_indices[c].indices.size(); i++)
261  {
262  int j = cluster_indices[c].indices[i];
263  float x = cloud_filtered->points[j].x;
264  float y = cloud_filtered->points[j].y;
265  float z = cloud_filtered->points[j].z;
266  if (i == 0)
267  {
268  xmin = xmax = x;
269  ymin = ymax = y;
270  zmin = zmax = z;
271  }
272  else
273  {
274  xmin = std::min(xmin, x);
275  xmax = std::max(xmax, x);
276  ymin = std::min(ymin, y);
277  ymax = std::max(ymax, y);
278  zmin = std::min(zmin, z);
279  zmax = std::max(zmax, z);
280  }
281  }
282 
283  // Check if these dimensions make sense for the block size specified
284  float xside = xmax-xmin;
285  float yside = ymax-ymin;
286  float zside = zmax-zmin;
287 
288  const float tol = 0.01; // 1 cm error tolerance
289  // In order to be part of the block, xside and yside must be between
290  // blocksize and blocksize*sqrt(2)
291  // z must be equal to or smaller than blocksize
292  if (xside > block_size_ - tol && xside < block_size_*sqrt(2) + tol &&
293  yside > block_size_ - tol && yside < block_size_*sqrt(2) + tol &&
294  zside > tol && zside < block_size_ + tol)
295  {
296  float x = xmin + xside/2.0;
297  float y = ymin + yside/2.0;
298  float z = zmax - block_size_/2.0;
299 
300  // Discard blocks outside the table limits (normally something detected on the robot)
301  // Note that table pose x marks the table border closest to arm_link_ frame, not its center
302  if ((table_pose_.size() == 3) &&
303  ((x < table_pose_[0]) || (x > table_pose_[0] + TABLE_SIZE_X) ||
304  (y < table_pose_[1] - TABLE_SIZE_Y/2.0) || (y > table_pose_[1] + TABLE_SIZE_Y/2.0)))
305  {
306  ROS_DEBUG_STREAM("Block at [" << x << ", " << y << "] outside table limits ["
307  << table_pose_[0] - TABLE_SIZE_X/2.0 << ", " << table_pose_[0] + TABLE_SIZE_X/2.0 << ", "
308  << table_pose_[1] - TABLE_SIZE_Y/2.0 << ", " << table_pose_[1] + TABLE_SIZE_Y/2.0);
309  continue;
310  }
311 
312  // If so, then figure out the position and the orientation of the block
313  float angle = atan(block_size_/((xside + yside)/2));
314 
315  if (yside < block_size_)
316  angle = 0.0;
317 
318  ROS_INFO_STREAM("xside: " << xside << " yside: " << yside << " zside " << zside << " angle: " << angle);
319  // Then add it to our set
320  ROS_INFO_STREAM("Adding a new block at [" << x << ", " << y << ", " << z << ", " << angle << "]");
321  addBlock(x, y, z, angle);
322  }
323  }
324 
325  if (result_.blocks.poses.size() > 0)
326  {
327  as_.setSucceeded(result_);
328  block_pub_.publish(result_.blocks);
329  ROS_INFO("[block detection] Set as succeeded!");
330  }
331  else
332  ROS_INFO("[block detection] Couldn't find any blocks this iteration!");
333  // as_.setAborted(result_);
334  }
335 
336 
337 private:
338 
339  void addBlock(float x, float y, float z, float angle)
340  {
341  geometry_msgs::Pose block_pose;
342  block_pose.position.x = x;
343  block_pose.position.y = y;
344  block_pose.position.z = z;
345 
346  Eigen::Quaternionf quat(Eigen::AngleAxis<float>(angle, Eigen::Vector3f(0,0,1)));
347 
348  block_pose.orientation.x = quat.x();
349  block_pose.orientation.y = quat.y();
350  block_pose.orientation.z = quat.z();
351  block_pose.orientation.w = quat.w();
352 
353  result_.blocks.poses.push_back(block_pose);
354  }
355 
356  void addTable()
357  {
358  // Add the table as a collision object into the world, so it gets excluded from the collision map
359  // As the blocks are small, they should also be excluded (assuming that padding_offset parameter on
360  // octomap sensor configuration is equal or bigger than block size)
361  moveit_msgs::CollisionObject co;
362  co.header.stamp = ros::Time::now();
363  co.header.frame_id = arm_link_;
364 
365  co.id = "table";
366  planning_scene_interface_.removeCollisionObjects(std::vector<std::string>(1, co.id));
367 
368  co.operation = moveit_msgs::CollisionObject::ADD;
369  co.primitives.resize(1);
370  co.primitives[0].type = shape_msgs::SolidPrimitive::BOX;
371  co.primitives[0].dimensions.resize(3);
372  co.primitives[0].dimensions[shape_msgs::SolidPrimitive::BOX_X] = TABLE_SIZE_X;
373  co.primitives[0].dimensions[shape_msgs::SolidPrimitive::BOX_Y] = TABLE_SIZE_Y;
374  co.primitives[0].dimensions[shape_msgs::SolidPrimitive::BOX_Z] = TABLE_SIZE_Z;
375  co.primitive_poses.resize(1);
376  co.primitive_poses[0].position.x = table_pose_[0] + TABLE_SIZE_X/2.0;
377  co.primitive_poses[0].position.y = table_pose_[1];
378  co.primitive_poses[0].position.z = table_pose_[2] - TABLE_SIZE_Z/2.0;
379 
380  ROS_INFO("Add the table as a collision object into the world");
381  std::vector<moveit_msgs::CollisionObject> collision_objects(1, co);
382  planning_scene_interface_.addCollisionObjects(collision_objects);
383  }
384 
385 };
386 
387 };
388 
389 int main(int argc, char** argv)
390 {
391  ros::init(argc, argv, "block_detection_action_server");
392 
394  ros::spin();
395 
396  return 0;
397 }
turtlebot_arm_block_manipulation::BlockDetectionGoalConstPtr goal_
boost::shared_ptr< const Goal > acceptNewGoal()
void publish(const boost::shared_ptr< M > &message) const
turtlebot_arm_block_manipulation::BlockDetectionFeedback feedback_
moveit::planning_interface::PlanningSceneInterface planning_scene_interface_
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
boost::shared_ptr< interactive_markers::InteractiveMarkerServer > server
actionlib::SimpleActionServer< turtlebot_arm_block_manipulation::BlockDetectionAction > as_
void fromROSMsg(const sensor_msgs::PointCloud2 &cloud, pcl::PointCloud< T > &pcl_cloud)
void addCollisionObjects(const std::vector< moveit_msgs::CollisionObject > &collision_objects, const std::vector< moveit_msgs::ObjectColor > &object_colors=std::vector< moveit_msgs::ObjectColor >()) const
#define ROS_WARN(...)
void setSucceeded(const Result &result=Result(), const std::string &text=std::string(""))
double y
ROSCPP_DECL void spin(Spinner &spinner)
bool waitForTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration &timeout, const ros::Duration &polling_sleep_duration=ros::Duration(0.01), std::string *error_msg=NULL) const
turtlebot_arm_block_manipulation::BlockDetectionResult result_
void setPreempted(const Result &result=Result(), const std::string &text=std::string(""))
#define ROS_INFO(...)
int main(int argc, char **argv)
void registerPreemptCallback(boost::function< void()> cb)
double z
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
quat
#define ROS_DEBUG_STREAM(args)
#define ROS_INFO_STREAM(args)
void transformPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const tf::Transform &transform)
bool getParam(const std::string &key, std::string &s) const
void removeCollisionObjects(const std::vector< std::string > &object_ids) const
static Time now()
void registerGoalCallback(boost::function< void()> cb)
double x
#define ROS_ERROR(...)
void cloudCb(const sensor_msgs::PointCloud2ConstPtr &msg)


turtlebot_arm_block_manipulation
Author(s): Michael Ferguson, Helen Oleynikova
autogenerated on Fri Feb 7 2020 03:56:21