ground_segmentation 1.0
Ground segmentation in pointcloud
Loading...
Searching...
No Matches
ground_segmentation::PointCloudGrid< PointT > Class Template Reference

Core grid-based ground segmentation algorithm. More...

#include <ground_detection.hpp>

Collaboration diagram for ground_segmentation::PointCloudGrid< PointT >:
[legend]

Public Types

typedef GridCell< PointT > CellType
 
typedef std::unordered_map< Index3D, CellType, Index3D::HashFunctionGridCellsType
 
typedef nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PCLPointCloudAdaptor< PointT > >, PCLPointCloudAdaptor< PointT >, 3, size_t > KDTree
 

Public Member Functions

 PointCloudGrid (const GridConfig &config)
 
void clear ()
 
void setInputCloud (typename pcl::PointCloud< PointT >::Ptr input, const Eigen::Quaterniond &R_body2World)
 
GridCellsTypegetGridCells ()
 
std::pair< typename pcl::PointCloud< PointT >::Ptr, typename pcl::PointCloud< PointT >::Ptr > segmentPoints ()
 Final segmentation step.
 
bool checkIndex3DInGrid (const Index3D &index) const
 
void setDistToGround (double z)
 

Private Member Functions

std::vector< Index3DgenerateIndices (const uint16_t &z_threshold)
 Generate neighbor offsets for region growing.
 
void cleanUp ()
 
void addPoint (const PointT &point)
 
void getGroundCells ()
 Classify grid cells into ground or obstacle.
 
std::vector< Index3DgetNeighbors (const GridCell< PointT > &cell, const TerrainType &type, const std::vector< Index3D > &neighbor_offsets)
 
double computeSlope (const Eigen::Hyperplane< double, int(3)> &plane) const
 Compute slope angle between surface normal and global Z-axis.
 
double computeSlope (const Eigen::Vector3d &normal)
 
bool fitGroundPlane (GridCell< PointT > &cell, const double &inlier_threshold)
 Fit a planar model to cell points using PROSAC.
 
void expandGrid (std::queue< Index3D > q)
 Region growing from robot cell using centroid KD-tree.
 
std::string classifySparsityBoundingBox (const GridCell< PointT > &cell, typename pcl::PointCloud< PointT >::Ptr cloud)
 
bool classifySparsityNormalDist (const GridCell< PointT > &cell)
 
bool findNearestGroundNeighbor (const Index3D &cid, Index3D &out_gid) const
 
bool pointIsGroundWrtCell (const PointT &p, const GridCell< PointT > &gcell) const
 

Private Attributes

std::vector< Index3Dneighbor_offsets
 
GridCellsType gridCells
 
GridConfig grid_config
 
std::vector< Index3Dground_cells
 
std::vector< Index3Dnon_ground_cells
 
Eigen::Quaterniond orientation
 
pcl::PointCloud< PointT >::Ptr centroid_cloud
 
std::vector< Index3Dcentroid_indices
 
std::unordered_map< Index3D, size_t, Index3D::HashFunctionindex_to_centroid_idx
 
pcl::PointCloud< PointT >::Ptr ground_points
 
pcl::PointCloud< PointT >::Ptr non_ground_points
 
pcl::PointCloud< PointT >::Ptr ground_inliers
 
pcl::PointCloud< PointT >::Ptr non_ground_inliers
 
pcl::SACSegmentation< PointT > seg
 

Detailed Description

template<typename PointT>
class ground_segmentation::PointCloudGrid< PointT >

Core grid-based ground segmentation algorithm.

Pipeline: 1) Voxelize point cloud into 3D grid 2) Compute PCA per cell 3) Classify primitives (line/plane/noise) 4) Fit plane model (RANSAC) 5) Apply slope threshold 6) Seed region at robot cell 7) Expand via centroid KD-tree

Outputs separated ground and non-ground clouds.

Template Parameters
PointTPCL point type

Definition at line 79 of file ground_detection.hpp.

Member Typedef Documentation

◆ CellType

template<typename PointT >
typedef GridCell<PointT> ground_segmentation::PointCloudGrid< PointT >::CellType

Definition at line 84 of file ground_detection.hpp.

◆ GridCellsType

template<typename PointT >
typedef std::unordered_map<Index3D, CellType, Index3D::HashFunction> ground_segmentation::PointCloudGrid< PointT >::GridCellsType

