ground_segmentation 1.0
Ground segmentation in pointcloud
Loading...
Searching...
No Matches
ground_detection.hpp
Go to the documentation of this file.
1
15#pragma once
16
18#include <nanoflann.hpp>
19#include <unordered_map>
20
22{
23
33template<typename PointT>
35{
36 typedef pcl::PointCloud<PointT> PointCloudType;
37
39
42
43 // Must return the number of data points
44 inline size_t kdtree_get_point_count() const {return cloud.points.size();}
45
46 // Returns the dim'th component of the idx'th point in the point cloud
47 inline double kdtree_get_pt(const size_t idx, const size_t dim) const
48 {
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;}
52 }
53
54 // Optional bounding-box computation
55 template<class BBOX>
56 bool kdtree_get_bbox(BBOX & /*bb*/) const {return false;}
57};
58
59
78template<typename PointT>
80{
81
82public:
83
85 typedef std::unordered_map<Index3D, CellType, Index3D::HashFunction> GridCellsType;
86
87 PointCloudGrid(const GridConfig & config);
88 void clear();
89 void setInputCloud(
90 typename pcl::PointCloud<PointT>::Ptr input,
91 const Eigen::Quaterniond & R_body2World);
93
108 std::pair<typename pcl::PointCloud<PointT>::Ptr,
109 typename pcl::PointCloud<PointT>::Ptr> segmentPoints();
110
111 // Build KD-Tree
112 typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double,
115 bool checkIndex3DInGrid(const Index3D & index) const;
116
117 void setDistToGround(double z)
118 {
120 }
121private:
130 std::vector<Index3D> generateIndices(const uint16_t & z_threshold);
131 void cleanUp();
132 void addPoint(const PointT & point);
133
145 void getGroundCells();
146
147 std::vector<Index3D> getNeighbors(
148 const GridCell<PointT> & cell, const TerrainType & type,
149 const std::vector<Index3D> & neighbor_offsets);
150
159 double computeSlope(const Eigen::Hyperplane<double, int(3)> & plane) const;
160 double computeSlope(const Eigen::Vector3d & normal);
161
174 bool fitGroundPlane(GridCell<PointT> & cell, const double & inlier_threshold);
175
186 void expandGrid(std::queue<Index3D> q);
187 std::string classifySparsityBoundingBox(
188 const GridCell<PointT> & cell,
189 typename pcl::PointCloud<PointT>::Ptr cloud);
191
193 const Index3D & cid,
194 Index3D & out_gid) const;
195
197 const PointT & p,
198 const GridCell<PointT> & gcell) const;
199
200 std::vector<Index3D> neighbor_offsets;
201
204 std::vector<Index3D> ground_cells;
205 std::vector<Index3D> non_ground_cells;
206 Eigen::Quaterniond orientation;
207
208 // Add these members to your class:
209 typename pcl::PointCloud<PointT>::Ptr centroid_cloud;
210 std::vector<Index3D> centroid_indices; // Corresponding Index3D for each centroid
211 std::unordered_map<Index3D, size_t, Index3D::HashFunction> index_to_centroid_idx;
212
213 typename pcl::PointCloud<PointT>::Ptr ground_points;
214 typename pcl::PointCloud<PointT>::Ptr non_ground_points;
215 typename pcl::PointCloud<PointT>::Ptr ground_inliers;
216 typename pcl::PointCloud<PointT>::Ptr non_ground_inliers;
217
218 pcl::SACSegmentation<PointT> seg;
219};
220template<typename PointT>
222{
223
224 centroid_cloud.reset(new pcl::PointCloud<PointT>());
225 grid_config = config;
226
227 if (grid_config.processing_phase == 2) {
228 neighbor_offsets = generateIndices(1);
229 } else {
230 neighbor_offsets = generateIndices(0);
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}
243
244template<typename PointT>
245std::vector<Index3D> PointCloudGrid<PointT>::generateIndices(const uint16_t & z_threshold)
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}
265
266template<typename PointT>
268 const Index3D & cid,
269 Index3D & out_gid) const
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}
306
307template<typename PointT>
309 const PointT & p,
310 const GridCell<PointT> & gcell) const
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}
336
337template<typename PointT>
339{
340 gridCells.clear();
341}
342
343template<typename PointT>
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}
352
353template<typename PointT>
354void PointCloudGrid<PointT>::addPoint(const PointT & point)
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}
371
372template<typename PointT>
373double PointCloudGrid<PointT>::computeSlope(const Eigen::Vector3d & normal)
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}
381
382template<typename PointT>
383double PointCloudGrid<PointT>::computeSlope(const Eigen::Hyperplane<double, int(3)> & plane) const
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}
391
392template<typename PointT>
394 const GridCell<PointT> & cell,
395 typename pcl::PointCloud<PointT>::Ptr cloud)
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}
418
419template<typename PointT>
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}
438
439template<typename PointT>
441 const GridCell<PointT> & cell,
442 const TerrainType & type,
443 const std::vector<Index3D> & idx)
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}
472
473template<typename PointT>
474bool PointCloudGrid<PointT>::fitGroundPlane(GridCell<PointT> & cell, const double & threshold)
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}
494
495template<typename PointT>
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]) {
533 non_ground_cells.push_back(cell_id);
534 } else {
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) {
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))) {
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 {
593 non_ground_cells.push_back(cell_id);
594 }
595 continue;
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);
601 continue;
602 }
603 } else {
606 non_ground_cells.push_back(cell_id);
607 continue;
608 }
609
610 if (!fitGroundPlane(cell, grid_config.groundInlierThreshold)) {
612 non_ground_cells.push_back(cell_id);
613 continue;
614 }
615
616 if (cell.slope < (grid_config.slopeThresholdDegrees * (M_PI / 180)) ) {
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 {
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
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}
664
665template<typename PointT>
666void PointCloudGrid<PointT>::expandGrid(std::queue<Index3D> q)
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
736 if (dz > grid_config.maxGroundHeightDeviation)
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}
748
749template<typename PointT>
751 typename pcl::PointCloud<PointT>::Ptr input,
752 const Eigen::Quaterniond & R_body2World)
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}
761
762template<typename PointT>
764{
765 if (auto search = gridCells.find(index); search != gridCells.end()) {
766 return true;
767 } else {
768 return false;
769 }
770}
771
772template<typename PointT>
773std::pair<typename pcl::PointCloud<PointT>::Ptr,
774 typename pcl::PointCloud<PointT>::Ptr> PointCloudGrid<PointT>::segmentPoints()
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
783 getGroundCells();
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) &&
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);
809 auto score2 = classifySparsityBoundingBox(cell, non_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 }
859 *ground_points += *ground_inliers;
860 *non_ground_points += *non_ground_inliers;
861 }
862
863 if (grid_config.processing_phase == 1)
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}
895
896} //namespace ground_segmentation
Core grid-based ground segmentation algorithm.
std::vector< Index3D > getNeighbors(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.
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
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)
bool findNearestGroundNeighbor(const Index3D &cid, Index3D &out_gid) const
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::PointCloud< PointT >::Ptr points
Configuration parameters controlling segmentation behavior.
Integer 3D grid index used as key in hash map.
nanoflann adaptor for PCL point clouds.
double kdtree_get_pt(const size_t idx, const size_t dim) const