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>
00021 #include <cmath>
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_);
00030 nh.param<double>("door_radius", door_radius_, door_radius_);
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);
00051 pcl::io::savePCDFileASCII (filter_polygon_filename_, cropping_polygon_);
00052 }
00053
00054 }
00055
00056
00057
00058 void DoorHandleDetector::cloudCallback(const PointCloudType::ConstPtr& msg){
00059
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
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
00079 if(inside_cloud.size() == 0){
00080 ROS_INFO("No points found in door region");
00081 return;
00082 }
00083 else
00084 {
00085
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);
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
00098
00099 if(handle_cloud_publisher_.getNumSubscribers() > 0){
00100 handle_cloud_publisher_.publish(handle);
00101 }
00102 }
00103
00104
00105 if(vis_pub_.getNumSubscribers() > 0){
00106 cropping_polygon_.header.frame_id = door_frame_;
00107 cropping_polygon_.header.stamp = msg->header.stamp;
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
00126 pcl::SACSegmentation<pcl::PointXYZ> seg;
00127 seg.setOptimizeCoefficients (true);
00128 seg.setModelType (pcl::SACMODEL_PLANE);
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)
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
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
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
00171 pcl::ModelCoefficients handle_coeff;
00172 pcl::PointIndices::Ptr h_inliers (new pcl::PointIndices);
00173
00174 pcl::SACSegmentation<pcl::PointXYZ> seg2;
00175 seg2.setOptimizeCoefficients (true);
00176 seg2.setModelType (pcl::SACMODEL_LINE);
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
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
00206
00207
00208
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;
00221 }
00222
00223
00224 Eigen::Vector3f door_line(-door_normal(1),door_normal(0),0);
00225 double angle = atan2(door_line(1), door_line(0));
00226
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
00242 Eigen::Vector3f handle_mean, handle_axis;
00243 if(!computeMainAxis(handle_cloud, handle_mean, handle_axis, 15.0)) return;
00244
00245 if (!((handle_axis.array() == handle_axis.array())).all() ||
00246 !((handle_mean.array() == handle_mean.array())).all())
00247 {
00248 ROS_WARN("No valid axis for grasp");
00249 return;
00250 }
00251
00252
00253
00254 double handle_dist_from_door = door_normal.dot(handle_mean) + plane_dist_from_origin;
00255
00256 if(handle_dist_from_door > 0){
00257 door_normal *= -1.0;
00258 }
00259
00260 if(handle_mean.dot(handle_axis) > 0){
00261 handle_axis *= -1.0;
00262 }
00263 Eigen::Vector3f door_axis = handle_axis.cross(door_normal);
00264 Eigen::Matrix3f grm;
00265 grm << door_normal, door_axis, handle_axis;
00266
00267
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
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
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
00332 main_axis = eigenvectors.col(0);
00333
00334
00335
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