Definition at line 85 of file ground_detection.hpp.

◆ KDTree

template<typename PointT >
typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCLPointCloudAdaptor<PointT> >, PCLPointCloudAdaptor<PointT>, 3, size_t> ground_segmentation::PointCloudGrid< PointT >::KDTree

Definition at line 114 of file ground_detection.hpp.

Constructor & Destructor Documentation

◆ PointCloudGrid()

template<typename PointT >
ground_segmentation::PointCloudGrid< PointT >::PointCloudGrid ( const GridConfig config)

Definition at line 221 of file ground_detection.hpp.

222{
223
224 centroid_cloud.reset(new pcl::PointCloud<PointT>());
225 grid_config = config;
226
227 if (grid_config.processing_phase == 2) {
229 } else {
231 }
232
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>());
237
238 seg.setOptimizeCoefficients(true);
239 seg.setModelType(pcl::SACMODEL_PLANE);
240 seg.setMethodType(pcl::SAC_PROSAC);
241 seg.setMaxIterations(1000);
242}
pcl::PointCloud< PointT >::Ptr centroid_cloud
pcl::SACSegmentation< PointT > seg
pcl::PointCloud< PointT >::Ptr non_ground_inliers
pcl::PointCloud< PointT >::Ptr ground_points
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.

Member Function Documentation

◆ addPoint()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::addPoint ( const PointT &  point)
private

Definition at line 354 of file ground_detection.hpp.

355{
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;
359
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));
363
364 CellType & cell = gridCells[{x, y, z}];
365 // information is redundant:
366 cell.x = x;
367 cell.y = y;
368 cell.z = z;
369 cell.points->push_back(point);
370}

References ground_segmentation::GridCell< PointT >::points, ground_segmentation::GridCell< PointT >::x, ground_segmentation::GridCell< PointT >::y, and ground_segmentation::GridCell< PointT >::z.

◆ checkIndex3DInGrid()

template<typename PointT >
bool ground_segmentation::PointCloudGrid< PointT >::checkIndex3DInGrid ( const Index3D index) const

Definition at line 763 of file ground_detection.hpp.

764{
765 if (auto search = gridCells.find(index); search != gridCells.end()) {
766 return true;
767 } else {
768 return false;
769 }
770}

◆ classifySparsityBoundingBox()

template<typename PointT >
std::string ground_segmentation::PointCloudGrid< PointT >::classifySparsityBoundingBox ( const GridCell< PointT > &  cell,
typename pcl::PointCloud< PointT >::Ptr  cloud 
)
private

Definition at line 393 of file ground_detection.hpp.

396{
397 if (cell.points->empty() || cloud->empty()) {return "Empty";}
398
399 PointT min_pt, max_pt;
400 pcl::getMinMax3D(*cell.points, min_pt, max_pt);
401
402 double volume = (max_pt.x - min_pt.x) *
403 (max_pt.y - min_pt.y) *
404 (max_pt.z - min_pt.z);
405
406 if (volume <= 0.0) {return "Degenerate";}
407
408 double sparsity = volume / static_cast<double>(cloud->size());
409
410 if (sparsity < 0.001) {
411 return "Low sparsity";
412 } else if (sparsity < 0.01) {
413 return "Medium sparsity";
414 } else {
415 return "High sparsity";
416 }
417}

References ground_segmentation::GridCell< PointT >::points.

◆ classifySparsityNormalDist()

template<typename PointT >
bool ground_segmentation::PointCloudGrid< PointT >::classifySparsityNormalDist ( const GridCell< PointT > &  cell)
private

Definition at line 420 of file ground_detection.hpp.

421{
422 if (cell.points->empty()) {return false;}
423
424 // Assume cell.centroid and cell.normal are Eigen::Vector3d and cell.normal is normalized
425 double sum_abs_proj = 0.0;
426
427 for (const auto & pt : cell.points->points) {
428 Eigen::Vector3d diff(pt.x - cell.centroid[0], pt.y - cell.centroid[1], pt.z - cell.centroid[2]);
429 double proj = diff.dot(cell.normal); // projection along normal
430 sum_abs_proj += std::abs(proj);
431 }
432
433 double mean_abs_proj = sum_abs_proj / cell.points->size();
434
435 // Threshold: tune for your use case (0.1 = flat/ground)
436 return mean_abs_proj < 0.1;
437}

