handle_detector.cpp
Go to the documentation of this file.
00001 #include "handle_detector.h"
00002 
00003 #include <pcl/ros/conversions.h>
00004 #include <pcl_ros/transforms.h>
00005 #include <pcl/common/transforms.h>
00006 
00007 #include <pcl/ModelCoefficients.h>
00008 #include <pcl/sample_consensus/method_types.h>
00009 #include <pcl/sample_consensus/model_types.h>
00010 #include <pcl/segmentation/sac_segmentation.h>
00011 #include <pcl/filters/extract_indices.h>
00012 #include <Eigen/Geometry>
00013 #include "visualize_polygon.hpp"
00014 #include "polygon_filter.h"
00015 #include <pcl/filters/passthrough.h>
00016 #include <pcl/io/pcd_io.h>
00017 
00018 #include <visualization_msgs/Marker.h>
00019 
00020 #include <limits> //quiet_nan
00021 #include <cmath> //atan2
00022 
00023 DoorHandleDetector::DoorHandleDetector() : door_frame_("/door"), active_(false), 
00024                                            filter_polygon_filename_("filter_polygon.pcd"),
00025                                            door_radius_(1.05)
00026 {
00027     ros::NodeHandle nh("~");
00028     nh.param<std::string>("door_frame", door_frame_, door_frame_);
00029     nh.param<bool>("start_active", active_, active_); //By default wait for activation by service call
00030     nh.param<double>("door_radius", door_radius_, door_radius_); //By default wait for activation by service call
00031     nh.param<std::string>("filter_polygon_filename", filter_polygon_filename_, filter_polygon_filename_);
00032     ROS_INFO("Using door frame: %s", door_frame_.c_str());
00033 
00034     if(active_) cloud_sub_.subscribe(node_, "cloud_in", 10);
00035 
00036     tf_filter_ = new tf::MessageFilter<PointCloudType>(cloud_sub_, tfListener_, door_frame_, 3);
00037     tf_filter_->registerCallback( boost::bind(&DoorHandleDetector::cloudCallback, this, _1) );
00038 
00039     door_cloud_publisher_ = node_.advertise<PointCloudType> ("door_cloud", 10, false);
00040     handle_cloud_publisher_ = node_.advertise<PointCloudType> ("handle_cloud", 10, false);
00041     door_state_publisher_  = node_.advertise<door_perception_msgs::DoorState> ("door_state", 10, false);
00042     door_state_service_ = node_.advertiseService("detect_door_state_in_cloud", &DoorHandleDetector::serviceCallback, this);
00043     vis_pub_ = node_.advertise<visualization_msgs::Marker>( "door_state_visualization", 2 );
00044     handle_pose_pub_ = node_.advertise<geometry_msgs::PoseStamped>( "door_handle", 3 );
00045 
00046     service_on_ = node_.advertiseService("handle_detection_on", &DoorHandleDetector::switchActive, this);
00047     service_off_ = node_.advertiseService("handle_detection_off", &DoorHandleDetector::switchInactive, this);
00048 
00049     if (pcl::io::loadPCDFile<PointType> (filter_polygon_filename_, cropping_polygon_) == -1){
00050       cropping_polygon_ = standardCroppingPolygon<PointType>(1.05f); //assumed door radius
00051       pcl::io::savePCDFileASCII (filter_polygon_filename_, cropping_polygon_);
00052     }
00053 
00054 }
00055 
00056 
00057 
00058 void DoorHandleDetector::cloudCallback(const PointCloudType::ConstPtr& msg){
00059   //TRANSFORMATION STUFF
00060     tf::StampedTransform to_door_frame;
00061     try {
00062       tfListener_.lookupTransform(door_frame_, msg->header.frame_id, msg->header.stamp, to_door_frame);
00063     }
00064     catch (tf::TransformException ex){
00065       ROS_WARN("%s",ex.what());
00066     }
00067 
00068     Eigen::Matrix4f eigen_transform;
00069     pcl_ros::transformAsMatrix(to_door_frame, eigen_transform);
00070     //tf::Transform sensor_in_door_frame = to_door_frame.inverse();
00071     
00072     PointCloudType transformed_cloud;
00073     pcl::transformPointCloud(*msg,transformed_cloud,eigen_transform);
00074     transformed_cloud.header.frame_id = door_frame_;
00075 
00076     PointCloudType inside_cloud = crop_by_polygon(cropping_polygon_, transformed_cloud, 0, 1);
00077     inside_cloud.header = transformed_cloud.header;
00078   //PROCESSING DOOR CLOUD
00079     if(inside_cloud.size() == 0){
00080       ROS_INFO("No points found in door region");
00081       return;
00082     }
00083     else
00084     {
00085       // Create the passthrough filtering object
00086       PointCloudType::Ptr cloud_filtered(new PointCloudType);
00087       pcl::PassThrough<PointType> pass;
00088       pass.setInputCloud (inside_cloud.makeShared());
00089       pass.setFilterFieldName ("z");
00090       pass.setFilterLimits (-0.5, 0.5); //Assumption: Handle is within half a meter of the door (tf) frame height
00091       pass.filter (*cloud_filtered);
00092       cloud_filtered->header = inside_cloud.header;
00093 
00094       PointCloudType handle;
00095       handle.header = inside_cloud.header;
00096       estimateDoorHandle(*cloud_filtered, handle);
00097       //Visualize
00098       //send handle cloud
00099       if(handle_cloud_publisher_.getNumSubscribers() > 0){
00100         handle_cloud_publisher_.publish(handle);
00101       }
00102     }
00103 
00104   //VISUALIZATION
00105     if(vis_pub_.getNumSubscribers() > 0){
00106       cropping_polygon_.header.frame_id = door_frame_;
00107       cropping_polygon_.header.stamp = msg->header.stamp;//Important for visualization
00108       visualization_msgs::Marker polygon = visualize_polygon(cropping_polygon_);
00109       vis_pub_.publish(polygon);
00110     }
00111 }
00112 
00113 
00115 bool DoorHandleDetector::doorHandleRansac(const PointCloudType& cloud, PointCloudType& handle_cloud, Eigen::Vector3f& door_normal, double& plane_distance_to_origin )
00116 {
00117   if(cloud.size() == 0){
00118     ROS_INFO("Cannot estimate door from empty cloud. Open?");
00119     return false;
00120   }
00121   ROS_WARN_COND(cloud.header.frame_id != (door_frame_), "Handle detection assumes cloud to be in the door frame (%s), not in %s", door_frame_.c_str(), cloud.header.frame_id.c_str());
00122 
00123   pcl::ModelCoefficients door_coeff;
00124   pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
00125   // Create the segmentation object
00126   pcl::SACSegmentation<pcl::PointXYZ> seg;
00127   seg.setOptimizeCoefficients (true); // Optional
00128   seg.setModelType (pcl::SACMODEL_PLANE); // Mandatory
00129   seg.setMethodType (pcl::SAC_RANSAC);
00130   seg.setDistanceThreshold (0.025);
00131 
00132   seg.setInputCloud (cloud.makeShared ());
00133   seg.segment (*inliers, door_coeff);
00134 
00135   if (inliers->indices.size () == 0)
00136   {
00137     ROS_WARN ("Could not estimate a planar model for the given cloud.");
00138     return false;
00139   }
00140 
00141   door_normal << door_coeff.values[0], door_coeff.values[1], door_coeff.values[2];
00142   door_normal.normalize();
00143   ROS_DEBUG("Normal xyz=(%.2f %.2f %.2f)", door_normal(0), door_normal(1), door_normal(2));
00144   ROS_WARN_COND(abs(door_normal(2)) > 0.1, "Door normal Z should be minimal!");
00145   plane_distance_to_origin = door_coeff.values[3];
00146   
00147   if (cloud.size() - inliers->indices.size () < 10) //minimum number of points to consider a handle
00148   {
00149     ROS_INFO_THROTTLE (10, "Found door, but there are too few points left for handle. (throttled to 1msg/10s)");
00150     return false;
00151   }
00152   // Extract the door plane outliers
00153   PointCloudType potential_handle;
00154   pcl::ExtractIndices<PointType> outlier_extractor;
00155   outlier_extractor.setInputCloud(cloud.makeShared());
00156   outlier_extractor.setIndices(inliers);
00157   outlier_extractor.setNegative(true);
00158   outlier_extractor.filter(potential_handle);
00159   ROS_DEBUG_STREAM("Door Outliers (Potential Handle): " << potential_handle.size());
00160 
00161   if(door_cloud_publisher_.getNumSubscribers() > 0){
00162     // Extract the door plane inliers
00163     PointCloudType door_cloud;
00164     outlier_extractor.setNegative(false);
00165     outlier_extractor.filter(door_cloud);
00166     ROS_DEBUG_STREAM("Door Inliers: " << door_cloud.size());
00167     door_cloud_publisher_.publish(door_cloud);
00168   }
00169 
00170   //Fit Line
00171   pcl::ModelCoefficients handle_coeff;
00172   pcl::PointIndices::Ptr h_inliers (new pcl::PointIndices);
00173   // Create the segmentation object
00174   pcl::SACSegmentation<pcl::PointXYZ> seg2;
00175   seg2.setOptimizeCoefficients (true); // Optional
00176   seg2.setModelType (pcl::SACMODEL_LINE); // Mandatory
00177   seg2.setMethodType (pcl::SAC_RANSAC);
00178   seg2.setDistanceThreshold (0.02);
00179 
00180   seg2.setInputCloud (potential_handle.makeShared ());
00181   seg2.segment (*h_inliers, handle_coeff);
00182 
00183   if (h_inliers->indices.size () < 10)
00184   {
00185     ROS_WARN ("Could not estimate a line model for the given handle.");
00186     return false;
00187   }
00188   ROS_WARN_COND(abs(handle_coeff.values[2]) > 0.1, "Handle axis Z should be minimal!");
00189   if(abs(handle_coeff.values[2]) > 0.3){
00190       ROS_WARN("Handle proposal rejected: Z (up/down) direction of handle should be minimal! Handle axis: (%.3f %.3f %.3f)", handle_coeff.values[0], handle_coeff.values[1], handle_coeff.values[2]);
00191       return false;
00192   }
00193   // Extract the handle line inliers
00194   
00195   pcl::ExtractIndices<PointType> inlier_extractor;
00196   inlier_extractor.setInputCloud(potential_handle.makeShared());
00197   inlier_extractor.setIndices(h_inliers);
00198   inlier_extractor.setNegative(false);
00199   inlier_extractor.filter(handle_cloud);
00200   ROS_DEBUG_STREAM("Handle Inliers (Handle): " << handle_cloud.size());
00201   return true;
00202 }
00203 
00204 
00205 //TODO: Ground Filter!
00206 //TODO: If performance becomes a problem, 1st project cropping polygon to
00207 //sensor frame, then crop, fit plane and send handle out in sensor frame.
00208 //Computes the door plane and the (smaller) angle to the Y axis
00209 void DoorHandleDetector::estimateDoorHandle(const PointCloudType& cloud, PointCloudType& handle_cloud)
00210 {
00211   Eigen::Vector3f door_normal(0,0,0);
00212   double  plane_dist_from_origin;
00213   if(!doorHandleRansac(cloud, handle_cloud, door_normal, plane_dist_from_origin)){
00214     door_state_msg_.header = cloud.header;; 
00215     door_state_msg_.door_found = false;
00216     door_state_msg_.angle = 0.0;
00217     if(door_state_publisher_.getNumSubscribers() > 0){
00218       door_state_publisher_.publish(door_state_msg_);
00219     }
00220     return; //No Valid Handle
00221   }
00222 
00223   //Door State (for service and publishing
00224   Eigen::Vector3f door_line(-door_normal(1),door_normal(0),0); //perpendicular to door normal
00225   double angle = atan2(door_line(1), door_line(0));//y and x equivalent, see below
00226   //Clamp to -20° to 160°
00227   if(angle < -20.0/180.0*3.1415927){
00228     angle += 3.1415927;
00229   }
00230   if(angle > 160.0/180.0*3.1415927){
00231     angle -= 3.1415927;
00232   }
00233 
00234   door_state_msg_.header = cloud.header;; 
00235   door_state_msg_.door_found = true;
00236   door_state_msg_.angle = angle;
00237   if(door_state_publisher_.getNumSubscribers() > 0){
00238     door_state_publisher_.publish(door_state_msg_);
00239   }
00240 
00241   //Computation of the Desired Gripper Transformations #########################
00242   Eigen::Vector3f handle_mean, handle_axis;
00243   if(!computeMainAxis(handle_cloud, handle_mean, handle_axis, 15.0)) return; //main axis eval is 100 times next eval
00244   //std::cout << "\nReturned handle_axis:\n" << handle_axis << "\nReturned handle_Mean:\n" << handle_mean << std::endl;
00245   if (!((handle_axis.array() == handle_axis.array())).all() || //checking for nan
00246       !((handle_mean.array() == handle_mean.array())).all()) 
00247   {
00248     ROS_WARN("No valid axis for grasp");
00249     return;
00250   }
00251   //ROS_INFO_THROTTLE(5,"Mean xyz=(%.2f %.2f %.2f)", handle_mean(0), handle_mean(1), handle_mean(2));
00252 
00253 //  http://mathworld.wolfram.com/HessianNormalForm.html
00254   double handle_dist_from_door = door_normal.dot(handle_mean) + plane_dist_from_origin;
00255   // X axis shall point into the door, so 
00256   if(handle_dist_from_door > 0){ //if door normal points towards handle,... 
00257     door_normal *= -1.0;//flip it
00258   }
00259   // Y Axis towards door hinge
00260   if(handle_mean.dot(handle_axis) > 0){ //also points in the direction hinge->handle
00261     handle_axis *= -1.0; //should point from handle->hinge
00262   }
00263   Eigen::Vector3f door_axis = handle_axis.cross(door_normal); // z-axis cross x-axis = y axis
00264   Eigen::Matrix3f grm;//gripper_rot_mat;
00265   grm << door_normal, door_axis, handle_axis;
00266   //ROS_INFO_STREAM_THROTTLE(5, "Desired Gripper Coord Frame\n: " << grm);
00267   //This is the desired gripper coord frame for the handle grasp
00268   tf::Matrix3x3 toGripperTargetCoordFrameMat(grm(0,0), grm(0,1), grm(0,2), 
00269                                              grm(1,0), grm(1,1), grm(1,2), 
00270                                              grm(2,0), grm(2,1), grm(2,2)) ;
00271   tf::Vector3 toGripperTargetCoordFrameVec(handle_mean(0), handle_mean(1), handle_mean(2));
00272   tf::Transform handlePose(toGripperTargetCoordFrameMat, toGripperTargetCoordFrameVec);
00273   tf::StampedTransform handlePoseStamped(handlePose, handle_cloud.header.stamp, handle_cloud.header.frame_id, handle_cloud.header.frame_id+"_handle");
00274   br_.sendTransform(handlePoseStamped);
00275   
00276   //Conversion madness
00277   tf::Stamped<tf::Pose> handlePoseStamped2(handlePoseStamped, handlePoseStamped.stamp_, handlePoseStamped.frame_id_);
00278   geometry_msgs::PoseStamped handlePoseMsg;
00279   tf::poseStampedTFToMsg(handlePoseStamped2, handlePoseMsg);
00280 
00281   if (handle_pose_pub_.getNumSubscribers() > 0){
00282     handle_pose_pub_.publish(handlePoseMsg);
00283   }
00284  
00285 }
00286 
00287 bool DoorHandleDetector::switchActive(std_srvs::Empty::Request  &req,
00288                                       std_srvs::Empty::Response &res )
00289 {
00290   active_ = true;
00291   cloud_sub_.subscribe(node_, "cloud_in", 10);
00292   ROS_INFO("Handle detection (from pointcloud) activated");
00293   return true;
00294 }
00295 bool DoorHandleDetector::switchInactive(std_srvs::Empty::Request  &req,
00296                                         std_srvs::Empty::Response &res )
00297 {
00298   active_ = false;
00299   cloud_sub_.unsubscribe();
00300   ROS_INFO("Handle detection (from pointcloud) deactivated");
00301   return true;
00302 }
00303 
00304 //Send out last result
00305 bool DoorHandleDetector::serviceCallback(door_perception_msgs::DoorStateSrv::Request& request, door_perception_msgs::DoorStateSrv::Response& response){
00306     response.state =door_state_msg_;    
00307     return true;
00308 }
00309 
00310 bool computeMainAxis(const PointCloudType& pc, Eigen::Vector3f& mean, Eigen::Vector3f& main_axis, float eval_ratio_threshold)
00311 {
00312   using namespace Eigen;
00313 
00314   mean = Vector3f::Zero();
00315   for(unsigned int i = 0; i < pc.size(); i++){
00316     const PointType& p = pc.points.at(i);
00317     mean += p.getVector3fMap ();
00318   }
00319   mean /= static_cast<float>(pc.size());
00320 
00321   Matrix3f cov = Matrix3f::Zero();
00322   for(unsigned int i = 0; i < pc.size(); i++){
00323     const PointType& p = pc.points.at(i);
00324     Vector3f v = p.getVector3fMap() - mean;
00325     cov += v * v.adjoint();
00326   }
00327   cov /= static_cast<float>(pc.size());
00328 
00329   JacobiSVD<Matrix3f> svd(cov, ComputeFullU);
00330   Matrix3f eigenvectors = svd.matrixU();
00331   //use first col as main eigenvector
00332   main_axis = eigenvectors.col(0); 
00333 
00334   //std::cout << "EigenVectors:" << std::endl << eigenvectors << std::endl;
00335   //std::cout << "EigenValues:\n" << svd.singularValues() << std::endl;
00336   Vector3f evals = svd.singularValues();
00337   if(evals(0) / evals(1) < eval_ratio_threshold){
00338       ROS_WARN_THROTTLE(5, "Low Eigenvalue Ratio: %f", (evals(0) / evals(1)));
00339       return false;
00340   }else 
00341       return true;
00342 }
00343 
00344 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Friends


door_perception
Author(s): Felix Endres
autogenerated on Wed Dec 26 2012 16:03:53