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
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
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
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
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
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));
00274
00275 for (int i = 0; i < this->num_samples; i++)
00276 {
00277
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
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));
00302
00303 for (int i = 0; i < this->num_samples; i++)
00304 {
00305
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
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
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);
00345
00346
00347 pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00348 new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00349
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
00359 CylindricalShell shell;
00360 shell.fitCylinder(cloud, neighborhoods[i], normals[i], curvature_axes[i]);
00361
00362
00363 shell.setExtent(2.0 * this->target_radius);
00364
00365
00366 shell.setNeighborhoodCentroidIndex(neighborhood_centroids[i]);
00367
00368
00369 if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00370 {
00371
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
00409 pcl::CurvatureEstimationTaubin<pcl::PointXYZ, pcl::PointCurvatureTaubin> estimator;
00410
00411
00412 estimator.setInputCloud(cloud);
00413
00414
00415 estimator.setRadiusSearch(this->NEIGHBOR_RADIUS);
00416
00417
00418 estimator.setNumSamples(this->num_samples);
00419
00420
00421 std::vector<int> indices(this->num_samples);
00422 std::srand(std::time(0));
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())
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
00447 estimator.setNumThreads(this->num_threads);
00448
00449
00450 pcl::PointCloud<pcl::PointCurvatureTaubin>::Ptr cloud_curvature(new pcl::PointCloud<pcl::PointCurvatureTaubin>);
00451
00452
00453 estimator.compute(*cloud_curvature);
00454
00455 printf(" elapsed time: %.3f sec\n", omp_get_wtime() - beginTime);
00456
00457
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);
00477
00478
00479 pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00480 new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00481
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
00491 double radius = 1.0 / fabs(cloud_curvature->points[i].median_curvature);
00492
00493
00494 if (radius > min_radius_osculating_sphere && radius < max_radius_osculating_sphere)
00495 {
00496
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
00503 shell.setExtent(2.0 * this->target_radius);
00504
00505
00506 shell.setNeighborhoodCentroidIndex(estimator.getNeighborhoodCentroids()[i]);
00507
00508
00509 if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00510 {
00511 cylinders_left_radius++;
00512
00513
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
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
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
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
00575 if (this->use_occlusion_filter)
00576 {
00577 int MAX_NUM_OCCLUDED = (int)handle.size() * 0.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
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
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
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
00649 estimator.setNumThreads(this->num_threads);
00650
00651
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
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);
00680
00681
00682 pcl::search::OrganizedNeighbor<pcl::PointXYZ>::Ptr organized_neighbor(
00683 new pcl::search::OrganizedNeighbor<pcl::PointXYZ>());
00684
00685 pcl::KdTreeFLANN<pcl::PointXYZ> tree;
00686 if (cloud->isOrganized())
00687 organized_neighbor->setInputCloud(cloud);
00688 else
00689 tree.setInputCloud(cloud);
00690
00691
00692
00693
00694 for (int i = 0; i < cloud_curvature->size(); i++)
00695 {
00696 if (isnan(cloud_curvature->points[i].normal[0]))
00697 continue;
00698
00699
00700 double radius = 1.0 / fabs(cloud_curvature->points[i].median_curvature);
00701
00702
00703
00704 if (radius > min_radius_osculating_sphere && radius < max_radius_osculating_sphere)
00705 {
00706
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
00715
00716
00717 shell.setExtent(2.0 * this->target_radius);
00718
00719
00720 shell.setNeighborhoodCentroidIndex(estimator.getNeighborhoodCentroids()[i]);
00721
00722
00723 if (shell.getRadius() > min_radius_cylinder && shell.getRadius() < max_radius_cylinder)
00724 {
00725 cylinders_left_radius++;
00726
00727
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 }