References ground_segmentation::GridCell< PointT >::centroid, ground_segmentation::GridCell< PointT >::normal, and ground_segmentation::GridCell< PointT >::points.

◆ cleanUp()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::cleanUp ( )
private

Definition at line 344 of file ground_detection.hpp.

345{
346 ground_cells.clear();
347 non_ground_cells.clear();
348 centroid_cloud->clear();
349 centroid_indices.clear();
350 index_to_centroid_idx.clear();
351}
std::unordered_map< Index3D, size_t, Index3D::HashFunction > index_to_centroid_idx

◆ clear()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::clear ( )

Definition at line 338 of file ground_detection.hpp.

339{
340 gridCells.clear();
341}

◆ computeSlope() [1/2]

template<typename PointT >
double ground_segmentation::PointCloudGrid< PointT >::computeSlope ( const Eigen::Hyperplane< double, int(3)> &  plane) const
private

Compute slope angle between surface normal and global Z-axis.

Parameters
normalEstimated plane normal
Returns
slope angle in radians

Uses body-to-world orientation to ensure gravity alignment.

Definition at line 383 of file ground_detection.hpp.

384{
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)));
390}

◆ computeSlope() [2/2]

template<typename PointT >
double ground_segmentation::PointCloudGrid< PointT >::computeSlope ( const Eigen::Vector3d &  normal)
private

Definition at line 373 of file ground_detection.hpp.

374{
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)));
380}

◆ expandGrid()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::expandGrid ( std::queue< Index3D q)
private

Region growing from robot cell using centroid KD-tree.

Expands only to:

  • Cells classified as GROUND
  • Within centroidSearchRadius
  • Within vertical threshold (Phase 2)

Ensures ground continuity and prevents floating ground artifacts.

Definition at line 666 of file ground_detection.hpp.

667{
668 // Wrap the PCL point cloud with nanoflann adaptor
669 PCLPointCloudAdaptor<PointT> pclAdaptor(*centroid_cloud);
670
671#if NANOFLANN_VERSION >= 0x150
672 nanoflann::SearchParameters search_params;
673#else
674 nanoflann::SearchParams search_params;
675#endif
676
677 search_params.eps = 0.0; // Larger tolerance for faster results
678 search_params.sorted = false; // No need to sort
679
680 nanoflann::KDTreeSingleIndexAdaptorParams build_params(10); // leaf size
681 KDTree tree(3, pclAdaptor, build_params);
682 tree.buildIndex();
683
684 const double radius = grid_config.centroidSearchRadius * grid_config.centroidSearchRadius; // nanoflann uses squared radius
685
686 while (!q.empty()) {
687 Index3D idx = q.front();
688 q.pop();
689
690 GridCell<PointT> & current_cell = gridCells[idx];
691 current_cell.in_queue = false; // Mark as not in queue now that we're processing it
692
693 if (current_cell.expanded) {continue;}
694 current_cell.expanded = true;
695
696 // Find current centroid index
697 size_t curr_centroid_idx = index_to_centroid_idx[idx];
698 const PointT & curr_centroid = centroid_cloud->points.at(curr_centroid_idx);
699
700 // Prepare radius search
701#if NANOFLANN_VERSION >= 0x150
702using Neighbor = nanoflann::ResultItem<size_t, double>;
703#else
704using Neighbor = std::pair<size_t, double>;
705#endif
706
707 std::vector<Neighbor> neighbors;
708
709
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)};
713
714 tree.radiusSearch(query_pt, radius, neighbors, search_params);
715
716 for (const auto & nb : neighbors) {
717 size_t ni = nb.first;
718 if (ni == curr_centroid_idx) {
719 continue; // skip self
720
721 }
722 Index3D neighbor_id = centroid_indices[ni];
723 if (neighbor_id == idx) {
724 continue; // redundant but safe
725 }
726
727 GridCell<PointT> & neighbor = gridCells[neighbor_id];
728 if (neighbor.points->empty() || neighbor.expanded || neighbor.in_queue || neighbor.terrain_type != TerrainType::GROUND) {continue;}
729
730 if (grid_config.processing_phase == 2) {
731 // Reject neighbor if centroid height difference is too large
732 // Height continuity constraint
733 double dz =
734 std::abs(curr_centroid.z - neighbor.centroid[2]);
735
737 continue;
738 }
739
740 if (neighbor.terrain_type == TerrainType::GROUND) {
741 q.push(neighbor_id);
742 neighbor.in_queue = true;
743 }
744 }
745 ground_cells.emplace_back(idx);
746 }
747}
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PCLPointCloudAdaptor< PointT > >, PCLPointCloudAdaptor< PointT >, 3, size_t > KDTree

