18#include <nanoflann.hpp>
19#include <unordered_map>
33template<
typename Po
intT>
49 if (dim == 0) {
return cloud.points[idx].x;}
else if (dim == 1) {
50 return cloud.points[idx].y;
51 }
else {
return cloud.points[idx].z;}
78template<
typename Po
intT>
85 typedef std::unordered_map<Index3D, CellType, Index3D::HashFunction>
GridCellsType;
90 typename pcl::PointCloud<PointT>::Ptr input,
91 const Eigen::Quaterniond & R_body2World);
108 std::pair<typename pcl::PointCloud<PointT>::Ptr,
112 typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double,
132 void addPoint(
const PointT & point);
159 double computeSlope(
const Eigen::Hyperplane<
double,
int(3)> & plane)
const;
189 typename pcl::PointCloud<PointT>::Ptr cloud);
218 pcl::SACSegmentation<PointT>
seg;
220template<
typename Po
intT>
224 centroid_cloud.reset(
new pcl::PointCloud<PointT>());
225 grid_config = config;
227 if (grid_config.processing_phase == 2) {
228 neighbor_offsets = generateIndices(1);
230 neighbor_offsets = generateIndices(0);
233 ground_points.reset(
new pcl::PointCloud<PointT>());
234 non_ground_points.reset(
new pcl::PointCloud<PointT>());
235 ground_inliers.reset(
new pcl::PointCloud<PointT>());
236 non_ground_inliers.reset(
new pcl::PointCloud<PointT>());
238 seg.setOptimizeCoefficients(
true);
239 seg.setModelType(pcl::SACMODEL_PLANE);
240 seg.setMethodType(pcl::SAC_PROSAC);
241 seg.setMaxIterations(1000);
244template<
typename Po
intT>
247 std::vector<Index3D> idxs;
249 for (
int dx = -1; dx <= 1; ++dx) {
250 for (
int dy = -1; dy <= 1; ++dy) {
251 for (
int dz = -1; dz <= z_threshold; ++dz) {
252 if (dx == 0 && dy == 0 && dz == 0) {
266template<
typename Po
intT>
272 double best_d2 = std::numeric_limits<double>::infinity();
274 for (
const auto & off : neighbor_offsets) {
278 if (!checkIndex3DInGrid(nid)) {
282 const auto & ncell = gridCells.at(nid);
285 ncell.points->empty() ||
291 double dx = double(nid.
x - cid.
x);
292 double dy = double(nid.
y - cid.
y);
293 double dz = double(nid.
z - cid.
z);
295 double d2 = dx*dx + dy*dy + 0.25*dz*dz;
307template<
typename Po
intT>
312 Eigen::Vector3d n = gcell.
normal;
314 if (!std::isfinite(n.x()) || !std::isfinite(n.y()) ||
315 !std::isfinite(n.z()) || n.norm() < 1e-6)
327 Eigen::Vector3d x(
double(p.x),
double(p.y),
double(p.z));
330 double plane_dist = std::abs(n.dot(x - c));
331 if (plane_dist > grid_config.groundInlierThreshold)
337template<
typename Po
intT>
343template<
typename Po
intT>
346 ground_cells.clear();
347 non_ground_cells.clear();
348 centroid_cloud->clear();
349 centroid_indices.clear();
350 index_to_centroid_idx.clear();
353template<
typename Po
intT>
356 double cell_x = point.x / grid_config.cellSizeX;
357 double cell_y = point.y / grid_config.cellSizeY;
358 double cell_z = point.z / grid_config.cellSizeZ;
360 int x =
static_cast<int>(std::floor(cell_x));
361 int y =
static_cast<int>(std::floor(cell_y));
362 int z =
static_cast<int>(std::floor(cell_z));
364 CellType & cell = gridCells[{x, y, z}];
369 cell.
points->push_back(point);
372template<
typename Po
intT>
375 const Eigen::Vector3d zNormal(Eigen::Vector3d::UnitZ());
376 Eigen::Vector3d planeNormal = normal;
377 planeNormal = orientation * planeNormal;
378 planeNormal.normalize();
379 return acos(std::abs(planeNormal.dot(zNormal)));
382template<
typename Po
intT>
385 const Eigen::Vector3d zNormal(Eigen::Vector3d::UnitZ());
386 Eigen::Vector3d planeNormal = plane.normal();
387 planeNormal = orientation * planeNormal;
388 planeNormal.normalize();
389 return acos(std::abs(planeNormal.dot(zNormal)));
392template<
typename Po
intT>
395 typename pcl::PointCloud<PointT>::Ptr cloud)
397 if (cell.
points->empty() || cloud->empty()) {
return "Empty";}
399 PointT min_pt, max_pt;
400 pcl::getMinMax3D(*cell.
points, min_pt, max_pt);
402 double volume = (max_pt.x - min_pt.x) *
403 (max_pt.y - min_pt.y) *
404 (max_pt.z - min_pt.z);
406 if (volume <= 0.0) {
return "Degenerate";}
408 double sparsity = volume /
static_cast<double>(cloud->size());
410 if (sparsity < 0.001) {
411 return "Low sparsity";
412 }
else if (sparsity < 0.01) {
413 return "Medium sparsity";
415 return "High sparsity";
419template<
typename Po
intT>
422 if (cell.
points->empty()) {
return false;}
425 double sum_abs_proj = 0.0;
427 for (
const auto & pt : cell.
points->points) {
429 double proj = diff.dot(cell.
normal);
430 sum_abs_proj += std::abs(proj);
433 double mean_abs_proj = sum_abs_proj / cell.
points->size();
436 return mean_abs_proj < 0.1;
439template<
typename Po
intT>
443 const std::vector<Index3D> & idx)
446 std::vector<Index3D> neighbors;
453 for (uint i = 0; i < idx.size(); ++i) {
454 Index3D neighbor_id = cell_id + idx[i];
456 if (!checkIndex3DInGrid(neighbor_id)) {
467 neighbors.push_back(
id);
473template<
typename Po
intT>
477 pcl::ModelCoefficients::Ptr coefficients(
new pcl::ModelCoefficients);
478 pcl::PointIndices::Ptr inliers(
new pcl::PointIndices);
479 seg.setInputCloud(cell.
points);
480 seg.setDistanceThreshold(threshold);
481 seg.segment(*inliers, *coefficients);
483 if (cell.
inliers->indices.size() == 0) {
487 Eigen::Vector3d plane_normal(coefficients->values[0], coefficients->values[1],
488 coefficients->values[2]);
489 double distToOrigin = coefficients->values[3];
490 auto plane = Eigen::Hyperplane<double, 3>(plane_normal, distToOrigin);
491 cell.
slope = computeSlope(plane);
495template<
typename Po
intT>
499 if (gridCells.empty()) {
505 centroid_cloud->points.reserve(gridCells.size());
506 centroid_indices.reserve(gridCells.size());
507 ground_cells.reserve(gridCells.size());
508 non_ground_cells.reserve(gridCells.size());
510 for (
auto & cellPair : gridCells) {
514 if ((cell.
points->size() < 3)) {
continue;}
516 Index3D cell_id = cellPair.first;
519 if (cell.
points->size() <= 5) {
520 Eigen::Vector4f squared_diff_sum(0, 0, 0, 0);
522 for (
typename pcl::PointCloud<PointT>::iterator it = cell.
points->begin();
523 it != cell.
points->end(); ++it)
525 Eigen::Vector4f diff = (*it).getVector4fMap() - cell.
centroid.template cast<float>();
526 squared_diff_sum += diff.array().square().matrix();
529 Eigen::Vector4f variance = squared_diff_sum / cell.
points->size();
531 if (variance[0] < variance[2] && variance[1] < variance[2]) {
533 non_ground_cells.push_back(cell_id);
542 centroid_cloud->points.push_back(centroid3d);
543 centroid_indices.push_back(cell_id);
544 index_to_centroid_idx[cell_id] = centroid_cloud->size() - 1;
549 Eigen::Matrix3d covariance_matrix;
550 pcl::computeCovarianceMatrixNormalized(*cell.
points, cell.
centroid, covariance_matrix);
552 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver(covariance_matrix,
553 Eigen::ComputeEigenvectors);
578 double angle_rad = acos(std::abs(v.dot(Eigen::Vector3d::UnitZ())));
580 if (angle_rad > ((90 - grid_config.slopeThresholdDegrees) * (M_PI / 180))) {
588 centroid_cloud->points.push_back(centroid3d);
589 centroid_indices.push_back(cell_id);
590 index_to_centroid_idx[cell_id] = centroid_cloud->size() - 1;
593 non_ground_cells.push_back(cell_id);
596 }
else if (ratio > 0.4) {
598 if (std::abs(computeSlope(cell.
normal)) > (grid_config.slopeThresholdDegrees * (M_PI / 180.0))) {
600 non_ground_cells.push_back(cell_id);
606 non_ground_cells.push_back(cell_id);
610 if (!fitGroundPlane(cell, grid_config.groundInlierThreshold)) {
612 non_ground_cells.push_back(cell_id);
616 if (cell.
slope < (grid_config.slopeThresholdDegrees * (M_PI / 180)) ) {
624 centroid_cloud->points.push_back(centroid3d);
625 centroid_indices.push_back(cell_id);
626 index_to_centroid_idx[cell_id] = centroid_cloud->size() - 1;
629 non_ground_cells.push_back(cell_id);
633 std::queue<Index3D> q;
638 int z =
static_cast<int>(std::floor(grid_config.distToGround / grid_config.cellSizeZ));
639 best_robot_cell =
Index3D{0, 0, z};
655 centroid3d.z = grid_config.distToGround;
657 centroid_cloud->points.push_back(centroid3d);
658 centroid_indices.push_back(best_robot_cell);
659 index_to_centroid_idx[best_robot_cell] = centroid_cloud->size() - 1;
661 q.push(best_robot_cell);
665template<
typename Po
intT>
671#if NANOFLANN_VERSION >= 0x150
672 nanoflann::SearchParameters search_params;
674 nanoflann::SearchParams search_params;
677 search_params.eps = 0.0;
678 search_params.sorted =
false;
680 nanoflann::KDTreeSingleIndexAdaptorParams build_params(10);
681 KDTree tree(3, pclAdaptor, build_params);
684 const double radius = grid_config.centroidSearchRadius * grid_config.centroidSearchRadius;
693 if (current_cell.
expanded) {
continue;}
697 size_t curr_centroid_idx = index_to_centroid_idx[idx];
698 const PointT & curr_centroid = centroid_cloud->points.at(curr_centroid_idx);
701#if NANOFLANN_VERSION >= 0x150
702using Neighbor = nanoflann::ResultItem<size_t, double>;
704using Neighbor = std::pair<size_t, double>;
707 std::vector<Neighbor> neighbors;
710 double query_pt[3] = {
static_cast<double>(curr_centroid.x),
711 static_cast<double>(curr_centroid.y),
712 static_cast<double>(curr_centroid.z)};
714 tree.radiusSearch(query_pt, radius, neighbors, search_params);
716 for (
const auto & nb : neighbors) {
717 size_t ni = nb.first;
718 if (ni == curr_centroid_idx) {
722 Index3D neighbor_id = centroid_indices[ni];
723 if (neighbor_id == idx) {
730 if (grid_config.processing_phase == 2) {
734 std::abs(curr_centroid.z - neighbor.
centroid[2]);
736 if (dz > grid_config.maxGroundHeightDeviation)
745 ground_cells.emplace_back(idx);
749template<
typename Po
intT>
751 typename pcl::PointCloud<PointT>::Ptr input,
752 const Eigen::Quaterniond & R_body2World)
756 orientation = R_body2World;
757 for (
typename pcl::PointCloud<PointT>::iterator it = input->begin(); it != input->end(); ++it) {
762template<
typename Po
intT>
765 if (
auto search = gridCells.find(index); search != gridCells.end()) {
772template<
typename Po
intT>
773std::pair<typename pcl::PointCloud<PointT>::Ptr,
776 ground_points->clear();
777 non_ground_points->clear();
778 ground_inliers->clear();
779 non_ground_inliers->clear();
781 pcl::ExtractIndices<PointT> extract_ground;
784 for (
auto & cell_id : ground_cells) {
791 *ground_points += *cell.
points;
795 extract_ground.setInputCloud(cell.
points);
796 extract_ground.setIndices(cell.
inliers);
798 extract_ground.setNegative(
false);
799 extract_ground.filter(*ground_inliers);
801 extract_ground.setNegative(
true);
802 extract_ground.filter(*non_ground_inliers);
804 if (ground_inliers->size() == 0) {
808 auto score1 = classifySparsityBoundingBox(cell, ground_inliers);
809 auto score2 = classifySparsityBoundingBox(cell, non_ground_inliers);
811 if (score1 == score2) {
812 Eigen::Vector4d centroid;
813 pcl::compute3DCentroid(*(ground_inliers), centroid);
816 double cell_z = centroid[2];
817 std::vector<double> neighbor_zs;
818 for (
const auto & offset : neighbor_offsets) {
819 Index3D nidx = cell_id + offset;
820 if (!checkIndex3DInGrid(nidx)) {
continue;}
821 const auto & ncell = gridCells[nidx];
823 neighbor_zs.push_back(ncell.centroid[2]);
827 if (neighbor_zs.empty()) {
828 *non_ground_points += *cell.
points;
832 double local_ref = *std::min_element(neighbor_zs.begin(), neighbor_zs.end());
834 if (std::abs(cell_z - local_ref) > grid_config.maxGroundHeightDeviation) {
835 *non_ground_points += *cell.
points;
839 bool reject_as_floating =
false;
840 int bz = cell_id.z - 1;
842 Index3D below(cell_id.x, cell_id.y, bz);
843 if (!checkIndex3DInGrid(below)) {
846 const auto & bcell = gridCells[below];
849 reject_as_floating =
true;
854 if (reject_as_floating) {
855 *non_ground_points += *cell.
points;
859 *ground_points += *ground_inliers;
860 *non_ground_points += *non_ground_inliers;
863 if (grid_config.processing_phase == 1)
865 for (
const auto & cell_id : non_ground_cells)
871 if (!findNearestGroundNeighbor(cell_id, nearest_ground_id)) {
872 *non_ground_points += *cell.
points;
876 const auto & gcell = gridCells[nearest_ground_id];
878 for (
const auto & pt : cell.
points->points)
880 if (pointIsGroundWrtCell(pt, gcell))
881 ground_points->push_back(pt);
883 non_ground_points->push_back(pt);
889 for (
const auto & cell_id : non_ground_cells) {
890 *non_ground_points += *gridCells[cell_id].points;
893 return std::make_pair(ground_points, non_ground_points);
Core grid-based ground segmentation algorithm.
std::vector< Index3D > getNeighbors(const GridCell< PointT > &cell, const TerrainType &type, const std::vector< Index3D > &neighbor_offsets)
std::vector< Index3D > non_ground_cells
void setDistToGround(double z)
double computeSlope(const Eigen::Hyperplane< double, int(3)> &plane) const
Compute slope angle between surface normal and global Z-axis.
GridCellsType & getGridCells()
pcl::PointCloud< PointT >::Ptr centroid_cloud
pcl::SACSegmentation< PointT > seg
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PCLPointCloudAdaptor< PointT > >, PCLPointCloudAdaptor< PointT >, 3, size_t > KDTree
void getGroundCells()
Classify grid cells into ground or obstacle.
pcl::PointCloud< PointT >::Ptr non_ground_inliers
std::vector< Index3D > neighbor_offsets
void addPoint(const PointT &point)
std::vector< Index3D > centroid_indices
bool pointIsGroundWrtCell(const PointT &p, const GridCell< PointT > &gcell) const
std::unordered_map< Index3D, CellType, Index3D::HashFunction > GridCellsType
pcl::PointCloud< PointT >::Ptr ground_points
PointCloudGrid(const GridConfig &config)
bool classifySparsityNormalDist(const GridCell< PointT > &cell)
pcl::PointCloud< PointT >::Ptr ground_inliers
pcl::PointCloud< PointT >::Ptr non_ground_points
std::vector< Index3D > generateIndices(const uint16_t &z_threshold)
Generate neighbor offsets for region growing.
std::pair< typename pcl::PointCloud< PointT >::Ptr, typename pcl::PointCloud< PointT >::Ptr > segmentPoints()
Final segmentation step.
bool fitGroundPlane(GridCell< PointT > &cell, const double &inlier_threshold)
Fit a planar model to cell points using PROSAC.
void setInputCloud(typename pcl::PointCloud< PointT >::Ptr input, const Eigen::Quaterniond &R_body2World)
std::vector< Index3D > ground_cells
GridCell< PointT > CellType
bool findNearestGroundNeighbor(const Index3D &cid, Index3D &out_gid) const
Eigen::Quaterniond orientation
void expandGrid(std::queue< Index3D > q)
Region growing from robot cell using centroid KD-tree.
std::unordered_map< Index3D, size_t, Index3D::HashFunction > index_to_centroid_idx
bool checkIndex3DInGrid(const Index3D &index) const
std::string classifySparsityBoundingBox(const GridCell< PointT > &cell, typename pcl::PointCloud< PointT >::Ptr cloud)
Core data structures and configuration types for grid-based ground segmentation.
TerrainType
Semantic classification label assigned to a grid cell.
Represents one discretized 3D voxel in the spatial grid.
pcl::PointIndices::Ptr inliers
pcl::PointCloud< PointT >::Ptr points
Eigen::Vector3d eigenvalues
PrimitiveType primitive_type
Eigen::Matrix3d eigenvectors
Configuration parameters controlling segmentation behavior.
Integer 3D grid index used as key in hash map.
nanoflann adaptor for PCL point clouds.
size_t kdtree_get_point_count() const
bool kdtree_get_bbox(BBOX &) const
pcl::PointCloud< PointT > PointCloudType
PCLPointCloudAdaptor(PointCloudType &cloud)
double kdtree_get_pt(const size_t idx, const size_t dim) const