affordances.cpp
Go to the documentation of this file.
00001 #include <handle_detector/affordances.h>
00002 
00003 typedef pcl::PointCloud<pcl::PointXYZ> PointCloud;
00004 
00005 const int TAUBIN = 0;
00006 const int PCA = 1;
00007 const int NORMALS = 2;
00008 
00009 const std::string CURVATURE_ESTIMATORS[] = {"Taubin", "PCA", "Normals"};
00010 
00011 const int Affordances::CURVATURE_ESTIMATOR = 0;
00012 const int Affordances::NUM_SAMPLES = 5000;
00013 const int Affordances::NUM_NEAREST_NEIGHBORS = 500;
00014 const double Affordances::NEIGHBOR_RADIUS = 0.025;
00015 const int Affordances::MAX_NUM_IN_FRONT = 20;
00016 const double Affordances::TARGET_RADIUS = 0.08;
00017 const double Affordances::RADIUS_ERROR = 0.013;
00018 const double Affordances::HANDLE_GAP = 0.08;
00019 const double Affordances::MAX_RANGE = 1.0;
00020 const bool Affordances::USE_CLEARANCE_FILTER = true;
00021 const bool Affordances::USE_OCCLUSION_FILTER = true;
00022 const int Affordances::ALIGNMENT_RUNS = 3;
00023 const int Affordances::ALIGNMENT_MIN_INLIERS = 10;
00024 const double Affordances::ALIGNMENT_DIST_RADIUS = 0.02;
00025 const double Affordances::ALIGNMENT_ORIENT_RADIUS = 0.1;
00026 const double Affordances::ALIGNMENT_RADIUS_RADIUS = 0.003;
00027 const double Affordances::WORKSPACE_MIN = -1.0;
00028 const double Affordances::WORKSPACE_MAX = 1.0;
00029 
00030 void Affordances::initParams(ros::NodeHandle node)
00031 {
00032   // read parameters from ROS launch file
00033   std::string file_default = "";
00034   node.param("file", this->file, file_default);
00035   node.param("target_radius", this->target_radius, this->TARGET_RADIUS);
00036   node.param("target_radius_error", this->radius_error, this->RADIUS_ERROR);
00037   node.param("affordance_gap", this->handle_gap, this->HANDLE_GAP);
00038   node.param("sample_size", this->num_samples, this->NUM_SAMPLES);
00039   node.param("max_range", this->max_range, this->MAX_RANGE);
00040   node.param("use_clearance_filter", this->use_clearance_filter, this->USE_CLEARANCE_FILTER);
00041   node.param("use_occlusion_filter", this->use_occlusion_filter, this->USE_OCCLUSION_FILTER);
00042   node.param("curvature_estimator", this->curvature_estimator, this->CURVATURE_ESTIMATOR);
00043   node.param("alignment_runs", this->alignment_runs, this->ALIGNMENT_RUNS);
00044   node.param("alignment_min_inliers", this->alignment_min_inliers, this->ALIGNMENT_MIN_INLIERS);
00045   node.param("alignment_dist_radius", this->alignment_dist_radius, this->ALIGNMENT_DIST_RADIUS);
00046   node.param("alignment_orient_radius", this->alignment_orient_radius, this->ALIGNMENT_ORIENT_RADIUS);
00047   node.param("alignment_radius_radius", this->alignment_radius_radius, this->ALIGNMENT_RADIUS_RADIUS);
00048   node.param("workspace_min_x", this->workspace_limits.min_x, this->WORKSPACE_MIN);
00049   node.param("workspace_max_x", this->workspace_limits.max_x, this->WORKSPACE_MAX);
00050   node.param("workspace_min_y", this->workspace_limits.min_y, this->WORKSPACE_MIN);
00051   node.param("workspace_max_y", this->workspace_limits.max_y, this->WORKSPACE_MAX);
00052   node.param("workspace_min_z", this->workspace_limits.min_z, this->WORKSPACE_MIN);
00053   node.param("workspace_max_z", this->workspace_limits.max_z, this->WORKSPACE_MAX);
00054   node.param("num_threads", this->num_threads, 1);
00055 
00056   // print parameters
00057   printf("PARAMETERS\n");
00058   printf(" file: %s\n", this->file.c_str());
00059   printf(" target radius: %.3f\n", this->target_radius);
00060   printf(" target radius error: %.3f\n", this->radius_error);
00061   printf(" min. affordance gap: %.3f\n", this->handle_gap);
00062   printf(" number of samples: %i\n", this->num_samples);
00063   printf(" max. range: %.3f\n", this->max_range);
00064   printf(" use clearance filter: %s\n", this->use_clearance_filter ? "true" : "false");
00065   printf(" use occlusion filter: %s\n", this->use_occlusion_filter ? "true" : "false");
00066   printf(" curvature estimator: %s\n", CURVATURE_ESTIMATORS[this->curvature_estimator].c_str());
00067   printf(" number of alignment runs: %i\n", this->alignment_runs);
00068   printf(" min. number of alignment inliers: %i\n", this->alignment_min_inliers);
00069   printf(" alignment distance threshold: %.3f\n", this->alignment_dist_radius);
00070   printf(" alignment orientation threshold: %.3f\n", this->alignment_orient_radius);
00071   printf(" alignment radius threshold: %.3f\n", this->alignment_radius_radius);
00072   printf(" workspace_min_x: %.3f\n", this->workspace_limits.min_x);
00073   printf(" workspace_max_x: %.3f\n", this->workspace_limits.max_x);
00074   printf(" workspace_min_y: %.3f\n", this->workspace_limits.min_y);
00075   printf(" workspace_max_y: %.3f\n", this->workspace_limits.max_y);
00076   printf(" workspace_min_z: %.3f\n", this->workspace_limits.min_z);
00077   printf(" workspace_max_z: %.3f\n", this->workspace_limits.max_z);
00078   printf(" num_threads: %i\n", this->num_threads);
00079 }
00080 
00081 PointCloud::Ptr Affordances::maxRangeFilter(const PointCloud::Ptr &cloud_in)
00082 {
00083   PointCloud::Ptr cloud_out(new PointCloud);
00084 
00085   for (std::size_t i = 0; i < cloud_in->points.size(); i++)
00086   {
00087     if (cloud_in->points[i].x * cloud_in->points[i].x + cloud_in->points[i].y * cloud_in->points[i].y
00088         + cloud_in->points[i].z * cloud_in->points[i].z < this->max_range * this->max_range)
00089       cloud_out->points.push_back(cloud_in->points[i]);
00090   }
00091 
00092   return cloud_out;
00093 }
00094 
00095 bool Affordances::isPointInWorkspace(double x, double y, double z, tf::StampedTransform *transform)
00096 {
00097   if (transform != NULL)
00098   {
00099     tf::Vector3 v(x, y, z);
00100     tf::Vector3 tf_v = (*transform) * v;
00101     x = tf_v.getX();
00102     y = tf_v.getY();
00103     z = tf_v.getZ();
00104   }
00105 
00106   WorkspaceLimits limits = this->workspace_limits;
00107 
00108   if (x >= limits.min_x && x <= limits.max_x && y >= limits.min_y && y <= limits.max_y && z >= limits.min_z
00109       && z <= limits.max_z)
00110   {
00111     return true;
00112   }
00113 
00114   return false;
00115 }
00116 
00117 PointCloud::Ptr Affordances::workspaceFilter(const PointCloud::Ptr &cloud_in, tf::StampedTransform *transform)
00118 {
00119   PointCloud::Ptr cloud_out(new PointCloud);
00120 
00121   for (std::size_t i = 0; i < cloud_in->points.size(); i++)
00122   {
00123     if (this->isPointInWorkspace(cloud_in->points[i].x, cloud_in->points[i].y, cloud_in->points[i].z, transform))
00124       cloud_out->points.push_back(cloud_in->points[i]);
00125   }
00126 
00127   return cloud_out;
00128 }
00129 
00130 PointCloudRGB::Ptr Affordances::workspaceFilter(const PointCloudRGB::Ptr &cloud_in, tf::StampedTransform *transform)
00131 {
00132   PointCloudRGB::Ptr cloud_out(new PointCloudRGB);
00133 
00134   for (std::size_t i = 0; i < cloud_in->points.size(); i++)
00135   {
00136     if (this->isPointInWorkspace(cloud_in->points[i].x, cloud_in->points[i].y, cloud_in->points[i].z, transform))
00137       cloud_out->points.push_back(cloud_in->points[i]);
00138   }
00139 
00140   return cloud_out;
00141 }
00142 
00143 int Affordances::numInFront(const PointCloud::Ptr &cloud, int center_index, double radius)
00144 {
00145   Eigen::Vector3f center = cloud->points[center_index].getVector3fMap();
00146   double dist_center = center.norm();
00147   double theta = atan(radius / dist_center);
00148   Eigen::Vector3f center_unit = center / dist_center;
00149   int num_in_front = 0;
00150 
00151   for (std::size_t i = 0; i < cloud->points.size(); i++)
00152   {
00153     if (isnan(cloud->points[i].x))
00154       continue;
00155 
00156     Eigen::Vector3f point = cloud->points[i].getVector3fMap();
00157     float point_norm = point.norm();
00158     Eigen::Vector3f point_unit = point / point_norm;
00159 
00160     if (fabs(point_unit.dot(center_unit)) < cos(theta))
00161       continue;
00162 
00163     if (point_norm < dist_center - radius)
00164       num_in_front++;
00165   }
00166 
00167   return num_in_front;
00168 }
00169 
00170 void Affordances::estimateCurvatureAxisPCA(const PointCloud::Ptr &cloud, int nn_center_idx, std::vector<int> nn_indices,
00171                                            Eigen::Vector3d &axis, Eigen::Vector3d &normal)
00172 {
00173   Eigen::Matrix3f covar_mat;
00174   Eigen::Vector4f nn_centroid;
00175   nn_centroid << cloud->points[nn_center_idx].x, cloud->points[nn_center_idx].y, cloud->points[nn_center_idx].z, 0;
00176 
00177   pcl::computeCovarianceMatrix(*cloud, nn_indices, nn_centroid, covar_mat);
00178 
00179   Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> eigen_solver(covar_mat);
00180   Eigen::Vector3f eig_vals = eigen_solver.eigenvalues();
00181   int max_index;
00182   eig_vals.maxCoeff(&max_index);
00183   axis = eigen_solver.eigenvectors().col(max_index).cast<double>();
00184   Eigen::Vector3d perp_axis;
00185   perp_axis << -axis(1), axis(0), 0;
00186   normal = axis.cross(perp_axis);
00187   normal /= normal.norm();
00188 }
00189 
00190 void Affordances::estimateCurvatureAxisNormals(const pcl::PointCloud<pcl::Normal>::Ptr &cloud_normals,
00191                                                const std::vector<int> &nn_indices, Eigen::Vector3d &axis,
00192                                                Eigen::Vector3d &normal)
00193 {
00194   Eigen::Matrix3d mat;
00195   mat.setZero();
00196 
00197   for (std::size_t j = 0; j < nn_indices.size(); j++)
00198   {
00199     Eigen::Vector3d normal;
00200     normal << cloud_normals->points[nn_indices[j]].normal[0], cloud_normals->points[nn_indices[j]].normal[1], cloud_normals->points[nn_indices[j]].normal[2];
00201     Eigen::Matrix3d matNormal = normal * normal.transpose();
00202     mat += matNormal;
00203   }
00204 
00205   Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver(mat);
00206   Eigen::Vector3d eig_vals = eigen_solver.eigenvalues();
00207   int min_index;
00208   eig_vals.minCoeff(&min_index);
00209   axis = eigen_solver.eigenvectors().col(min_index);
00210   Eigen::Vector3d perp_axis;
00211   perp_axis << -axis(1), axis(0), 0;
00212   normal = axis.cross(perp_axis);
00213   normal /= normal.norm();
00214 }
00215 
00216 void Affordances::estimateNormals(const PointCloud::Ptr &cloud, const pcl::PointCloud<pcl::Normal>::Ptr &cloud_normals)
00217 {
00218   pcl::NormalEstimationOMP<pcl::PointXYZ, pcl::Normal> normal_estimator;
00219   normal_estimator.setInputCloud(cloud);
00220   pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>());
00221   normal_estimator.setSearchMethod(tree);
00222   normal_estimator.setRadiusSearch(0.03);
00223   normal_estimator.compute(*cloud_normals);
00224 }
00225 
00226 std::vector<CylindricalShell> Affordances::searchAffordances(const PointCloud::Ptr &cloud,
00227                                                              tf::StampedTransform *transform)
00228 {
00229   std::vector<CylindricalShell> shells;
00230   shells.resize(0);
00231 
00232   if (this->curvature_estimator == TAUBIN)
00233     shells = this->searchAffordancesTaubin(cloud, transform);
00234   else if (this->curvature_estimator == NORMALS)
00235     shells = this->searchAffordancesNormalsOrPCA(cloud, transform);
00236   else if (this->curvature_estimator == PCA)
00237     shells = this->searchAffordancesNormalsOrPCA(cloud, transform);
00238 
00239   return shells;
00240 }
00241 
00242 std::vector<CylindricalShell> Affordances::searchAffordancesNormalsOrPCA(const PointCloud::Ptr &cloud,
00243                                                                          tf::StampedTransform *transform)
00244 {
00245   pcl::PointCloud<pcl::Normal>::Ptr cloud_normals(new pcl::PointCloud<pcl::Normal>);
00246 
00247   // estimate surface normals
00248   if (this->curvature_estimator == NORMALS)
00249   {
00250     double begin_time_normals_estimation = omp_get_wtime();
00251     printf("Estimating surface normals ...\n");
00252     this->estimateNormals(cloud, cloud_normals);
00253     printf(" elapsed time: %.3f sec\n", omp_get_wtime() - begin_time_normals_estimation);
00254   }
00255 
00256   // search cloud for a set of point neighborhoods
00257   double begin_time_axis = omp_get_wtime();
00258   printf("Estimating cylinder surface normal and curvature axis ...\n");
00259   pcl::PointXYZ searchPoint;
00260   std::vector<int> nn_indices;
00261   std::vector < std::vector<int> > neighborhoods(this->num_samples);
00262   std::vector<int> neighborhood_centroids(this->num_samples);
00263   std::vector<Eigen::Vector3d> normals(this->num_samples);
00264   std::vector<Eigen::Vector3d> curvature_axes(this->num_samples);
00265 
00266   // set-up an Organized Neighbor search
00267   if (cloud->isOrganized())
00268   {
00269     std::vector<float> nn_dists;
00270     pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00271         new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00272     organized_neighbor->setInputCloud(cloud);
00273     std::srand(std::time(0)); // use current time as seed for random generator
00274 
00275     for (int i = 0; i < this->num_samples; i++)
00276     {
00277       // sample random point from the point cloud
00278       int r = std::rand() % cloud->points.size();
00279 
00280       while (!pcl::isFinite((*cloud)[r]) || !this->isPointInWorkspace((*cloud)[r].x, (*cloud)[r].y, (*cloud)[r].z))
00281         r = std::rand() % cloud->points.size();
00282 
00283       // estimate cylinder curvature axis and normal
00284       if (organized_neighbor->radiusSearch((*cloud)[r], this->NEIGHBOR_RADIUS, nn_indices, nn_dists) > 0)
00285       {
00286         if (this->curvature_estimator == NORMALS)
00287           this->estimateCurvatureAxisNormals(cloud_normals, nn_indices, curvature_axes[i], normals[i]);
00288         else if (this->curvature_estimator == PCA)
00289           this->estimateCurvatureAxisPCA(cloud, r, nn_indices, curvature_axes[i], normals[i]);
00290 
00291         neighborhoods[i] = nn_indices;
00292         neighborhood_centroids[i] = r;
00293       }
00294     }
00295   }
00296   else
00297   {
00298     std::vector<float> nn_dists;
00299     pcl::KdTreeFLANN<pcl::PointXYZ> tree;
00300     tree.setInputCloud(cloud);
00301     std::srand(std::time(0)); // use current time as seed for random generator
00302 
00303     for (int i = 0; i < this->num_samples; i++)
00304     {
00305       // sample random point from the point cloud
00306       int r = std::rand() % cloud->points.size();
00307 
00308       while (!pcl::isFinite((*cloud)[r]) || !this->isPointInWorkspace((*cloud)[r].x, (*cloud)[r].y, (*cloud)[r].z))
00309         r = std::rand() % cloud->points.size();
00310 
00311       // estimate cylinder curvature axis and normal
00312       if (tree.radiusSearch((*cloud)[r], this->NEIGHBOR_RADIUS, nn_indices, nn_dists) > 0)
00313       {
00314         if (this->curvature_estimator == NORMALS)
00315           this->estimateCurvatureAxisNormals(cloud_normals, nn_indices, curvature_axes[i], normals[i]);
00316         else if (this->curvature_estimator == PCA)
00317           this->estimateCurvatureAxisPCA(cloud, r, nn_indices, curvature_axes[i], normals[i]);
00318 
00319         neighborhoods[i] = nn_indices;
00320         neighborhood_centroids[i] = r;
00321       }
00322     }
00323   }
00324 
00325   printf(" elapsed time: %.3f sec\n", omp_get_wtime() - begin_time_axis);
00326 
00327   // define lower and upper bounds on radius of cylinder
00328   double min_radius_cylinder = this->target_radius - this->radius_error;
00329   double max_radius_cylinder = this->target_radius + this->radius_error;
00330 
00331   if (this->use_clearance_filter)
00332     printf("Filtering on curvature, fitting cylinders, and filtering on low clearance ...\n");
00333   else
00334     printf("Filtering on curvature and fitting cylinders ...\n");
00335 
00336   double begin_time = omp_get_wtime();
00337   int cylinders_left_clearance = 0;
00338   Eigen::Vector3d normal;
00339   Eigen::Vector3d curvature_axis;
00340   Eigen::Vector3d curvature_centroid;
00341   std::vector<CylindricalShell> shells;
00342 
00343   double maxHandAperture = this->target_radius + this->radius_error;
00344   double outer_sample_radius = 1.5 * (maxHandAperture + this->handle_gap); // outer sample radius
00345 
00346   // for organized point clouds
00347   pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00348       new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00349   // for unorganized point clouds
00350   pcl::KdTreeFLANN<pcl::PointXYZ> tree;
00351   if (cloud->isOrganized())
00352     organized_neighbor->setInputCloud(cloud);
00353   else
00354     tree.setInputCloud(cloud);
00355 
00356   for (int i = 0; i < this->num_samples; i++)
00357   {
00358     // fit a cylinder to the neighborhood
00359     CylindricalShell shell;
00360     shell.fitCylinder(cloud, neighborhoods[i], normals[i], curvature_axes[i]);
00361 
00362     // set height of shell to 2 * <target_radius>
00363     shell.setExtent(2.0 * this->target_radius);
00364 
00365     // set index of centroid of neighborhood associated with the cylindrical shell
00366     shell.setNeighborhoodCentroidIndex(neighborhood_centroids[i]);
00367 
00368     // check cylinder radius against target radius
00369     if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00370     {
00371       // filter on low clearance
00372       if (this->use_clearance_filter)
00373       {
00374         pcl::PointXYZ searchPoint;
00375         std::vector<int> nn_indices;
00376         std::vector<float> nn_dists;
00377         Eigen::Vector3d centroid = shell.getCentroid();
00378         searchPoint.x = centroid(0);
00379         searchPoint.y = centroid(1);
00380         searchPoint.z = centroid(2);
00381         int num_in_radius = 0;
00382         if (cloud->isOrganized())
00383           num_in_radius = organized_neighbor->radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00384         else
00385           num_in_radius = tree.radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00386 
00387         if ((num_in_radius > 0) && (shell.hasClearance(cloud, nn_indices, maxHandAperture, this->handle_gap)))
00388           shells.push_back(shell);
00389       }
00390       else
00391         shells.push_back(shell);
00392     }
00393   }
00394 
00395   printf(" elapsed time: %.3f sec\n", omp_get_wtime() - begin_time);
00396   if (this->use_clearance_filter)
00397     printf(" cylinders left after clearance filtering: %i\n", (int)shells.size());
00398 
00399   return shells;
00400 }
00401 
00402 std::vector<CylindricalShell> Affordances::searchAffordancesTaubin(const PointCloud::Ptr &cloud,
00403                                                                    tf::StampedTransform *transform)
00404 {
00405   printf("Estimating curvature ...\n");
00406   double beginTime = omp_get_wtime();
00407 
00408   // set-up estimator
00409   pcl::CurvatureEstimationTaubin<pcl::PointXYZ, pcl::PointCurvatureTaubin> estimator;
00410 
00411   // set input source
00412   estimator.setInputCloud(cloud);
00413 
00414   // set radius search
00415   estimator.setRadiusSearch(this->NEIGHBOR_RADIUS);
00416 
00417   // set the number of samples
00418   estimator.setNumSamples(this->num_samples);
00419 
00420   // provide a set of neighborhood centroids
00421   std::vector<int> indices(this->num_samples);
00422   std::srand(std::time(0)); // use current time as seed for random generator
00423   int k;
00424   for (int i = 0; i < this->num_samples; i++)
00425   {
00426     int r = std::rand() % cloud->points.size();
00427     k = 0;
00428     while (!pcl::isFinite((*cloud)[r])
00429         || !this->isPointInWorkspace(cloud->points[r].x, cloud->points[r].y, cloud->points[r].z, transform))
00430     {
00431       r = std::rand() % cloud->points.size();
00432       k++;
00433       if (k == cloud->points.size()) // check that the cloud has finite points
00434       {
00435         printf("No finite points in cloud!\n");
00436         std::vector<CylindricalShell> shells;
00437         shells.resize(0);
00438         return shells;
00439       }
00440     }
00441     indices[i] = r;
00442   }
00443   boost::shared_ptr<std::vector<int> > indices_ptr(new std::vector<int>(indices));
00444   estimator.setIndices(indices_ptr);
00445 
00446   // set number of threads
00447   estimator.setNumThreads(this->num_threads);
00448 
00449   // output dataset
00450   pcl::PointCloud<pcl::PointCurvatureTaubin>::Ptr cloud_curvature(new pcl::PointCloud<pcl::PointCurvatureTaubin>);
00451 
00452   // compute median curvature, normal axis, curvature axis, and curvature centroid
00453   estimator.compute(*cloud_curvature);
00454 
00455   printf(" elapsed time: %.3f sec\n", omp_get_wtime() - beginTime);
00456 
00457   // define lower and upper bounds on radius of osculating sphere and cylinder
00458   double min_radius_osculating_sphere = this->target_radius - 2.0 * this->radius_error;
00459   double max_radius_osculating_sphere = this->target_radius + 2.0 * this->radius_error;
00460   double min_radius_cylinder = this->target_radius - this->radius_error;
00461   double max_radius_cylinder = this->target_radius + this->radius_error;
00462 
00463   if (this->use_clearance_filter)
00464     printf("Filtering on curvature, fitting cylinders, and filtering on low clearance ...\n");
00465   else
00466     printf("Filtering on curvature and fitting cylinders ...\n");
00467 
00468   double begin_time = omp_get_wtime();
00469   int cylinders_left_radius = 0;
00470   int cylinders_left_clearance = 0;
00471   Eigen::Vector3d normal;
00472   Eigen::Vector3d curvature_axis;
00473   std::vector<CylindricalShell> shells;
00474 
00475   double maxHandAperture = this->target_radius + this->radius_error;
00476   double outer_sample_radius = 1.5 * (maxHandAperture + this->handle_gap); // outer sample radius
00477 
00478   // for organized point clouds
00479   pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00480       new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00481   // for unorganized point clouds
00482   pcl::KdTreeFLANN<pcl::PointXYZ> tree;
00483   if (cloud->isOrganized())
00484     organized_neighbor->setInputCloud(cloud);
00485   else
00486     tree.setInputCloud(cloud);
00487 
00488   for (int i = 0; i < cloud_curvature->size(); i++)
00489   {
00490     // calculate radius of osculating sphere
00491     double radius = 1.0 / fabs(cloud_curvature->points[i].median_curvature);
00492 
00493     // filter out planar regions and cylinders that are too large
00494     if (radius > min_radius_osculating_sphere && radius < max_radius_osculating_sphere)
00495     {
00496       // fit a cylinder to the neighborhood
00497       normal << cloud_curvature->points[i].normal_x, cloud_curvature->points[i].normal_y, cloud_curvature->points[i].normal_z;
00498       curvature_axis << cloud_curvature->points[i].curvature_axis_x, cloud_curvature->points[i].curvature_axis_y, cloud_curvature->points[i].curvature_axis_z;
00499       CylindricalShell shell;
00500       shell.fitCylinder(cloud, estimator.getNeighborhoods()[i], normal, curvature_axis);
00501 
00502       // set height of shell to 2 * <target_radius>
00503       shell.setExtent(2.0 * this->target_radius);
00504 
00505       // set index of centroid of neighborhood associated with the cylindrical shell
00506       shell.setNeighborhoodCentroidIndex(estimator.getNeighborhoodCentroids()[i]);
00507 
00508       // check cylinder radius against target radius
00509       if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00510       {
00511         cylinders_left_radius++;
00512 
00513         // filter on low clearance
00514         if (this->use_clearance_filter)
00515         {
00516           pcl::PointXYZ searchPoint;
00517           std::vector<int> nn_indices;
00518           std::vector<float> nn_dists;
00519           Eigen::Vector3d centroid = shell.getCentroid();
00520           searchPoint.x = centroid(0);
00521           searchPoint.y = centroid(1);
00522           searchPoint.z = centroid(2);
00523           int num_in_radius = 0;
00524           if (cloud->isOrganized())
00525             num_in_radius = organized_neighbor->radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00526           else
00527             num_in_radius = tree.radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00528 
00529           if ((num_in_radius > 0) && (shell.hasClearance(cloud, nn_indices, maxHandAperture, this->handle_gap)))
00530             shells.push_back(shell);
00531         }
00532         else
00533           shells.push_back(shell);
00534       }
00535     }
00536   }
00537 
00538   printf(" elapsed time: %.3f sec\n", omp_get_wtime() - begin_time);
00539   printf(" cylinders left after radius filtering: %i\n", cylinders_left_radius);
00540   if (this->use_clearance_filter)
00541     printf(" cylinders left after clearance filtering: %i\n", (int)shells.size());
00542 
00543   return shells;
00544 }
00545 
00546 std::vector<std::vector<CylindricalShell> > Affordances::searchHandles(const PointCloud::Ptr &cloud,
00547                                                                        std::vector<CylindricalShell> shells)
00548 {
00549   std::vector < std::vector<CylindricalShell> > handles;
00550 
00551   // find colinear sets of cylinders
00552   if (this->alignment_runs > 0)
00553   {
00554     std::cout << "alignment search for colinear sets of cylinders (handles) ... " << std::endl;
00555     double beginTime = omp_get_wtime();
00556     std::vector<int> inliersMaxSet, outliersMaxSet;
00557 
00558     // linear search
00559     for (int i = 0; i < this->alignment_runs && shells.size() > 0; i++)
00560     {
00561       this->findBestColinearSet(shells, inliersMaxSet, outliersMaxSet);
00562       printf(" number of inliers in run %i: %i", i, (int)inliersMaxSet.size());
00563 
00564       if (inliersMaxSet.size() >= this->alignment_min_inliers)
00565       {
00566         // create handle from inlier indices
00567         std::vector<CylindricalShell> handle;
00568         for (std::size_t j = 0; j < inliersMaxSet.size(); j++)
00569         {
00570           int idx = inliersMaxSet[j];
00571           handle.push_back(shells[idx]);
00572         }
00573 
00574         // check for occlusions
00575         if (this->use_occlusion_filter)
00576         {
00577           int MAX_NUM_OCCLUDED = (int)handle.size() * 0.5; // 5
00578           int num_occluded = 0;
00579           bool is_occluded = false;
00580 
00581           for (std::size_t j = 0; j < handle.size(); j++)
00582           {
00583             if (this->numInFront(cloud, handle[j].getNeighborhoodCentroidIndex(),
00584                                  1.5 * this->target_radius + this->radius_error) > this->MAX_NUM_IN_FRONT)
00585             {
00586               num_occluded++;
00587               if (num_occluded > MAX_NUM_OCCLUDED)
00588               {
00589                 is_occluded = true;
00590                 break;
00591               }
00592             }
00593           }
00594 
00595           printf("  number of occluded affordances: %i; occluded: %s\n", num_occluded, is_occluded ? "true" : "false");
00596 
00597           if (!is_occluded)
00598             handles.push_back(handle);
00599         }
00600         else
00601         {
00602           handles.push_back(handle);
00603         }
00604 
00605         // prune list of cylindrical shells
00606         std::vector<CylindricalShell> remainder(outliersMaxSet.size());
00607         for (std::size_t j = 0; j < outliersMaxSet.size(); j++)
00608           remainder[j] = shells[outliersMaxSet[j]];
00609         shells = remainder;
00610         printf(", remaining cylinders: %i\n", (int)shells.size());
00611       }
00612       // do not check for occlusions
00613       else
00614       {
00615         break;
00616       }
00617     }
00618 
00619     printf(" elapsed time: %.3f\n", omp_get_wtime() - beginTime);
00620   }
00621 
00622   return handles;
00623 }
00624 
00625 std::vector<CylindricalShell> Affordances::searchAffordances(const PointCloud::Ptr &cloud,
00626                                                              const std::vector<int> &indices)
00627 {
00628   Eigen::MatrixXd samples(3, indices.size());
00629   for (std::size_t i = 0; i < indices.size(); i++)
00630     samples.col(i) = cloud->points[indices[i]].getVector3fMap().cast<double>();
00631 
00632   return this->searchAffordancesTaubin(cloud, samples);
00633 }
00634 
00635 std::vector<CylindricalShell> Affordances::searchAffordancesTaubin(const PointCloud::Ptr &cloud,
00636                                                                    const Eigen::MatrixXd &samples, bool is_logging)
00637 {
00638   if (is_logging)
00639     printf("Estimating curvature ...\n");
00640 
00641   double beginTime = omp_get_wtime();
00642 
00643   // set-up estimator
00644   pcl::PointCloud<pcl::PointCurvatureTaubin>::Ptr cloud_curvature(new pcl::PointCloud<pcl::PointCurvatureTaubin>);
00645   pcl::CurvatureEstimationTaubin<pcl::PointXYZ, pcl::PointCurvatureTaubin> estimator;
00646   estimator.setInputCloud(cloud);
00647   estimator.setRadiusSearch(this->NEIGHBOR_RADIUS);
00648   //~ estimator.setRadiusSearch(1.5*target_radius + radius_error);
00649   estimator.setNumThreads(this->num_threads);
00650 
00651   // compute median curvature, normal axis, curvature axis, and curvature centroid
00652   estimator.computeFeature(samples, *cloud_curvature);
00653 
00654   if (is_logging)
00655     printf(" elapsed time: %.3f sec, cylinders left: %i\n", omp_get_wtime() - beginTime,
00656            (int)cloud_curvature->points.size());
00657 
00658   // define lower and upper bounds on radius of osculating sphere and cylinder
00659   double min_radius_osculating_sphere = this->target_radius - 2.0 * this->radius_error;
00660   double max_radius_osculating_sphere = this->target_radius + 2.0 * this->radius_error;
00661   double min_radius_cylinder = this->target_radius - this->radius_error;
00662   double max_radius_cylinder = this->target_radius + this->radius_error;
00663 
00664   if (is_logging && this->use_clearance_filter)
00665     printf("Filtering on curvature, fitting cylinders, and filtering on low clearance ...\n");
00666   else if (is_logging)
00667     printf("Filtering on curvature and fitting cylinders ...\n");
00668 
00669   double begin_time = omp_get_wtime();
00670   int cylinders_left_radius = 0;
00671   int cylinders_left_clearance = 0;
00672   Eigen::Vector3d normal;
00673   Eigen::Vector3d curvature_axis;
00674   std::vector<CylindricalShell> shells;
00675   double tcyltotal = 0.0;
00676   double tcleartotal = 0.0;
00677 
00678   double maxHandAperture = this->target_radius + this->radius_error;
00679   double outer_sample_radius = 1.5 * (maxHandAperture + this->handle_gap); // outer sample radius
00680 
00681   // for organized point clouds
00682   pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00683       new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00684   // for unorganized point clouds
00685   pcl::KdTreeFLANN<pcl::PointXYZ> tree;
00686   if (cloud->isOrganized())
00687     organized_neighbor->setInputCloud(cloud);
00688   else
00689     tree.setInputCloud(cloud);
00690 
00691   //~ #ifdef _OPENMP
00692   //~ #pragma omp parallel for shared (cylinderList) firstprivate(cloud_curvature) private(radius, centroid_cyl, extent_cyl, normal, curvature_axis, curvature_centroid) num_threads(this->num_threads)
00693   //~ #endif
00694   for (int i = 0; i < cloud_curvature->size(); i++)
00695   {
00696     if (isnan(cloud_curvature->points[i].normal[0]))
00697       continue;
00698 
00699     // calculate radius of osculating sphere
00700     double radius = 1.0 / fabs(cloud_curvature->points[i].median_curvature);
00701     //~ printf("%i: radius of osculating sphere: %.4f\n", i, radius);
00702 
00703     // filter out planar regions and cylinders that are too large
00704     if (radius > min_radius_osculating_sphere && radius < max_radius_osculating_sphere)
00705     {
00706       // fit a cylinder to the neighborhood
00707       double tcyl0 = omp_get_wtime();
00708       normal << cloud_curvature->points[i].normal_x, cloud_curvature->points[i].normal_y, cloud_curvature->points[i].normal_z;
00709       curvature_axis << cloud_curvature->points[i].curvature_axis_x, cloud_curvature->points[i].curvature_axis_y, cloud_curvature->points[i].curvature_axis_z;
00710       CylindricalShell shell;
00711       shell.fitCylinder(cloud, estimator.getNeighborhoods()[i], normal, curvature_axis);
00712       tcyltotal += omp_get_wtime() - tcyl0;
00713 
00714       //~ printf(" radius of fitted cylinder: %.4f\n", shell.getRadius());
00715 
00716       // set height of shell to 2 * <target_radius>
00717       shell.setExtent(2.0 * this->target_radius);
00718 
00719       // set index of centroid of neighborhood associated with the cylindrical shell
00720       shell.setNeighborhoodCentroidIndex(estimator.getNeighborhoodCentroids()[i]);
00721 
00722       // check cylinder radius against target radius
00723       if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00724       {
00725         cylinders_left_radius++;
00726 
00727         // filter on low clearance
00728         if (this->use_clearance_filter)
00729         {
00730           double tclear0 = omp_get_wtime();
00731           pcl::PointXYZ searchPoint;
00732           std::vector<int> nn_indices;
00733           std::vector<float> nn_dists;
00734           Eigen::Vector3d centroid = shell.getCentroid();
00735           searchPoint.x = centroid(0);
00736           searchPoint.y = centroid(1);
00737           searchPoint.z = centroid(2);
00738           int num_in_radius = 0;
00739           if (cloud->isOrganized())
00740             num_in_radius = organized_neighbor->radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00741           else
00742             num_in_radius = tree.radiusSearch(searchPoint, outer_sample_radius, nn_indices, nn_dists);
00743 
00744           if ((num_in_radius > 0) && (shell.hasClearance(cloud, nn_indices, maxHandAperture, this->handle_gap)))
00745             shells.push_back(shell);
00746 
00747           tcleartotal += omp_get_wtime() - tclear0;
00748         }
00749         else
00750           shells.push_back(shell);
00751       }
00752     }
00753   }
00754 
00755   if (is_logging)
00756   {
00757     printf(" elapsed time: %.3f sec\n", omp_get_wtime() - begin_time);
00758     printf(" cylinders left after radius filtering: %i\n", cylinders_left_radius);
00759     if (this->use_clearance_filter)
00760       printf(" cylinders left after clearance filtering: %i\n", (int)shells.size());
00761     printf("  cylinder/circle fitting: %.3f sec\n", tcyltotal);
00762     printf("  shell search: %.3f sec\n", tcleartotal);
00763   }
00764 
00765   return shells;
00766 }
00767 
00768 std::vector<int> Affordances::createRandomIndices(const PointCloud::Ptr &cloud, int size)
00769 {
00770   std::vector<int> indices(size);
00771 
00772   for (int i = 0; i < size; i++)
00773   {
00774     int r = std::rand() % cloud->points.size();
00775     while (!pcl::isFinite((*cloud)[r])
00776         || !this->isPointInWorkspace(cloud->points[r].x, cloud->points[r].y, cloud->points[r].z))
00777       r = std::rand() % cloud->points.size();
00778     indices[i] = r;
00779   }
00780 
00781   return indices;
00782 }
00783 
00784 void Affordances::findBestColinearSet(const std::vector<CylindricalShell> &list, std::vector<int> &inliersMaxSet,
00785                                       std::vector<int> &outliersMaxSet)
00786 {
00787   int maxInliers = 0;
00788   double orientRadius2 = this->alignment_orient_radius * this->alignment_orient_radius;
00789   double distRadius2 = this->alignment_dist_radius * this->alignment_dist_radius;
00790 
00791   for (std::size_t i = 0; i < list.size(); i++)
00792   {
00793     Eigen::Vector3d axis = list[i].getCurvatureAxis();
00794     Eigen::Vector3d centroid = list[i].getCentroid();
00795     double radius = list[i].getRadius();
00796     std::vector<int> inliers, outliers;
00797 
00798     for (std::size_t j = 0; j < list.size(); j++)
00799     {
00800       Eigen::Vector3d distToOrientVec = (Eigen::MatrixXd::Identity(3, 3) - axis * axis.transpose())
00801           * list[j].getCurvatureAxis();
00802       double distToOrient = distToOrientVec.cwiseProduct(distToOrientVec).sum();
00803       Eigen::Vector3d distToAxisVec = (Eigen::MatrixXd::Identity(3, 3) - axis * axis.transpose())
00804           * (list[j].getCentroid() - centroid);
00805       double distToAxis = distToAxisVec.cwiseProduct(distToAxisVec).sum();
00806       double distToRadius = fabs(list[j].getRadius() - radius);
00807 
00808       if (distToOrient < orientRadius2 && distToAxis < distRadius2 && distToRadius < this->alignment_radius_radius)
00809         inliers.push_back(j);
00810       else
00811         outliers.push_back(j);
00812     }
00813 
00814     if (inliers.size() > maxInliers)
00815     {
00816       maxInliers = inliers.size();
00817       inliersMaxSet = inliers;
00818       outliersMaxSet = outliers;
00819     }
00820   }
00821 }


handle_detector
Author(s):
autogenerated on Fri Aug 28 2015 10:59:15