References ground_segmentation::GridCell< PointT >::centroid, ground_segmentation::GridCell< PointT >::expanded, ground_segmentation::GROUND, ground_segmentation::GridCell< PointT >::in_queue, ground_segmentation::GridCell< PointT >::points, and ground_segmentation::GridCell< PointT >::terrain_type.

◆ findNearestGroundNeighbor()

template<typename PointT >
bool ground_segmentation::PointCloudGrid< PointT >::findNearestGroundNeighbor ( const Index3D cid,
Index3D out_gid 
) const
private

Definition at line 267 of file ground_detection.hpp.

270{
271 bool found = false;
272 double best_d2 = std::numeric_limits<double>::infinity();
273
274 for (const auto & off : neighbor_offsets) {
275
276 Index3D nid = cid + off;
277
278 if (!checkIndex3DInGrid(nid)) {
279 continue;
280 }
281
282 const auto & ncell = gridCells.at(nid);
283
284 if (ncell.terrain_type != TerrainType::GROUND ||
285 ncell.points->empty() ||
286 !ncell.expanded)
287 {
288 continue;
289 }
290
291 double dx = double(nid.x - cid.x);
292 double dy = double(nid.y - cid.y);
293 double dz = double(nid.z - cid.z);
294
295 double d2 = dx*dx + dy*dy + 0.25*dz*dz;
296
297 if (d2 < best_d2) {
298 best_d2 = d2;
299 out_gid = nid;
300 found = true;
301 }
302 }
303
304 return found;
305}
bool checkIndex3DInGrid(const Index3D &index) const

References ground_segmentation::GROUND, ground_segmentation::Index3D::x, ground_segmentation::Index3D::y, and ground_segmentation::Index3D::z.

◆ fitGroundPlane()

template<typename PointT >
bool ground_segmentation::PointCloudGrid< PointT >::fitGroundPlane ( GridCell< PointT > &  cell,
const double &  inlier_threshold 
)
private

Fit a planar model to cell points using PROSAC.

Parameters
cellGrid cell
thresholdRANSAC inlier threshold (meters)
Returns
true if plane successfully estimated

Sets:

  • cell.inliers
  • cell.slope

Definition at line 474 of file ground_detection.hpp.

475{
476
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); // Adjust this threshold based on your needs
481 seg.segment(*inliers, *coefficients);
482 cell.inliers = inliers;
483 if (cell.inliers->indices.size() == 0) {
484 return false;
485 }
486
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);
492 return true;
493}
double computeSlope(const Eigen::Hyperplane< double, int(3)> &plane) const
Compute slope angle between surface normal and global Z-axis.

References ground_segmentation::GridCell< PointT >::inliers, ground_segmentation::GridCell< PointT >::points, and ground_segmentation::GridCell< PointT >::slope.

◆ generateIndices()

template<typename PointT >
std::vector< Index3D > ground_segmentation::PointCloudGrid< PointT >::generateIndices ( const uint16_t &  z_threshold)
private

Generate neighbor offsets for region growing.

Parameters
z_thresholdDefines vertical expansion range.

Phase 1 → z_threshold = 0 (horizontal only) Phase 2 → z_threshold = 1 (vertical connectivity allowed)

Definition at line 245 of file ground_detection.hpp.

246{
247 std::vector<Index3D> idxs;
248
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) {
253 continue;
254 }
255 Index3D idx;
256 idx.x = dx;
257 idx.y = dy;
258 idx.z = dz;
259 idxs.push_back(idx);
260 }
261 }
262 }
263 return idxs;
264}

References ground_segmentation::Index3D::x, ground_segmentation::Index3D::y, and ground_segmentation::Index3D::z.

◆ getGridCells()

template<typename PointT >
GridCellsType & ground_segmentation::PointCloudGrid< PointT >::getGridCells ( )
inline

◆ getGroundCells()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::getGroundCells ( )
private

Classify grid cells into ground or obstacle.

Steps:

  • Reject sparse cells
  • PCA eigenvalue analysis
  • Primitive classification
  • Plane fitting
  • Slope thresholding
  • Robot-seeded region growing

