$search
00001 /********************************************************************* 00002 * 00003 * Software License Agreement (BSD License) 00004 * 00005 * Copyright (c) 2010, Robert Bosch LLC. 00006 * All rights reserved. 00007 * 00008 * Redistribution and use in source and binary forms, with or without 00009 * modification, are permitted provided that the following conditions 00010 * are met: 00011 * 00012 * * Redistributions of source code must retain the above copyright 00013 * notice, this list of conditions and the following disclaimer. 00014 * * Redistributions in binary form must reproduce the above 00015 * copyright notice, this list of conditions and the following 00016 * disclaimer in the documentation and/or other materials provided 00017 * with the distribution. 00018 * * Neither the name of the Robert Bosch nor the names of its 00019 * contributors may be used to endorse or promote products derived 00020 * from this software without specific prior written permission. 00021 * 00022 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00023 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00024 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00025 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00026 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00027 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00028 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00029 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00030 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00031 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00032 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00033 * POSSIBILITY OF SUCH DAMAGE. 00034 * 00035 *********************************************************************/ 00036 00037 #include <iostream> 00038 #include <opencv2/highgui/highgui.hpp> 00039 #include <opencv2/core/core.hpp> 00040 #include <cv_bridge/CvBridge.h> 00041 #include <sensor_msgs/Image.h> 00042 #include <image_transport/image_transport.h> 00043 #include <sensor_msgs/fill_image.h> 00044 #include <sensor_msgs/PointCloud2.h> 00045 #include <sensor_msgs/point_cloud_conversion.h> 00046 #include <visualization_msgs/Marker.h> 00047 #include <image_geometry/pinhole_camera_model.h> 00048 00049 #include <pcl/ros/conversions.h> 00050 #include <pcl/point_types.h> 00051 #include <pcl_ros/transforms.h> 00052 #include <pcl/point_cloud.h> 00053 #include <pcl/point_types.h> 00054 #include <pcl/io/io.h> 00055 #include <pcl/filters/voxel_grid.h> 00056 #include <pcl/filters/passthrough.h> 00057 #include <pcl/filters/extract_indices.h> 00058 #include <pcl/features/normal_3d.h> 00059 #include <pcl/kdtree/kdtree_flann.h> 00060 #include <pcl/sample_consensus/method_types.h> 00061 #include <pcl/sample_consensus/model_types.h> 00062 #include <pcl/segmentation/sac_segmentation.h> 00063 #include <pcl/filters/project_inliers.h> 00064 #include <pcl/surface/convex_hull.h> 00065 #include <pcl/segmentation/extract_polygonal_prism_data.h> 00066 #include <pcl/segmentation/extract_clusters.h> 00067 00068 #include <tf/tf.h> 00069 #include <tf/transform_datatypes.h> 00070 #include <tf/transform_listener.h> 00071 #include <tf/transform_broadcaster.h> 00072 00073 #include <tabletop_object_detector/marker_generator.h> 00074 #include <tabletop_object_detector/TabletopSegmentation.h> 00075 00076 #include "augmented_object_selection/GC3DApplication.hpp" 00077 00078 using namespace std; 00079 using namespace cv; 00080 using namespace tabletop_object_detector; 00081 00082 class GrabCutNode 00083 { 00084 typedef pcl::PointXYZ Point; 00085 typedef pcl::KdTree<Point>::Ptr KdTreePtr; 00086 public: 00087 std::string node_name_; 00089 ros::NodeHandle nh_; 00091 ros::NodeHandle priv_nh_; 00093 tf::TransformListener listener_; 00094 // for database fitting 00095 ros::ServiceClient object_recognition_client; 00096 image_transport::CameraPublisher mask_pub; 00098 ros::ServiceServer object_selection_srv_; 00099 // converts OpenCV to ROS images 00100 sensor_msgs::CvBridge img_bridge_; 00101 // converts OpenCV to ROS images 00102 sensor_msgs::CvBridge depth_img_bridge_; 00103 // image transport 00104 image_transport::ImageTransport it; 00105 // Create image and info message objects for segmentation mask topic 00106 sensor_msgs::Image mask_img_msg; 00108 ros::Publisher marker_pub_; 00110 int num_markers_published_; 00112 int current_marker_id_; 00114 image_transport::Subscriber image_sub_; 00115 00116 //std::string detection_frame; 00117 00119 int inlier_threshold_; 00121 double plane_detection_voxel_size_; 00123 double clustering_voxel_size_; 00125 double z_filter_min_, z_filter_max_; 00127 double table_z_filter_min_, table_z_filter_max_; 00129 double cluster_distance_; 00131 int min_cluster_size_; 00134 std::string processing_frame_; 00136 double up_direction_; 00137 00138 00139 GrabCutNode(const std::string& node_name,ros::NodeHandle& nh) 00140 : node_name_(node_name), nh_(nh), priv_nh_("~"), listener_(ros::Duration(120.0)), it(nh) 00141 { 00142 ROS_INFO("grabcut_node: starting"); 00143 00144 // marker 00145 num_markers_published_ = 1; 00146 current_marker_id_ = 1; 00147 marker_pub_ = nh.advertise<visualization_msgs::Marker>(nh.resolveName("markers_out"), 10); 00148 00149 //initialize operational flags 00150 priv_nh_.param<int>("inlier_threshold", inlier_threshold_, 300); 00151 priv_nh_.param<double>("plane_detection_voxel_size", plane_detection_voxel_size_, 0.01); 00152 priv_nh_.param<double>("clustering_voxel_size", clustering_voxel_size_, 0.003); 00153 priv_nh_.param<double>("z_filter_min", z_filter_min_, 0.4); 00154 priv_nh_.param<double>("z_filter_max", z_filter_max_, 1.25); 00155 priv_nh_.param<double>("table_z_filter_min", table_z_filter_min_, 0.01); 00156 priv_nh_.param<double>("table_z_filter_max", table_z_filter_max_, 0.50); 00157 priv_nh_.param<double>("cluster_distance", cluster_distance_, 0.03); 00158 priv_nh_.param<int>("min_cluster_size", min_cluster_size_, 300); 00159 priv_nh_.param<std::string>("processing_frame", processing_frame_, ""); 00160 priv_nh_.param<double>("up_direction", up_direction_, -1.0); 00161 00162 // Create image publisher for segmentation mask 00163 mask_pub = it.advertiseCamera( nh.getNamespace() + "/" + node_name_ + "/mask_image/image_raw", 3 ); 00164 00165 // object selection service 00166 ROS_INFO("grabcut_node: advertising object_detection service"); 00167 object_selection_srv_ = nh_.advertiseService(nh_.resolveName("object_selection_srv"), &GrabCutNode::serviceCallback, this); 00168 00169 // fake subscriber for kinect data 00170 std::string image_topic = nh_.resolveName("image_in"); 00171 image_sub_ = it.subscribe(image_topic, 1 , &GrabCutNode::imageCallback, this); 00172 } 00173 00175 ~GrabCutNode() {} 00176 00177 void imageCallback(const sensor_msgs::ImageConstPtr& msg) 00178 { 00179 } 00180 00181 /* Scales camera extrinsics by a factor to allow for conversion of camera 00182 * parameters between pyramid levels. */ 00183 void scaleCameraInfo(sensor_msgs::CameraInfo& info, float scale) 00184 { 00185 info.height = info.height / scale; 00186 info.width = info.width / scale; 00187 info.roi.height = info.roi.height / scale; 00188 info.roi.width = info.roi.width / scale; 00189 info.K = info.K; 00190 for (int i=0; i<8; i++) info.K[i] /= scale; 00191 info.P = info.P; 00192 for (int i=0; i<10; i++) info.P[i] /= scale; 00193 } 00194 00195 /* Publishes image and camera info */ 00196 void 00197 publishImage( 00198 const image_transport::CameraPublisher& pub, 00199 sensor_msgs::CameraInfo& info_msg, 00200 sensor_msgs::Image& img_msg, 00201 const cv::Mat& image, 00202 const std::string& encoding) 00203 { 00204 fillImage(img_msg, encoding, image.rows, image.cols, image.step, const_cast<uint8_t*>(image.data)); 00205 printf("Image frame id: %s \n",img_msg.header.frame_id.c_str()); 00206 pub.publish(img_msg, info_msg); 00207 } 00208 00209 /* Filter received point cloud based on binary mask */ 00210 void 00211 filterPointCloud(const sensor_msgs::CameraInfo& info_msg, 00212 cv::Mat& mask, 00213 pcl::PointCloud<pcl::PointXYZRGB>& cloud) 00214 { 00215 image_geometry::PinholeCameraModel model_; 00216 model_.fromCameraInfo(info_msg); 00217 00218 ROS_INFO("grab_view: cloud size before filtering is %d", int(cloud.points.size())); 00219 pcl::PointCloud<pcl::PointXYZRGB>::VectorType::iterator iter; 00220 pcl::PointCloud<pcl::PointXYZRGB>::VectorType new_points; 00221 new_points.reserve(cloud.points.size()); 00222 int point_id = 0; 00223 for (iter = cloud.points.begin(); iter != cloud.points.end(); ++iter) 00224 { 00225 point_id++; 00226 pcl::PointXYZRGB point = *iter; 00227 if (isnan(point.x) || isnan(point.y) || isnan(point.z)) 00228 continue; 00229 cv::Point3d p3d(point.x, point.y, point.z); 00230 cv::Point2d p2d = model_.project3dToPixel(p3d); 00231 int x = round(p2d.x); 00232 int y = round(p2d.y); 00233 int mask_val = int(mask.at<unsigned char>(y, x)); 00234 if (mask_val > 0) 00235 new_points.push_back(point); 00236 //mask.at<unsigned char>(y, x) = 5; 00237 } 00238 ROS_INFO("grab_view: cloud size after filtering is %d", int(new_points.size())); 00239 00240 // Filter point cloud based on scaled camera info and mask 00241 00242 // static const char WINDOW[] = "processPointCloud Mask"; 00243 // cv::namedWindow(WINDOW); 00244 // cv::imshow(WINDOW, mask*50); 00245 // cv::waitKey(); 00246 00247 00248 cloud.width = 0; 00249 cloud.height = 0; 00250 cloud.is_dense = 0; 00251 cloud.points = new_points; 00252 00253 return; 00254 } 00255 00256 00257 bool getPointCloud(sensor_msgs::PointCloud2& cloud_msg, pcl::PointCloud<pcl::PointXYZRGB>& cloud) 00258 { 00259 // get camera info 00260 std::string topic = nh_.resolveName("cloud_in"); 00261 ROS_INFO("Waiting for a point_cloud2 on topic %s", topic.c_str()); 00262 sensor_msgs::PointCloud2::ConstPtr cloud_msg_ptr = ros::topic::waitForMessage<sensor_msgs::PointCloud2>(topic, nh_, ros::Duration(10.0)); 00263 if(!cloud_msg_ptr) { 00264 ROS_ERROR("Could not receive a point cloud!"); 00265 return false; 00266 } 00267 else { 00268 cloud_msg = *cloud_msg_ptr; 00269 pcl::fromROSMsg<pcl::PointXYZRGB>(cloud_msg, cloud); 00270 ROS_INFO("Got point cloud!"); 00271 } 00272 return true; 00273 } 00274 00275 bool getCameraInfo(sensor_msgs::CameraInfo& camera_info_msg) 00276 { 00277 // get camera info 00278 std::string camera_info_topic = nh_.resolveName("camera_info_in"); 00279 ROS_INFO("Waiting for camera_info on topic %s", camera_info_topic.c_str()); 00280 sensor_msgs::CameraInfoConstPtr camera_info_ptr = ros::topic::waitForMessage<sensor_msgs::CameraInfo>(camera_info_topic, nh_, ros::Duration(10.0)); 00281 if(!camera_info_ptr) { 00282 ROS_ERROR("Could not receive a camera info!"); 00283 return false; 00284 } 00285 else { 00286 camera_info_msg = *camera_info_ptr; 00287 } 00288 return true; 00289 } 00290 00291 bool getImage(sensor_msgs::Image& image_msg, Mat& image) 00292 { 00293 // get image 00294 std::string image_topic = nh_.resolveName("image_in"); 00295 ROS_INFO("Waiting for image on topic %s", image_topic.c_str()); 00296 sensor_msgs::ImageConstPtr image_ptr = ros::topic::waitForMessage<sensor_msgs::Image>(image_topic, nh_, ros::Duration(10.0)); 00297 if(!image_ptr) { 00298 ROS_ERROR("Could not receive an image!"); 00299 return false; 00300 } 00301 image_msg = *image_ptr; 00302 IplImage* ipl_image; 00303 try { 00304 ipl_image = img_bridge_.imgMsgToCv(image_ptr, "bgr8"); 00305 } 00306 catch (sensor_msgs::CvBridgeException e) { 00307 ROS_ERROR("%s: Unable to convert %s image to bgr8", 00308 node_name_.c_str(), 00309 image_ptr->encoding.c_str()); 00310 return false; 00311 } 00312 image = ipl_image; 00313 00314 return true; 00315 } 00316 00317 bool getDepthImage(sensor_msgs::Image& image_msg, Mat& image) 00318 { 00319 // get image 00320 std::string image_topic = nh_.resolveName("depth_image_in"); 00321 ROS_INFO("Waiting for image on topic %s", image_topic.c_str()); 00322 sensor_msgs::ImageConstPtr image_ptr = ros::topic::waitForMessage<sensor_msgs::Image>(image_topic, nh_, ros::Duration(10.0)); 00323 if(!image_ptr) { 00324 ROS_ERROR("Could not receive an image!"); 00325 return false; 00326 } 00327 image_msg = *image_ptr; 00328 IplImage* ipl_image; 00329 try { 00330 ipl_image = depth_img_bridge_.imgMsgToCv(image_ptr, "bgr8"); 00331 } 00332 catch (sensor_msgs::CvBridgeException e) { 00333 ROS_ERROR("%s: Unable to convert %s image", 00334 node_name_.c_str(), 00335 image_ptr->encoding.c_str()); 00336 return false; 00337 } 00338 image = ipl_image; 00339 00340 return true; 00341 } 00342 00343 bool serviceCallback(TabletopSegmentation::Request &request, TabletopSegmentation::Response &response) { 00344 00345 // get point cloud 00346 sensor_msgs::PointCloud2 cloud_msg; 00347 pcl::PointCloud<pcl::PointXYZRGB> cloud; 00348 getPointCloud(cloud_msg,cloud); 00349 00350 // get image 00351 sensor_msgs::Image image_msg; 00352 Mat image; 00353 getImage(image_msg, image); 00354 00355 // get depth image 00356 sensor_msgs::Image depth_image_msg; 00357 Mat depth_image; 00358 getDepthImage(depth_image_msg, depth_image); 00359 00360 // get camera info 00361 sensor_msgs::CameraInfo camera_info_msg; 00362 getCameraInfo(camera_info_msg); 00363 00364 // Initialize GC3DApplication object 00365 GC3DApplication gcapp("grab cut 3D", image, depth_image); 00366 00367 // Wait for instructions 00368 while (ros::ok()) 00369 { 00370 int c = cvWaitKey(0); 00371 switch( (char) c ) 00372 { 00373 // r: reset segmentation 00374 case 'r': 00375 cout << endl; 00376 gcapp.initializedIs(false); 00377 break; 00378 00379 // n: run next iteration 00380 case 'n': 00381 { 00382 cout << "<" << gcapp.iterCount() << "... "; 00383 if( gcapp.rectState() == GC3DApplication::SET) 00384 { 00385 gcapp.iterCountInc(); 00386 cout << gcapp.iterCount() << ">" << endl; 00387 } 00388 else 00389 cout << "rect must be determined>" << endl; 00390 break; 00391 } 00392 00393 // d: set default rectangle 00394 case 'd': 00395 gcapp.rectIs(GC3DApplication::DEFAULT_RECT); 00396 break; 00397 00398 // u: get new image from camera 00399 case 'u': 00400 // get point cloud 00401 getPointCloud(cloud_msg,cloud); 00402 // get image 00403 getImage(image_msg, image); 00404 // get camera info 00405 getCameraInfo(camera_info_msg); 00406 break; 00407 00408 // w, k, y, g, b: change background color 00409 case 'w': 00410 cout << "Setting background color to white" << endl; 00411 gcapp.winColorIs(GC3DApplication::WHITE); 00412 break; 00413 case 'k': 00414 cout << "Setting background color to black" << endl; 00415 gcapp.winColorIs(GC3DApplication::BLACK); 00416 break; 00417 case 'y': 00418 cout << "Setting background color to gray" << endl; 00419 gcapp.winColorIs(GC3DApplication::GRAY); 00420 break; 00421 case 'g': 00422 cout << "Setting background color to green" << endl; 00423 gcapp.winColorIs(GC3DApplication::GREEN); 00424 break; 00425 case 'b': 00426 cout << "Setting background color to blue" << endl; 00427 gcapp.winColorIs(GC3DApplication::BLUE); 00428 break; 00429 00430 // 0, 1, 2, 3, 4, 5: change pyramid level 00431 case '0': 00432 case '1': 00433 case '2': 00434 case '3': 00435 case '4': 00436 case '5': 00437 { 00438 // int plevel = (int)((char)c - '0'); 00439 // pcam.pyrLevelIs(plevel); 00440 // cout << "Changing pyramid level to " << plevel << endl; 00441 // gcapp.imageIs(pcam.image()); 00442 break; 00443 } 00444 00445 // p: publish segmentation mask on outgoing topic 00446 case 'p': 00447 { 00448 // Fetch binary mask from GCApp object 00449 cv::Mat binary_mask = gcapp.binaryMask(); 00450 00451 // process point cloud 00452 pcl::PointCloud<pcl::PointXYZRGB> cloud, detection_cloud; 00453 pcl::fromROSMsg<pcl::PointXYZRGB>(cloud_msg, cloud); 00454 00455 // transform cloud to detection frame 00456 ROS_INFO("Transforming point cloud to %s frame", processing_frame_.c_str()); 00457 transformPointCloud( processing_frame_, cloud, detection_cloud); 00458 00459 // convert pointcloud to message 00460 sensor_msgs::PointCloud2 detection_cloud_msg; 00461 pcl::toROSMsg<pcl::PointXYZRGB>(detection_cloud, detection_cloud_msg); 00462 00463 // detect table 00464 if(!detectTable(detection_cloud_msg, response)) { 00465 ROS_ERROR("Couldn't find table."); 00466 return false; 00467 } 00468 00469 processPointCloud(cloud_msg, image_msg, camera_info_msg, binary_mask, response); 00470 00471 cout << "Publishing mask image" << endl; 00472 // Get image header from incoming image message 00473 mask_img_msg.header.stamp = image_msg.header.stamp; 00474 mask_img_msg.header.frame_id = image_msg.header.frame_id; 00475 // NB: publishing mask*255 so that it can be viewed as a monochrome 00476 // image to check the mask. 00477 publishImage(mask_pub, camera_info_msg, mask_img_msg, binary_mask*255, sensor_msgs::image_encodings::TYPE_8UC1); 00478 return true; 00479 break; 00480 } 00481 } 00482 00483 } 00484 00485 return true; 00486 } 00487 00488 bool transformPointCloud(const std::string &target_frame, const pcl::PointCloud<pcl::PointXYZRGB>& cloud_in, pcl::PointCloud<pcl::PointXYZRGB>& cloud_out) 00489 { 00490 //convert cloud to processing frame 00491 string err_msg; 00492 try 00493 { 00494 pcl_ros::transformPointCloud(target_frame, cloud_in, cloud_out, listener_); 00495 } 00496 catch (tf::TransformException ex) 00497 { 00498 ROS_ERROR("Failed to transform cloud from frame %s into frame %s: %s", cloud_in.header.frame_id.c_str(),target_frame.c_str(),ex.what()); 00499 return false; 00500 } 00501 ROS_INFO("Input cloud converted to %s frame", target_frame.c_str()); 00502 return true; 00503 } 00504 00505 bool transformPointCloud(const std::string &target_frame, const sensor_msgs::PointCloud2& cloud_in, sensor_msgs::PointCloud2& cloud_out) 00506 { 00507 //convert cloud to processing frame 00508 sensor_msgs::PointCloud old_cloud,old_cloud_transformed; 00509 sensor_msgs::convertPointCloud2ToPointCloud (cloud_in, old_cloud); 00510 string err_msg; 00511 if (listener_.canTransform(target_frame.c_str(),cloud_in.header.frame_id.c_str(), ros::Time(0), &err_msg)) 00512 { 00513 try 00514 { 00515 listener_.transformPointCloud(target_frame, old_cloud, old_cloud_transformed); 00516 } 00517 catch (tf::TransformException ex) 00518 { 00519 ROS_ERROR("Failed to transform cloud from frame %s into frame %s: %s", old_cloud.header.frame_id.c_str(),target_frame.c_str(),ex.what()); 00520 return false; 00521 } 00522 } 00523 else 00524 { 00525 ROS_ERROR("Could not transform %s to %s: %s", cloud_in.header.frame_id.c_str(), target_frame.c_str(), err_msg.c_str()); 00526 return false; 00527 } 00528 sensor_msgs::convertPointCloudToPointCloud2 (old_cloud_transformed, cloud_out); 00529 ROS_INFO("Input cloud converted to %s frame", target_frame.c_str()); 00530 return true; 00531 } 00532 00534 tf::Transform getPlaneTransform (pcl::ModelCoefficients coeffs, double up_direction) 00535 { 00536 ROS_ASSERT(coeffs.values.size() > 3); 00537 double a = coeffs.values[0], b = coeffs.values[1], c = coeffs.values[2], d = coeffs.values[3]; 00538 //asume plane coefficients are normalized 00539 btVector3 position(-a*d, -b*d, -c*d); 00540 btVector3 z(a, b, c); 00541 //make sure z points "up" 00542 ROS_DEBUG("z.dot: %0.3f", z.dot(btVector3(0,0,1))); 00543 ROS_DEBUG("in getPlaneTransform, z: %0.3f, %0.3f, %0.3f", z[0], z[1], z[2]); 00544 if ( z.dot( btVector3(0, 0, up_direction) ) < 0) 00545 { 00546 z = -1.0 * z; 00547 ROS_INFO("flipped z"); 00548 } 00549 ROS_DEBUG("in getPlaneTransform, z: %0.3f, %0.3f, %0.3f", z[0], z[1], z[2]); 00550 00551 //try to align the x axis with the x axis of the original frame 00552 //or the y axis if z and x are too close too each other 00553 btVector3 x(1, 0, 0); 00554 if ( fabs(z.dot(x)) > 1.0 - 1.0e-4) x = btVector3(0, 1, 0); 00555 btVector3 y = z.cross(x).normalized(); 00556 x = y.cross(z).normalized(); 00557 00558 btMatrix3x3 rotation; 00559 rotation[0] = x; // x 00560 rotation[1] = y; // y 00561 rotation[2] = z; // z 00562 rotation = rotation.transpose(); 00563 btQuaternion orientation; 00564 rotation.getRotation(orientation); 00565 return tf::Transform(orientation, position); 00566 } 00567 00568 template <typename PointT> 00569 bool getPlanePoints (const pcl::PointCloud<PointT> &table, 00570 const tf::Transform& table_plane_trans, 00571 sensor_msgs::PointCloud &table_points) 00572 { 00573 // Prepare the output 00574 table_points.header = table.header; 00575 table_points.points.resize (table.points.size ()); 00576 for (size_t i = 0; i < table.points.size (); ++i) 00577 { 00578 table_points.points[i].x = table.points[i].x; 00579 table_points.points[i].y = table.points[i].y; 00580 table_points.points[i].z = table.points[i].z; 00581 } 00582 00583 // Transform the data 00584 tf::TransformListener listener; 00585 tf::StampedTransform table_pose_frame(table_plane_trans, table.header.stamp, 00586 table.header.frame_id, "table_frame"); 00587 listener.setTransform(table_pose_frame); 00588 std::string error_msg; 00589 if (!listener.canTransform("table_frame", table_points.header.frame_id, table_points.header.stamp, &error_msg)) 00590 { 00591 ROS_ERROR("Can not transform point cloud from frame %s to table frame; error %s", 00592 table_points.header.frame_id.c_str(), error_msg.c_str()); 00593 return false; 00594 } 00595 try 00596 { 00597 listener.transformPointCloud("table_frame", table_points, table_points); 00598 } 00599 catch (tf::TransformException ex) 00600 { 00601 ROS_ERROR("Failed to transform point cloud from frame %s into table_frame; error %s", 00602 table_points.header.frame_id.c_str(), ex.what()); 00603 return false; 00604 } 00605 table_points.header.stamp = table.header.stamp; 00606 table_points.header.frame_id = "table_frame"; 00607 return true; 00608 } 00609 00610 template <class PointCloudType> 00611 Table getTable(std_msgs::Header cloud_header, 00612 const tf::Transform &table_plane_trans, 00613 const PointCloudType &table_points) 00614 { 00615 Table table; 00616 00617 //get the extents of the table 00618 if (!table_points.points.empty()) 00619 { 00620 table.x_min = table_points.points[0].x; 00621 table.x_max = table_points.points[0].x; 00622 table.y_min = table_points.points[0].y; 00623 table.y_max = table_points.points[0].y; 00624 } 00625 for (size_t i=1; i<table_points.points.size(); ++i) 00626 { 00627 if (table_points.points[i].x<table.x_min && table_points.points[i].x>-3.0) table.x_min = table_points.points[i].x; 00628 if (table_points.points[i].x>table.x_max && table_points.points[i].x< 3.0) table.x_max = table_points.points[i].x; 00629 if (table_points.points[i].y<table.y_min && table_points.points[i].y>-3.0) table.y_min = table_points.points[i].y; 00630 if (table_points.points[i].y>table.y_max && table_points.points[i].y< 3.0) table.y_max = table_points.points[i].y; 00631 } 00632 00633 geometry_msgs::Pose table_pose; 00634 tf::poseTFToMsg(table_plane_trans, table_pose); 00635 table.pose.pose = table_pose; 00636 table.pose.header = cloud_header; 00637 00638 visualization_msgs::Marker tableMarker = MarkerGenerator::getTableMarker(table.x_min, table.x_max, 00639 table.y_min, table.y_max); 00640 tableMarker.header = cloud_header; 00641 tableMarker.pose = table_pose; 00642 tableMarker.ns = "tabletop_node"; 00643 tableMarker.id = current_marker_id_++; 00644 marker_pub_.publish(tableMarker); 00645 00646 return table; 00647 } 00648 00649 bool detectTable(const sensor_msgs::PointCloud2 &cloud, TabletopSegmentation::Response &response) 00650 { 00651 TabletopSegmentation::Response segmentation_response; 00652 00653 ROS_INFO("Starting process on new cloud"); 00654 ROS_INFO("In frame %s", cloud.header.frame_id.c_str()); 00655 00656 // PCL objects 00657 KdTreePtr normals_tree_, clusters_tree_; 00658 pcl::VoxelGrid<Point> grid_, grid_objects_; 00659 pcl::PassThrough<Point> pass_; 00660 pcl::NormalEstimation<Point, pcl::Normal> n3d_; 00661 pcl::SACSegmentationFromNormals<Point, pcl::Normal> seg_; 00662 pcl::ProjectInliers<Point> proj_; 00663 pcl::ConvexHull<Point> hull_; 00664 pcl::ExtractPolygonalPrismData<Point> prism_; 00665 pcl::EuclideanClusterExtraction<Point> pcl_cluster_; 00666 00667 // Filtering parameters 00668 grid_.setLeafSize (plane_detection_voxel_size_, plane_detection_voxel_size_, plane_detection_voxel_size_); 00669 grid_objects_.setLeafSize (clustering_voxel_size_, clustering_voxel_size_, clustering_voxel_size_); 00670 grid_.setFilterFieldName ("z"); 00671 pass_.setFilterFieldName ("z"); 00672 00673 pass_.setFilterLimits (z_filter_min_, z_filter_max_); 00674 grid_.setFilterLimits (z_filter_min_, z_filter_max_); 00675 grid_.setDownsampleAllData (false); 00676 grid_objects_.setDownsampleAllData (false); 00677 00678 normals_tree_ = boost::make_shared<pcl::KdTreeFLANN<Point> > (); 00679 clusters_tree_ = boost::make_shared<pcl::KdTreeFLANN<Point> > (); 00680 00681 // Normal estimation parameters 00682 n3d_.setKSearch (10); 00683 n3d_.setSearchMethod (normals_tree_); 00684 // Table model fitting parameters 00685 seg_.setDistanceThreshold (0.05); 00686 seg_.setMaxIterations (10000); 00687 seg_.setNormalDistanceWeight (0.1); 00688 seg_.setOptimizeCoefficients (true); 00689 seg_.setModelType (pcl::SACMODEL_NORMAL_PLANE); 00690 seg_.setMethodType (pcl::SAC_RANSAC); 00691 seg_.setProbability (0.99); 00692 00693 proj_.setModelType (pcl::SACMODEL_PLANE); 00694 00695 // Consider only objects in a given layer above the table 00696 prism_.setHeightLimits (table_z_filter_min_, table_z_filter_max_); 00697 00698 // Clustering parameters 00699 pcl_cluster_.setClusterTolerance (cluster_distance_); 00700 pcl_cluster_.setMinClusterSize (min_cluster_size_); 00701 pcl_cluster_.setSearchMethod (clusters_tree_); 00702 00703 // Step 1 : Filter, remove NaNs and downsample 00704 pcl::PointCloud<Point> cloud_t; 00705 pcl::fromROSMsg (cloud, cloud_t); 00706 pcl::PointCloud<Point>::ConstPtr cloud_ptr = boost::make_shared<const pcl::PointCloud<Point> > (cloud_t); 00707 00708 pcl::PointCloud<Point> cloud_filtered; 00709 pass_.setInputCloud (cloud_ptr); 00710 pass_.filter (cloud_filtered); 00711 pcl::PointCloud<Point>::ConstPtr cloud_filtered_ptr = 00712 boost::make_shared<const pcl::PointCloud<Point> > (cloud_filtered); 00713 ROS_INFO("Step 1 done"); 00714 if (cloud_filtered.points.size() < (unsigned int)min_cluster_size_) 00715 { 00716 ROS_INFO("Filtered cloud only has %d points", (int)cloud_filtered.points.size()); 00717 response.result = segmentation_response.NO_TABLE; 00718 return false; 00719 } 00720 00721 pcl::PointCloud<Point> cloud_downsampled; 00722 grid_.setInputCloud (cloud_filtered_ptr); 00723 grid_.filter (cloud_downsampled); 00724 pcl::PointCloud<Point>::ConstPtr cloud_downsampled_ptr = 00725 boost::make_shared<const pcl::PointCloud<Point> > (cloud_downsampled); 00726 if (cloud_downsampled.points.size() < (unsigned int)min_cluster_size_) 00727 { 00728 ROS_INFO("Downsampled cloud only has %d points", (int)cloud_downsampled.points.size()); 00729 response.result = segmentation_response.NO_TABLE; 00730 return false; 00731 } 00732 00733 // Step 2 : Estimate normals 00734 pcl::PointCloud<pcl::Normal> cloud_normals; 00735 n3d_.setInputCloud (cloud_downsampled_ptr); 00736 n3d_.compute (cloud_normals); 00737 pcl::PointCloud<pcl::Normal>::ConstPtr cloud_normals_ptr = 00738 boost::make_shared<const pcl::PointCloud<pcl::Normal> > (cloud_normals); 00739 ROS_INFO("Step 2 done"); 00740 00741 // Step 3 : Perform planar segmentation 00742 pcl::PointIndices table_inliers; pcl::ModelCoefficients table_coefficients; 00743 seg_.setInputCloud (cloud_downsampled_ptr); 00744 seg_.setInputNormals (cloud_normals_ptr); 00745 seg_.segment (table_inliers, table_coefficients); 00746 pcl::PointIndices::ConstPtr table_inliers_ptr = boost::make_shared<const pcl::PointIndices> (table_inliers); 00747 pcl::ModelCoefficients::ConstPtr table_coefficients_ptr = 00748 boost::make_shared<const pcl::ModelCoefficients> (table_coefficients); 00749 00750 if (table_coefficients.values.size () <=3) 00751 { 00752 ROS_INFO("Failed to detect table in scan"); 00753 response.result = segmentation_response.NO_TABLE; 00754 return false; 00755 } 00756 00757 if ( table_inliers.indices.size() < (unsigned int)inlier_threshold_) 00758 { 00759 ROS_INFO("Plane detection has %d inliers, below min threshold of %d", (int)table_inliers.indices.size(), 00760 inlier_threshold_); 00761 response.result = segmentation_response.NO_TABLE; 00762 return false; 00763 } 00764 00765 ROS_INFO ("[TableObjectDetector::input_callback] Model found with %d inliers: [%f %f %f %f].", 00766 (int)table_inliers.indices.size (), 00767 table_coefficients.values[0], table_coefficients.values[1], 00768 table_coefficients.values[2], table_coefficients.values[3]); 00769 ROS_INFO("Step 3 done"); 00770 00771 // Step 4 : Project the table inliers on the table 00772 pcl::PointCloud<Point> table_projected; 00773 proj_.setInputCloud (cloud_downsampled_ptr); 00774 proj_.setIndices (table_inliers_ptr); 00775 proj_.setModelCoefficients (table_coefficients_ptr); 00776 proj_.filter (table_projected); 00777 pcl::PointCloud<Point>::ConstPtr table_projected_ptr = 00778 boost::make_shared<const pcl::PointCloud<Point> > (table_projected); 00779 ROS_INFO("Step 4 done"); 00780 00781 sensor_msgs::PointCloud table_points; 00782 tf::Transform table_plane_trans = getPlaneTransform (table_coefficients, up_direction_); 00783 //takes the points projected on the table and transforms them into the PointCloud message 00784 //while also transforming them into the table's coordinate system 00785 if (!getPlanePoints<Point> (table_projected, table_plane_trans, table_points)) 00786 { 00787 response.result = segmentation_response.OTHER_ERROR; 00788 return false; 00789 } 00790 ROS_INFO("Table computed"); 00791 00792 response.table = getTable<sensor_msgs::PointCloud>(cloud.header, table_plane_trans, table_points); 00793 response.result = segmentation_response.SUCCESS; 00794 return true; 00795 } 00796 00797 bool processPointCloud(const sensor_msgs::PointCloud2& cloud_msg, const sensor_msgs::Image& image_msg, sensor_msgs::CameraInfo& camera_info_msg, cv::Mat& binary_mask, TabletopSegmentation::Response &response) 00798 { 00799 pcl::PointCloud<pcl::PointXYZRGB> cloud, converted_cloud, detection_cloud; 00800 pcl::fromROSMsg<pcl::PointXYZRGB>(cloud_msg, cloud); 00801 00802 // convert cloud to optical frame 00803 ROS_INFO("Transforming point cloud from %s frame to %s frame", cloud_msg.header.frame_id.c_str(), image_msg.header.frame_id.c_str()); 00804 transformPointCloud(image_msg.header.frame_id, cloud, converted_cloud); 00805 00806 ROS_INFO("Filtering point cloud with grab cut mask"); 00807 filterPointCloud(camera_info_msg, binary_mask, converted_cloud); 00808 if (converted_cloud.points.empty()) { 00809 ROS_ERROR("grabcut_node: No points left after filtering"); 00810 return false; 00811 } 00812 00813 // transform cloud to detection frame 00814 ROS_INFO("Transforming point cloud to %s frame", processing_frame_.c_str()); 00815 transformPointCloud( processing_frame_, converted_cloud, detection_cloud); 00816 00817 // convert pointcloud to message 00818 sensor_msgs::PointCloud2 detection_cloud_msg; 00819 pcl::toROSMsg<pcl::PointXYZRGB>(detection_cloud, detection_cloud_msg); 00820 00821 // fill service response 00822 response.result = response.SUCCESS; 00823 sensor_msgs::PointCloud cluster; 00824 sensor_msgs::convertPointCloud2ToPointCloud(detection_cloud_msg,cluster); 00825 response.clusters.push_back(cluster); 00826 00827 // publish markers 00828 publishClusterMarkers<sensor_msgs::PointCloud>(response.clusters, detection_cloud_msg.header); 00829 clearOldMarkers(cloud_msg.header.frame_id); 00830 00831 return true; 00832 } 00833 00835 template <class PointCloudType> 00836 void publishClusterMarkers(const std::vector<PointCloudType> &clusters, 00837 roslib::Header cloud_header) 00838 { 00839 geometry_msgs::Pose table_pose; 00840 //tf::poseTFToMsg(table_plane_trans, table_pose); 00841 for (size_t i=0; i<clusters.size() 00842 ; i++) 00843 { 00844 ROS_INFO("Cloud size: %d",(int)clusters[i].points.size()); 00845 visualization_msgs::Marker cloud_marker = MarkerGenerator::getCloudMarker(clusters[i]); 00846 cloud_marker.header = cloud_header; 00847 //set the marker in the pose of the table 00848 //cloud_marker.pose = table_pose; 00849 cloud_marker.ns = "tabletop_node"; 00850 cloud_marker.id = current_marker_id_++; 00851 marker_pub_.publish(cloud_marker); 00852 } 00853 } 00854 00855 void clearOldMarkers(std::string frame_id) 00856 { 00857 for (int id=current_marker_id_; id < num_markers_published_; id++) 00858 { 00859 visualization_msgs::Marker delete_marker; 00860 delete_marker.header.stamp = ros::Time::now(); 00861 delete_marker.header.frame_id = frame_id; 00862 delete_marker.id = id; 00863 delete_marker.action = visualization_msgs::Marker::DELETE; 00864 delete_marker.ns = "tabletop_node"; 00865 marker_pub_.publish(delete_marker); 00866 } 00867 num_markers_published_ = current_marker_id_; 00868 current_marker_id_ = 0; 00869 } 00870 00871 }; 00872 00873 00874 int main( int argc, char** argv ) 00875 { 00876 string node_name = "grabcut_tabletop_node"; 00877 ros::init(argc, argv, node_name); 00878 00879 // Run grab cut node 00880 ros::NodeHandle nh; 00881 GrabCutNode node(node_name,nh); 00882 00883 // ...and spin 00884 ros::spin(); 00885 00886 return 0; 00887 }