shape_extraction.cpp
Go to the documentation of this file.
00001 #include "surface_perception/shape_extraction.h"
00002 
00003 #include <limits.h>
00004 
00005 #include "Eigen/Eigen"
00006 #include "geometry_msgs/Pose.h"
00007 #include "geometry_msgs/Vector3.h"
00008 #include "pcl/ModelCoefficients.h"
00009 #include "pcl/filters/project_inliers.h"
00010 #include "pcl/point_cloud.h"
00011 #include "pcl/point_types.h"
00012 #include "pcl/surface/convex_hull.h"
00013 #include "ros/ros.h"
00014 
00015 #include "pcl/features/normal_3d.h"
00016 #include "surface_perception/surface.h"
00017 #include "surface_perception/typedefs.h"
00018 
00019 namespace {
00024 bool hasHullOnXYPlane(const pcl::ModelCoefficients::Ptr& model,
00025                       const PointCloudC::Ptr& flat_projected) {
00026   Eigen::Vector3d x_axis_(1.0, 0.0, 0.0);
00027   Eigen::Vector3d y_axis_(0.0, 1.0, 0.0);
00028   Eigen::Vector3d z_axis_(0.0, 0.0, 1.0);
00029 
00030   std::vector<int>* indices_ = new std::vector<int>();
00031   for (size_t i = 0; i < flat_projected->points.size(); i++) {
00032     indices_->push_back(i);
00033   }
00034   double projection_angle_thresh_ = cos(0.174532925);
00035   int dimension = 2;
00036   bool xy_proj_safe = true;
00037   bool yz_proj_safe = true;
00038   bool xz_proj_safe = true;
00039 
00040   // Check the input's normal to see which projection to use
00041   PointC p0 = flat_projected->points[(*indices_)[0]];
00042   PointC p1 = flat_projected->points[(*indices_)[indices_->size() - 1]];
00043   PointC p2 = flat_projected->points[(*indices_)[indices_->size() / 2]];
00044 
00045   Eigen::Array4f dy1dy2 = (p1.getArray4fMap() - p0.getArray4fMap()) /
00046                           (p2.getArray4fMap() - p0.getArray4fMap());
00047   while (!((dy1dy2[0] != dy1dy2[1]) || (dy1dy2[2] != dy1dy2[1]))) {
00048     p0 = flat_projected->points[(*indices_)[rand() % indices_->size()]];
00049     p1 = flat_projected->points[(*indices_)[rand() % indices_->size()]];
00050     p2 = flat_projected->points[(*indices_)[rand() % indices_->size()]];
00051     dy1dy2 = (p1.getArray4fMap() - p0.getArray4fMap()) /
00052              (p2.getArray4fMap() - p0.getArray4fMap());
00053   }
00054 
00055   pcl::PointCloud<PointC> normal_calc_cloud;
00056   normal_calc_cloud.points.resize(3);
00057   normal_calc_cloud.points[0] = p0;
00058   normal_calc_cloud.points[1] = p1;
00059   normal_calc_cloud.points[2] = p2;
00060 
00061   Eigen::Vector4d normal_calc_centroid;
00062   Eigen::Matrix3d normal_calc_covariance;
00063   pcl::computeMeanAndCovarianceMatrix(normal_calc_cloud, normal_calc_covariance,
00064                                       normal_calc_centroid);
00065   // Need to set -1 here. See eigen33 for explanations.
00066   Eigen::Vector3d::Scalar eigen_value;
00067   Eigen::Vector3d plane_params;
00068   pcl::eigen33(normal_calc_covariance, eigen_value, plane_params);
00069   float theta_x = fabsf(static_cast<float>(plane_params.dot(x_axis_)));
00070   float theta_y = fabsf(static_cast<float>(plane_params.dot(y_axis_)));
00071   float theta_z = fabsf(static_cast<float>(plane_params.dot(z_axis_)));
00072 
00073   std::vector<PointC> points;
00074   points.push_back(p0);
00075   points.push_back(p1);
00076   points.push_back(p2);
00077 
00078   // Check for degenerate cases of each projection
00079   // We must avoid projections in which the plane projects as a line
00080   if (theta_z > projection_angle_thresh_) {
00081     xz_proj_safe = false;
00082     yz_proj_safe = false;
00083   }
00084   if (theta_x > projection_angle_thresh_) {
00085     xz_proj_safe = false;
00086     xy_proj_safe = false;
00087   }
00088   if (theta_y > projection_angle_thresh_) {
00089     xy_proj_safe = false;
00090     yz_proj_safe = false;
00091   }
00092 
00093   if (!xy_proj_safe) {
00094     ROS_WARN(
00095         "Warning: could not use the following three points to calculate convex "
00096         "hull!");
00097     ROS_WARN("P0 (%f, %f, %f)", p0.x, p0.y, p0.z);
00098     ROS_WARN("P1 (%f, %f, %f)", p1.x, p1.y, p1.z);
00099     ROS_WARN("P2 (%f, %f, %f)", p2.x, p2.y, p2.z);
00100 
00101     return false;
00102   }
00103 
00104   return true;
00105 }
00106 }  // namespace
00107 
00108 namespace surface_perception {
00109 bool FitBox(const PointCloudC::Ptr& input,
00110             const pcl::PointIndices::Ptr& indices,
00111             const pcl::ModelCoefficients::Ptr& model, geometry_msgs::Pose* pose,
00112             geometry_msgs::Vector3* dimensions) {
00113   // The minimum volume shape found thus far.
00114   double min_volume = std::numeric_limits<double>::max();
00115   Eigen::Matrix3f transformation;  // the transformation for the best-fit shape
00116 
00117   // Compute z height as maximum distance from planes
00118   double height = 0.0;
00119   for (size_t i = 0; i < indices->indices.size(); ++i) {
00120     int index = indices->indices[i];
00121     const PointC& pt = input->at(index);
00122     const Eigen::Vector4f pp(pt.x, pt.y, pt.z, 1);
00123     Eigen::Vector4f m(model->values[0], model->values[1], model->values[2],
00124                       model->values[3]);
00125     double distance_to_plane = fabs(pp.dot(m));
00126     if (distance_to_plane > height) {
00127       height = distance_to_plane;
00128     }
00129   }
00130 
00131   // Project object into 2d, using plane model coefficients
00132   pcl::PointCloud<pcl::PointXYZRGB>::Ptr flat(
00133       new pcl::PointCloud<pcl::PointXYZRGB>);
00134   pcl::ProjectInliers<pcl::PointXYZRGB> projection;
00135   projection.setModelType(pcl::SACMODEL_PLANE);
00136   projection.setInputCloud(input);
00137   if (indices && indices->indices.size() > 0) {
00138     projection.setIndices(indices);
00139   }
00140   projection.setModelCoefficients(model);
00141   projection.filter(*flat);
00142 
00143   // Rotate plane so that Z=0
00144   pcl::PointCloud<pcl::PointXYZRGB>::Ptr flat_projected(
00145       new pcl::PointCloud<pcl::PointXYZRGB>);
00146   Eigen::Vector3f normal(model->values[0], model->values[1], model->values[2]);
00147   Eigen::Quaternionf qz;
00148   qz.setFromTwoVectors(normal, Eigen::Vector3f::UnitZ());
00149   Eigen::Matrix3f plane_rotation = qz.toRotationMatrix();
00150   Eigen::Matrix3f inv_plane_rotation = plane_rotation.inverse();
00151 
00152   for (size_t i = 0; i < flat->size(); ++i) {
00153     pcl::PointXYZRGB p;
00154     p.getVector3fMap() = plane_rotation * (*flat)[i].getVector3fMap();
00155     flat_projected->push_back(p);
00156   }
00157 
00158   // Find the convex hull
00159   pcl::PointCloud<pcl::PointXYZRGB> hull;
00160   pcl::ConvexHull<pcl::PointXYZRGB> convex_hull;
00161 
00162   // Check if ConvexHull works for the given object
00163   if (hasHullOnXYPlane(model, flat_projected)) {
00164     convex_hull.setInputCloud(flat_projected);
00165     convex_hull.setDimension(2);
00166     convex_hull.reconstruct(hull);
00167   } else {
00168     return false;
00169   }
00170 
00171   // Record the best dimensions
00172   double best_x_dim = 0.0;
00173   double best_y_dim = 0.0;
00174 
00175   // Try fitting a rectangle
00176   for (size_t i = 0; i + 1 < hull.size(); ++i) {
00177     // For each pair of hull points, determine the angle
00178     double rise = hull[i + 1].y - hull[i].y;
00179     double run = hull[i + 1].x - hull[i].x;
00180     // and normalize..
00181     {
00182       double l = sqrt((rise * rise) + (run * run));
00183       rise = rise / l;
00184       run = run / l;
00185     }
00186 
00187     // Build rotation matrix from change of basis
00188     Eigen::Matrix3f rotation;
00189     rotation(0, 0) = run;
00190     rotation(0, 1) = rise;
00191     rotation(0, 2) = 0.0;
00192     rotation(1, 0) = -rise;
00193     rotation(1, 1) = run;
00194     rotation(1, 2) = 0.0;
00195     rotation(2, 0) = 0.0;
00196     rotation(2, 1) = 0.0;
00197     rotation(2, 2) = 1.0;
00198     Eigen::Matrix3f inv_rotation = rotation.inverse();
00199 
00200     // Project hull to new coordinate system
00201     pcl::PointCloud<pcl::PointXYZRGB> projected_cloud;
00202     for (size_t j = 0; j < hull.size(); ++j) {
00203       pcl::PointXYZRGB p;
00204       p.getVector3fMap() = rotation * hull[j].getVector3fMap();
00205       projected_cloud.push_back(p);
00206     }
00207 
00208     // Compute min/max
00209     double x_min = std::numeric_limits<double>::max();
00210     double x_max = -std::numeric_limits<double>::max();
00211     double y_min = std::numeric_limits<double>::max();
00212     double y_max = -std::numeric_limits<double>::max();
00213     for (size_t j = 0; j < projected_cloud.size(); ++j) {
00214       if (projected_cloud[j].x < x_min) x_min = projected_cloud[j].x;
00215       if (projected_cloud[j].x > x_max) x_max = projected_cloud[j].x;
00216 
00217       if (projected_cloud[j].y < y_min) y_min = projected_cloud[j].y;
00218       if (projected_cloud[j].y > y_max) y_max = projected_cloud[j].y;
00219     }
00220 
00221     // Is this the best estimate?
00222     double area = (x_max - x_min) * (y_max - y_min);
00223 
00224     if (area * height < min_volume) {
00225       transformation = inv_plane_rotation * inv_rotation;
00226 
00227       Eigen::Vector3f pose3f((x_max + x_min) / 2.0, (y_max + y_min) / 2.0,
00228                              projected_cloud[0].z + height / 2.0);
00229       pose3f = transformation * pose3f;
00230       pose->position.x = pose3f(0);
00231       pose->position.y = pose3f(1);
00232       pose->position.z = pose3f(2);
00233 
00234       best_x_dim = x_max - x_min;
00235       best_y_dim = y_max - y_min;
00236 
00237       min_volume = area * height;
00238     }
00239   }
00240 
00241   Eigen::Matrix3f adjusted_transformation =
00242       StandardizeBoxOrientation(transformation, best_x_dim, best_y_dim,
00243                                 &(dimensions->x), &(dimensions->y));
00244 
00245   dimensions->z = height;
00246 
00247   Eigen::Quaternionf q(adjusted_transformation);
00248   pose->orientation.x = q.x();
00249   pose->orientation.y = q.y();
00250   pose->orientation.z = q.z();
00251   pose->orientation.w = q.w();
00252 
00253   return true;
00254 }
00255 
00256 bool FitBoxOnSurface(const pcl::PointCloud<pcl::PointXYZRGB>::Ptr& input,
00257                      const pcl::PointIndicesPtr& indices,
00258                      const Surface& surface, geometry_msgs::Pose* pose,
00259                      geometry_msgs::Vector3* dimensions) {
00260   bool success = FitBox(input, indices, surface.coefficients, pose, dimensions);
00261   if (!success) {
00262     return false;
00263   }
00264 
00265   // The box intersects with the surface. We adjust its dimensions and pose so
00266   // that it is resting on the surface.
00267   dimensions->z -= surface.dimensions.z / 2;
00268 
00269   Eigen::Quaternionf q;
00270   q.w() = pose->orientation.w;
00271   q.x() = pose->orientation.x;
00272   q.y() = pose->orientation.y;
00273   q.z() = pose->orientation.z;
00274   Eigen::Matrix3f mat(q);
00275   Eigen::Vector3f up = mat.col(2) * surface.dimensions.z / 4;
00276   pose->position.x += up.x();
00277   pose->position.y += up.y();
00278   pose->position.z += up.z();
00279 
00280   return true;
00281 }
00282 
00283 Eigen::Matrix3f StandardizeBoxOrientation(
00284     const Eigen::Matrix3f& rotation_matrix, double x_dim, double y_dim,
00285     double* updated_x_dim, double* updated_y_dim) {
00286   // The matrix to be outputed
00287   Eigen::Matrix3f output_matrix;
00288 
00289   // Flip orientation if necessary to force x dimension < y dimension
00290   if (x_dim > y_dim) {
00291     Eigen::Vector3f y_axis = rotation_matrix.col(1);
00292     // There are two choices for the new x axis. This chooses the one that is
00293     // closer to the positive x direction of the data.
00294     if (y_axis.x() < 0) {
00295       y_axis = -1 * rotation_matrix.col(1);
00296     }
00297     output_matrix.col(0) = y_axis;
00298     output_matrix.col(1) = rotation_matrix.col(2).cross(y_axis);
00299   } else {
00300     output_matrix.col(0) = rotation_matrix.col(0);
00301     output_matrix.col(1) = rotation_matrix.col(1);
00302   }
00303   output_matrix.col(2) = rotation_matrix.col(2);
00304 
00305   // Update the dimensions
00306   if (x_dim > y_dim) {
00307     *updated_x_dim = y_dim;
00308     *updated_y_dim = x_dim;
00309   } else {
00310     *updated_x_dim = x_dim;
00311     *updated_y_dim = y_dim;
00312   }
00313 
00314   // Check if the object is facing towards or perpendicular to the positive
00315   // x-axis. If not, the angle theta between x basis vector and x axis should be
00316   // 90 < theta <= 180, which means the result of dot product of the two vectors
00317   // would be negative.
00318   Eigen::Vector3f x_axis(1.0, 0.0, 0.0);
00319   if (output_matrix.col(0).dot(x_axis) < 0.0) {
00320     output_matrix.col(0) = output_matrix.col(0) * -1.0;
00321 
00322     output_matrix.col(1) = output_matrix.col(2).cross(output_matrix.col(0));
00323   }
00324 
00325   return output_matrix;
00326 }
00327 }  // namespace surface_perception


surface_perception
Author(s): Yu-Tang Peng
autogenerated on Thu Jun 6 2019 17:36:21