Definition at line 496 of file ground_detection.hpp.

497{
498
499 if (gridCells.empty()) {
500 return;
501 }
502
503 this->cleanUp();
504
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());
509
510 for (auto & cellPair : gridCells) {
511 GridCell<PointT> & cell = cellPair.second;
512
513 //Too few points
514 if ((cell.points->size() < 3)) {continue;}
515
516 Index3D cell_id = cellPair.first;
517 pcl::compute3DCentroid(*(cell.points), cell.centroid);
518
519 if (cell.points->size() <= 5) {
520 Eigen::Vector4f squared_diff_sum(0, 0, 0, 0);
521
522 for (typename pcl::PointCloud<PointT>::iterator it = cell.points->begin();
523 it != cell.points->end(); ++it)
524 {
525 Eigen::Vector4f diff = (*it).getVector4fMap() - cell.centroid.template cast<float>();
526 squared_diff_sum += diff.array().square().matrix();
527 }
528
529 Eigen::Vector4f variance = squared_diff_sum / cell.points->size();
530
531 if (variance[0] < variance[2] && variance[1] < variance[2]) {
532 cell.terrain_type = TerrainType::OBSTACLE;
533 non_ground_cells.push_back(cell_id);
534 } else {
535 cell.terrain_type = TerrainType::GROUND;
536 PointT centroid3d;
537
538 centroid3d.x = cell.centroid[0];
539 centroid3d.y = cell.centroid[1];
540 centroid3d.z = cell.centroid[2];
541
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;
545 }
546 continue;
547 }
548
549 Eigen::Matrix3d covariance_matrix;
550 pcl::computeCovarianceMatrixNormalized(*cell.points, cell.centroid, covariance_matrix);
551
552 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver(covariance_matrix,
553 Eigen::ComputeEigenvectors);
554 cell.eigenvectors = eigen_solver.eigenvectors();
555 cell.eigenvalues = eigen_solver.eigenvalues();
556
557 Eigen::Vector3d normal = cell.eigenvectors.col(0);
558
559 // Ensure all normals point upward
560 if (normal(2) < 0) {
561 normal *= -1; // flip the normal direction
562 }
563
564 normal.normalize();
565 cell.normal = normal;
566
567 double ratio = cell.eigenvalues[2] / cell.eigenvalues.sum();
568 if (ratio > 0.950) {
569 cell.primitive_type = PrimitiveType::LINE;
570
571 Eigen::Vector3d v = cell.eigenvectors.col(2);
572 if (v(2) < 0) {
573 v *= -1; // flip the normal direction
574 }
575 v = orientation * v;
576 v.normalize();
577
578 double angle_rad = acos(std::abs(v.dot(Eigen::Vector3d::UnitZ())));
579
580 if (angle_rad > ((90 - grid_config.slopeThresholdDegrees) * (M_PI / 180))) {
581 cell.terrain_type = TerrainType::GROUND;
582 PointT centroid3d;
583
584 centroid3d.x = cell.centroid[0];
585 centroid3d.y = cell.centroid[1];
586 centroid3d.z = cell.centroid[2];
587
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;
591 } else {
592 cell.terrain_type = TerrainType::OBSTACLE;
593 non_ground_cells.push_back(cell_id);
594 }
595 continue;
596 } else if (ratio > 0.4) {
597 cell.primitive_type = PrimitiveType::PLANE;
598 if (std::abs(computeSlope(cell.normal)) > (grid_config.slopeThresholdDegrees * (M_PI / 180.0))) {
599 cell.terrain_type = TerrainType::OBSTACLE;
600 non_ground_cells.push_back(cell_id);
601 continue;
602 }
603 } else {
604 cell.terrain_type = TerrainType::OBSTACLE;
605 cell.primitive_type = PrimitiveType::NOISE;
606 non_ground_cells.push_back(cell_id);
607 continue;
608 }
609
611 cell.terrain_type = TerrainType::OBSTACLE;
612 non_ground_cells.push_back(cell_id);
613 continue;
614 }
615
616 if (cell.slope < (grid_config.slopeThresholdDegrees * (M_PI / 180)) ) {
617 cell.terrain_type = TerrainType::GROUND;
618 PointT centroid3d;
619
620 centroid3d.x = cell.centroid[0];
621 centroid3d.y = cell.centroid[1];
622 centroid3d.z = cell.centroid[2];
623
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;
627 } else {
628 cell.terrain_type = TerrainType::OBSTACLE;
629 non_ground_cells.push_back(cell_id);
630 }
631 }
632
633 std::queue<Index3D> q;
634
635 Index3D best_robot_cell;
636
637 // Compute z index directly from distToGround
638 int z = static_cast<int>(std::floor(grid_config.distToGround / grid_config.cellSizeZ));
639 best_robot_cell = Index3D{0, 0, z};
640
641 // Ensure the cell exists (even if empty)
642 GridCell<PointT> & robot_cell = gridCells[best_robot_cell];
643
644 // Mark semantics
645 robot_cell.terrain_type = TerrainType::GROUND;
646
647 // IMPORTANT: allow expansion even without points
648 robot_cell.expanded = false;
649 robot_cell.in_queue = true;
650
651 // Add centroid ONLY for KD-tree seeding
652 PointT centroid3d;
653 centroid3d.x = 0.0;
654 centroid3d.y = 0.0;
655 centroid3d.z = grid_config.distToGround;
656
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;
660
661 q.push(best_robot_cell);
662 expandGrid(q);
663}
bool fitGroundPlane(GridCell< PointT > &cell, const double &inlier_threshold)
Fit a planar model to cell points using PROSAC.
void expandGrid(std::queue< Index3D > q)
Region growing from robot cell using centroid KD-tree.

