-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported noether_filtering subset extraction to noether_tpp
- Loading branch information
Showing
5 changed files
with
395 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
.../include/noether_tpp/mesh_modifiers/subset_extraction/extruded_polygon_subset_extractor.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright 2022 Southwest Research Institute | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#pragma once | ||
|
||
#include <noether_tpp/mesh_modifiers/subset_extraction/subset_extractor.h> | ||
|
||
namespace noether | ||
{ | ||
/** | ||
* @brief Extracts the submesh with the vertices captured inside an extruded polygon defined by the input boundary | ||
* points | ||
* @details Operations | ||
* - Fit plane to boundary points | ||
* - Project boundary points onto fitted plane | ||
* - Extrude a prism defined by the projected boundary to the extents of the mesh in the direction of the fitted plane | ||
* normal | ||
* - Identify the vertices within the extruded prism | ||
* - Cluster the inlier vertices | ||
* - Extract the single cluster of vertices whose centroid is closest to the centroid of the boundary | ||
*/ | ||
struct ExtrudedPolygonSubsetExtractor : SubsetExtractor | ||
{ | ||
struct Params | ||
{ | ||
/** @brief Distance (m) above which boundary points will not be included as inliers to the plane fit */ | ||
double plane_distance_threshold = 1.0; | ||
/** @brief Minimum number of vertices in a cluster */ | ||
int min_cluster_size = 100; | ||
/** @brief Maximum number of vertices in a cluster*/ | ||
int max_cluster_size = std::numeric_limits<int>::max(); | ||
/** @brief Minimum distance (m) between clusters */ | ||
double cluster_tolerance = 0.15; | ||
}; | ||
|
||
std::vector<int> extract(const pcl::PCLPointCloud2& cloud, const Eigen::MatrixX3d& boundary) const override; | ||
|
||
Params params; | ||
}; | ||
|
||
/** | ||
* @brief Extracts the submesh with the vertices captured inside an extruded polygon defined by the input boundary | ||
* points | ||
*/ | ||
struct ExtrudedPolygonSubMeshExtractor : SubMeshExtractor | ||
{ | ||
pcl::PolygonMesh extract(const pcl::PolygonMesh& mesh, const Eigen::MatrixX3d& boundary) const override; | ||
ExtrudedPolygonSubsetExtractor extractor; | ||
}; | ||
|
||
} // namespace noether |
57 changes: 57 additions & 0 deletions
57
noether_tpp/include/noether_tpp/mesh_modifiers/subset_extraction/subset_extractor.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright 2022 Southwest Research Institute | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#pragma once | ||
|
||
#include <Eigen/Dense> | ||
#include <pcl/PolygonMesh.h> | ||
|
||
namespace noether | ||
{ | ||
/** | ||
* @brief Base class for extracting a point cloud subset from an input given a set of boundary points | ||
*/ | ||
struct SubsetExtractor | ||
{ | ||
/** | ||
* @brief Extracts the inlying subset of points from a cloud given a set of boundary points | ||
* @param cloud | ||
* @param boundary - ordered 3D points (row-wise) defining a closed boundary polygon | ||
* @return | ||
*/ | ||
virtual std::vector<int> extract(const pcl::PCLPointCloud2& cloud, const Eigen::MatrixX3d& boundary) const = 0; | ||
}; | ||
|
||
/** | ||
* @brief Base class for extracting a submesh from a mesh given a set of boundary points | ||
*/ | ||
struct SubMeshExtractor | ||
{ | ||
/** | ||
* @brief Extracts a submesh from a mesh given a set of boundary points | ||
* @param mesh | ||
* @param boundary - ordered 3D points (row-wise) defining a closed boundary polygon | ||
* @return | ||
*/ | ||
virtual pcl::PolygonMesh extract(const pcl::PolygonMesh& mesh, const Eigen::MatrixX3d& boundary) const = 0; | ||
}; | ||
|
||
/** | ||
* @brief Extracts the sub-mesh from inlier vertices | ||
*/ | ||
pcl::PolygonMesh extractSubMeshFromInlierVertices(const pcl::PolygonMesh& input_mesh, | ||
const std::vector<int>& inlier_vertex_indices); | ||
|
||
} // namespace noether |
217 changes: 217 additions & 0 deletions
217
noether_tpp/src/mesh_modifiers/subset_extraction/extruded_polygon_subset_extractor.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* | ||
* Copyright 2018 Southwest Research Institute | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
#include <noether_tpp/mesh_modifiers/subset_extraction/extruded_polygon_subset_extractor.h> | ||
|
||
#include <pcl/common/common.h> | ||
#include <pcl/segmentation/extract_polygonal_prism_data.h> | ||
#include <pcl/segmentation/extract_clusters.h> | ||
#include <pcl/ModelCoefficients.h> | ||
#include <pcl/filters/extract_indices.h> | ||
#include <pcl/segmentation/sac_segmentation.h> | ||
#include <pcl/search/kdtree.h> | ||
#include <pcl/features/normal_3d.h> | ||
#include <pcl/pcl_config.h> | ||
|
||
namespace | ||
{ | ||
#if PCL_VERSION_COMPARE(<, 1, 10, 0) | ||
template <typename T> | ||
using shared_ptr = std::shared_ptr<T>; | ||
|
||
template <typename T> | ||
auto make_shared = std::make_shared<T>; | ||
#else | ||
template <typename T> | ||
using shared_ptr = pcl::shared_ptr<T>; | ||
|
||
template <typename T> | ||
auto make_shared = pcl::make_shared<T>; | ||
#endif | ||
|
||
/** @brief Creates a shared pointer with no-op destructor for various PCL methods that require a pointer */ | ||
template <typename T> | ||
shared_ptr<const T> createLocalPointer(const T& obj) | ||
{ | ||
return shared_ptr<const T>(&obj, [](const T*) {}); | ||
} | ||
|
||
bool clusterComparator(const pcl::PointIndices& a, | ||
const pcl::PointIndices& b, | ||
pcl::PointCloud<pcl::PointXYZ>::ConstPtr cloud, | ||
const Eigen::Vector3f& boundary_centroid) | ||
{ | ||
// Add cluster indices to vector as shared pointers with empty destructors | ||
std::vector<pcl::PointIndices::ConstPtr> cluster_indices{ createLocalPointer(a), createLocalPointer(b) }; | ||
|
||
pcl::ExtractIndices<pcl::PointXYZ> extract; | ||
extract.setInputCloud(cloud); | ||
|
||
// Find the distance between the selection polygon origin and the centroid of each cluster | ||
std::vector<float> dist; | ||
for (auto indices : cluster_indices) | ||
{ | ||
extract.setIndices(indices); | ||
pcl::PointCloud<pcl::PointXYZ> index_cloud; | ||
extract.filter(index_cloud); | ||
|
||
float avg_dist = | ||
(index_cloud.getMatrixXfMap(3, 4, 0).rowwise() - boundary_centroid.transpose()).colwise().mean().norm(); | ||
dist.push_back(avg_dist); | ||
} | ||
|
||
return dist.front() < dist.back(); | ||
} | ||
|
||
Eigen::Hyperplane<double, 3> fitPlaneToPoints(const Eigen::MatrixX3d& points, const double plane_distance_threshold) | ||
{ | ||
// Create a point cloud from the selection points | ||
pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(new pcl::PointCloud<pcl::PointXYZ>()); | ||
input_cloud->resize(points.rows()); | ||
input_cloud->getMatrixXfMap(3, 4, 0) = points.cast<float>().transpose(); | ||
|
||
// Set segmentation parameters | ||
pcl::SACSegmentation<pcl::PointXYZ> seg; | ||
seg.setOptimizeCoefficients(true); | ||
seg.setModelType(pcl::SACMODEL_PLANE); | ||
seg.setMethodType(pcl::SAC_RANSAC); | ||
seg.setDistanceThreshold(plane_distance_threshold); | ||
seg.setInputCloud(input_cloud); | ||
|
||
// Extract best fit cylinder inliers and calculate cylinder coefficients | ||
auto plane_coefficients = make_shared<pcl::ModelCoefficients>(); | ||
pcl::PointIndices::Ptr plane_inliers(new pcl::PointIndices); | ||
seg.segment(*plane_inliers, *plane_coefficients); | ||
|
||
if (plane_inliers->indices.size() == 0) | ||
throw std::runtime_error("Unable to fit a plane to the data"); | ||
|
||
Eigen::Hyperplane<float, 3> plane; | ||
plane.coeffs() = Eigen::Map<Eigen::Vector4f>(plane_coefficients->values.data()); | ||
|
||
return plane.cast<double>(); | ||
} | ||
|
||
std::vector<int> getSubMeshIndices(const pcl::PointCloud<pcl::PointXYZ>::Ptr vertices, | ||
const Eigen::MatrixX3d& projected_boundary, | ||
const noether::ExtrudedPolygonSubsetExtractor::Params& params) | ||
{ | ||
// Calculate the diagonal of search_points_ cloud | ||
pcl::PointXYZ min_pt, max_pt; | ||
pcl::getMinMax3D(*vertices, min_pt, max_pt); | ||
float half_dist = (max_pt.getVector3fMap() - min_pt.getVector3fMap()).norm() / 2.0; | ||
|
||
// Create a point cloud from the projected boundary | ||
auto projected_boundary_cloud = make_shared<pcl::PointCloud<pcl::PointXYZ>>(); | ||
projected_boundary_cloud->resize(projected_boundary.rows()); | ||
projected_boundary_cloud->getMatrixXfMap(3, 4, 0) = projected_boundary.cast<float>().transpose(); | ||
|
||
// Extrude the convex hull by half the max distance | ||
pcl::ExtractPolygonalPrismData<pcl::PointXYZ> prism; | ||
pcl::PointIndicesPtr selection_indices(new pcl::PointIndices{}); | ||
prism.setInputCloud(vertices); | ||
prism.setInputPlanarHull(projected_boundary_cloud); | ||
prism.setHeightLimits(-half_dist, half_dist); | ||
prism.segment(*selection_indices); | ||
|
||
if (selection_indices->indices.empty()) | ||
throw std::runtime_error("No points found within polygon boundary"); | ||
|
||
// Pull out the points that are inside the user selected prism | ||
auto prism_indices = make_shared<pcl::PointIndices>(); | ||
pcl::ExtractIndices<pcl::PointXYZ> extract; | ||
extract.setInputCloud(vertices); | ||
extract.setIndices(selection_indices); | ||
extract.setNegative(false); | ||
extract.filter(prism_indices->indices); | ||
|
||
// Extract the clusters within the boundary | ||
std::vector<pcl::PointIndices> cluster_indices; | ||
{ | ||
auto tree = make_shared<pcl::search::KdTree<pcl::PointXYZ>>(); | ||
|
||
pcl::EuclideanClusterExtraction<pcl::PointXYZ> cluster; | ||
cluster.setInputCloud(vertices); | ||
cluster.setIndices(prism_indices); | ||
cluster.setMinClusterSize(params.min_cluster_size); | ||
cluster.setMaxClusterSize(params.max_cluster_size); | ||
cluster.setClusterTolerance(params.cluster_tolerance); | ||
|
||
cluster.extract(cluster_indices); | ||
} | ||
|
||
// Find the cluster closest to the centroid of the selection polygon | ||
Eigen::Vector3d boundary_centroid = projected_boundary.colwise().mean(); | ||
|
||
// Find the closest cluster | ||
auto closest_cluster = std::min_element( | ||
cluster_indices.begin(), | ||
cluster_indices.end(), | ||
std::bind( | ||
&clusterComparator, std::placeholders::_1, std::placeholders::_2, vertices, boundary_centroid.cast<float>())); | ||
|
||
if (closest_cluster == cluster_indices.end()) | ||
throw std::runtime_error("No clusters found"); | ||
|
||
return closest_cluster->indices; | ||
} | ||
|
||
} // namespace | ||
|
||
namespace noether | ||
{ | ||
std::vector<int> ExtrudedPolygonSubsetExtractor::extract(const pcl::PCLPointCloud2& cloud, | ||
const Eigen::MatrixX3d& boundary) const | ||
{ | ||
// Check size of selection points vector | ||
if (boundary.rows() < 3) | ||
throw std::runtime_error("At least three points required to create a closed loop"); | ||
|
||
// Convert to point cloud class | ||
auto vertices = make_shared<pcl::PointCloud<pcl::PointXYZ>>(); | ||
pcl::fromPCLPointCloud2(cloud, *vertices); | ||
|
||
// Check the size of the input point cloud | ||
if (vertices->points.size() == 0) | ||
throw std::runtime_error("No points to search for inside the selection polygon"); | ||
|
||
// Take boundary points and fit a plane to them using RANSAC | ||
Eigen::Hyperplane<double, 3> plane = fitPlaneToPoints(boundary, params.plane_distance_threshold); | ||
|
||
// Project the selection points onto the fitted plane | ||
// Note: Eigen does not appear to support a vectorized form of the `projection` method so a for loop is required | ||
Eigen::MatrixX3d projected_boundary(boundary.rows(), 3); | ||
for (Eigen::Index i = 0; i < boundary.rows(); ++i) | ||
{ | ||
projected_boundary.row(i) = plane.projection(boundary.row(i)); | ||
} | ||
|
||
/* Find all of the sensor data points inside a volume whose perimeter is defined by the projected selection points | ||
* and whose depth is defined by the largest length of the bounding box of the input point cloud */ | ||
std::vector<int> subset_vertex_indices = getSubMeshIndices(vertices, projected_boundary, params); | ||
if (subset_vertex_indices.empty()) | ||
throw std::runtime_error("Unable to identify points in the region of interest"); | ||
|
||
return subset_vertex_indices; | ||
} | ||
|
||
pcl::PolygonMesh ExtrudedPolygonSubMeshExtractor::extract(const pcl::PolygonMesh& mesh, | ||
const Eigen::MatrixX3d& boundary) const | ||
{ | ||
std::vector<int> subset_indices = extractor.extract(mesh.cloud, boundary); | ||
return extractSubMeshFromInlierVertices(mesh, subset_indices); | ||
} | ||
|
||
} // namespace noether |
Oops, something went wrong.