References ground_segmentation::GridCell< PointT >::centroid, ground_segmentation::GridCell< PointT >::eigenvalues, ground_segmentation::GridCell< PointT >::eigenvectors, ground_segmentation::GridCell< PointT >::expanded, ground_segmentation::GROUND, ground_segmentation::GridCell< PointT >::in_queue, ground_segmentation::LINE, ground_segmentation::NOISE, ground_segmentation::GridCell< PointT >::normal, ground_segmentation::OBSTACLE, ground_segmentation::PLANE, ground_segmentation::GridCell< PointT >::points, ground_segmentation::GridCell< PointT >::primitive_type, ground_segmentation::GridCell< PointT >::slope, and ground_segmentation::GridCell< PointT >::terrain_type.

◆ getNeighbors()

template<typename PointT >
std::vector< Index3D > ground_segmentation::PointCloudGrid< PointT >::getNeighbors ( const GridCell< PointT > &  cell,
const TerrainType type,
const std::vector< Index3D > &  neighbor_offsets 
)
private

Definition at line 440 of file ground_detection.hpp.

444{
445
446 std::vector<Index3D> neighbors;
447
448 Index3D cell_id;
449 cell_id.x = cell.x;
450 cell_id.y = cell.y;
451 cell_id.z = cell.z;
452
453 for (uint i = 0; i < idx.size(); ++i) {
454 Index3D neighbor_id = cell_id + idx[i];
455
456 if (!checkIndex3DInGrid(neighbor_id)) {
457 continue;
458 }
459
460 const GridCell<PointT> & neighbor = gridCells[neighbor_id];
461
462 if (neighbor.points->size() > 0 && neighbor.terrain_type == type) {
463 Index3D id;
464 id.x = neighbor.x;
465 id.y = neighbor.y;
466 id.z = neighbor.z;
467 neighbors.push_back(id);
468 }
469 }
470 return neighbors;
471}

References ground_segmentation::GridCell< PointT >::points, ground_segmentation::GridCell< PointT >::terrain_type, ground_segmentation::GridCell< PointT >::x, ground_segmentation::Index3D::x, ground_segmentation::GridCell< PointT >::y, ground_segmentation::Index3D::y, ground_segmentation::GridCell< PointT >::z, and ground_segmentation::Index3D::z.

◆ pointIsGroundWrtCell()

template<typename PointT >
bool ground_segmentation::PointCloudGrid< PointT >::pointIsGroundWrtCell ( const PointT &  p,
const GridCell< PointT > &  gcell 
) const
private

Definition at line 308 of file ground_detection.hpp.

311{
312 Eigen::Vector3d n = gcell.normal;
313
314 if (!std::isfinite(n.x()) || !std::isfinite(n.y()) ||
315 !std::isfinite(n.z()) || n.norm() < 1e-6)
316 {
317 return false;
318 }
319
320 n.normalize();
321
322 Eigen::Vector3d c(
323 double(gcell.centroid[0]),
324 double(gcell.centroid[1]),
325 double(gcell.centroid[2]));
326
327 Eigen::Vector3d x(double(p.x), double(p.y), double(p.z));
328
329 // Distance to plane
330 double plane_dist = std::abs(n.dot(x - c));
331 if (plane_dist > grid_config.groundInlierThreshold)
332 return false;
333
334 return true;
335}

References ground_segmentation::GridCell< PointT >::centroid, and ground_segmentation::GridCell< PointT >::normal.

◆ segmentPoints()

template<typename PointT >
std::pair< typename pcl::PointCloud< PointT >::Ptr, typename pcl::PointCloud< PointT >::Ptr > ground_segmentation::PointCloudGrid< PointT >::segmentPoints ( )

Final segmentation step.

Splits:

  • Ground points
  • Non-ground points

Applies:

  • Inlier extraction
  • Sparsity checks
  • Floating cell rejection
Returns
pair<ground_cloud, non_ground_cloud>

Definition at line 774 of file ground_detection.hpp.

775{
776 ground_points->clear();
777 non_ground_points->clear();
778 ground_inliers->clear();
779 non_ground_inliers->clear();
780
781 pcl::ExtractIndices<PointT> extract_ground;
782
784 for (auto & cell_id : ground_cells) {
785
786 GridCell<PointT> & cell = gridCells[cell_id];
787
788 if ((cell.points->size() <= 5 || cell.primitive_type == PrimitiveType::LINE) &&
789 cell.terrain_type == TerrainType::GROUND)
790 {
791 *ground_points += *cell.points;
792 continue;
793 }
794
795 extract_ground.setInputCloud(cell.points);
796 extract_ground.setIndices(cell.inliers);
797
798 extract_ground.setNegative(false);
799 extract_ground.filter(*ground_inliers);
800
801 extract_ground.setNegative(true);
802 extract_ground.filter(*non_ground_inliers);
803
804 if (ground_inliers->size() == 0) {
805 continue;
806 }
807
808 auto score1 = classifySparsityBoundingBox(cell, ground_inliers);
810
811 if (score1 == score2) {
812 Eigen::Vector4d centroid;
813 pcl::compute3DCentroid(*(ground_inliers), centroid);
814
815 // For each candidate ground cell:
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];
822 if (ncell.terrain_type == TerrainType::GROUND) {
823 neighbor_zs.push_back(ncell.centroid[2]);
824 }
825 }
826
827 if (neighbor_zs.empty()) {
828 *non_ground_points += *cell.points;
829 continue;
830 }
831
832 double local_ref = *std::min_element(neighbor_zs.begin(), neighbor_zs.end());
833
834 if (std::abs(cell_z - local_ref) > grid_config.maxGroundHeightDeviation) {
835 *non_ground_points += *cell.points;
836 continue;
837 }
838
839 bool reject_as_floating = false;
840 int bz = cell_id.z - 1;
841 while (true) {
842 Index3D below(cell_id.x, cell_id.y, bz);
843 if (!checkIndex3DInGrid(below)) {
844 break; // Out of bound: stop looping
845 }
846 const auto & bcell = gridCells[below];
847 if (!bcell.points->empty() && bcell.terrain_type != TerrainType::GROUND) {
848 // Found an occupied non-ground cell below—reject as floating!
849 reject_as_floating = true;
850 break;
851 }
852 --bz;
853 }
854 if (reject_as_floating) {
855 *non_ground_points += *cell.points;
856 continue;
857 }
858 }
861 }
862
864 {
865 for (const auto & cell_id : non_ground_cells)
866 {
867 const GridCell<PointT> & cell = gridCells[cell_id];
868
869 Index3D nearest_ground_id;
870
871 if (!findNearestGroundNeighbor(cell_id, nearest_ground_id)) {
872 *non_ground_points += *cell.points;
873 continue;
874 }
875
876 const auto & gcell = gridCells[nearest_ground_id];
877
878 for (const auto & pt : cell.points->points)
879 {
880 if (pointIsGroundWrtCell(pt, gcell))
881 ground_points->push_back(pt);
882 else
883 non_ground_points->push_back(pt);
884 }
885 }
886 }
887 else
888 {
889 for (const auto & cell_id : non_ground_cells) {
890 *non_ground_points += *gridCells[cell_id].points;
891 }
892 }
893 return std::make_pair(ground_points, non_ground_points);
894}
void getGroundCells()
Classify grid cells into ground or obstacle.
bool pointIsGroundWrtCell(const PointT &p, const GridCell< PointT > &gcell) const
bool findNearestGroundNeighbor(const Index3D &cid, Index3D &out_gid) const
std::string classifySparsityBoundingBox(const GridCell< PointT > &cell, typename pcl::PointCloud< PointT >::Ptr cloud)

References ground_segmentation::GROUND, ground_segmentation::GridCell< PointT >::inliers, ground_segmentation::LINE, ground_segmentation::GridCell< PointT >::points, ground_segmentation::GridCell< PointT >::primitive_type, and ground_segmentation::GridCell< PointT >::terrain_type.

◆ setDistToGround()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::setDistToGround ( double  z)
inline

◆ setInputCloud()

template<typename PointT >
void ground_segmentation::PointCloudGrid< PointT >::setInputCloud ( typename pcl::PointCloud< PointT >::Ptr  input,
const Eigen::Quaterniond &  R_body2World 
)

Definition at line 750 of file ground_detection.hpp.

753{
754
755 this->clear();
756 orientation = R_body2World;
757 for (typename pcl::PointCloud<PointT>::iterator it = input->begin(); it != input->end(); ++it) {
758 this->addPoint(*it);
759 }
760}

Member Data Documentation

◆ centroid_cloud

template<typename PointT >
pcl::PointCloud<PointT>::Ptr ground_segmentation::PointCloudGrid< PointT >::centroid_cloud
private

Definition at line 209 of file ground_detection.hpp.

◆ centroid_indices

template<typename PointT >
std::vector<Index3D> ground_segmentation::PointCloudGrid< PointT >::centroid_indices
private

Definition at line 210 of file ground_detection.hpp.

◆ grid_config

template<typename PointT >
GridConfig ground_segmentation::PointCloudGrid< PointT >::grid_config
private

◆ gridCells

template<typename PointT >
GridCellsType ground_segmentation::PointCloudGrid< PointT >::gridCells
private

◆ ground_cells

template<typename PointT >
std::vector<Index3D> ground_segmentation::PointCloudGrid< PointT >::ground_cells
private

Definition at line 204 of file ground_detection.hpp.

◆ ground_inliers

template<typename PointT >
pcl::PointCloud<PointT>::Ptr ground_segmentation::PointCloudGrid< PointT >::ground_inliers
private

Definition at line 215 of file ground_detection.hpp.

◆ ground_points

template<typename PointT >
pcl::PointCloud<PointT>::Ptr ground_segmentation::PointCloudGrid< PointT >::ground_points
private

Definition at line 213 of file ground_detection.hpp.

◆ index_to_centroid_idx

template<typename PointT >
std::unordered_map<Index3D, size_t, Index3D::HashFunction> ground_segmentation::PointCloudGrid< PointT >::index_to_centroid_idx
private

Definition at line 211 of file ground_detection.hpp.

◆ neighbor_offsets

template<typename PointT >
std::vector<Index3D> ground_segmentation::PointCloudGrid< PointT >::neighbor_offsets
private

Definition at line 200 of file ground_detection.hpp.

◆ non_ground_cells

template<typename PointT >
std::vector<Index3D> ground_segmentation::PointCloudGrid< PointT >::non_ground_cells
private

Definition at line 205 of file ground_detection.hpp.

◆ non_ground_inliers

template<typename PointT >
pcl::PointCloud<PointT>::Ptr ground_segmentation::PointCloudGrid< PointT >::non_ground_inliers
private

Definition at line 216 of file ground_detection.hpp.

◆ non_ground_points

template<typename PointT >
pcl::PointCloud<PointT>::Ptr ground_segmentation::PointCloudGrid< PointT >::non_ground_points
private

Definition at line 214 of file ground_detection.hpp.

◆ orientation

template<typename PointT >
Eigen::Quaterniond ground_segmentation::PointCloudGrid< PointT >::orientation
private

Definition at line 206 of file ground_detection.hpp.

◆ seg

template<typename PointT >
pcl::SACSegmentation<PointT> ground_segmentation::PointCloudGrid< PointT >::seg
private

Definition at line 218 of file ground_detection.hpp.


The documentation for this class was generated from the following file: