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 if(0 == pcl::computeCovarianceMatrixNormalized(*cell.points, cell.centroid, covariance_matrix)){
551 continue; // THIS SHOULD NEVER HAPPEN (only here to fix compiler warning)
552 }
553
554 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver(covariance_matrix,
555 Eigen::ComputeEigenvectors);
556 cell.eigenvectors = eigen_solver.eigenvectors();
557 cell.eigenvalues = eigen_solver.eigenvalues();
558
559 Eigen::Vector3d normal = cell.eigenvectors.col(0);
560
561 // Ensure all normals point upward
562 if (normal(2) < 0) {
563 normal *= -1; // flip the normal direction
564 }
565
566 normal.normalize();
567 cell.normal = normal;
568
569 double ratio = cell.eigenvalues[2] / cell.eigenvalues.sum();
570 if (ratio > 0.950) {
572
573 Eigen::Vector3d v = cell.eigenvectors.col(2);
574 if (v(2) < 0) {
575 v *= -1; // flip the normal direction
576 }
577 v = orientation * v;
578 v.normalize();
579
580 double angle_rad = acos(std::abs(v.dot(Eigen::Vector3d::UnitZ())));
581
582 if (angle_rad > ((90 - grid_config.slopeThresholdDegrees) * (M_PI / 180))) {
584 PointT centroid3d;
585
586 centroid3d.x = cell.centroid[0];
587 centroid3d.y = cell.centroid[1];
588 centroid3d.z = cell.centroid[2];
589
590 centroid_cloud->points.push_back(centroid3d);
591 centroid_indices.push_back(cell_id);
592 index_to_centroid_idx[cell_id] = centroid_cloud->size() - 1;
593 } else {
595 non_ground_cells.push_back(cell_id);
596 }
597 continue;
598 } else if (ratio > 0.4) {
600 if (std::abs(computeSlope(cell.normal)) > (grid_config.slopeThresholdDegrees * (M_PI / 180.0))) {
602 non_ground_cells.push_back(cell_id);
603 continue;
604 }
605 } else {
608 non_ground_cells.push_back(cell_id);
609 continue;
610 }
611
612 if (!fitGroundPlane(cell, grid_config.groundInlierThreshold)) {
614 non_ground_cells.push_back(cell_id);
615 continue;
616 }
617
618 if (cell.slope < (grid_config.slopeThresholdDegrees * (M_PI / 180)) ) {
620 PointT centroid3d;
621
622 centroid3d.x = cell.centroid[0];
623 centroid3d.y = cell.centroid[1];
624 centroid3d.z = cell.centroid[2];
625
626 centroid_cloud->points.push_back(centroid3d);
627 centroid_indices.push_back(cell_id);
628 index_to_centroid_idx[cell_id] = centroid_cloud->size() - 1;
629 } else {
631 non_ground_cells.push_back(cell_id);
632 }
633 }
634
635 std::queue<Index3D> q;
636
637 Index3D best_robot_cell;
638
639 // Compute z index directly from distToGround
640 int z = static_cast<int>(std::floor(grid_config.distToGround / grid_config.cellSizeZ));
641 best_robot_cell = Index3D{0, 0, z};
642
643 // Ensure the cell exists (even if empty)
644 GridCell<PointT> & robot_cell = gridCells[best_robot_cell];
645
646 // Mark semantics
648
649 // IMPORTANT: allow expansion even without points
650 robot_cell.expanded = false;
651 robot_cell.in_queue = true;
652
653 // Add centroid ONLY for KD-tree seeding
654 PointT centroid3d;
655 centroid3d.x = 0.0;
656 centroid3d.y = 0.0;
657 centroid3d.z = grid_config.distToGround;
658
659 centroid_cloud->points.push_back(centroid3d);
660 centroid_indices.push_back(best_robot_cell);
661 index_to_centroid_idx[best_robot_cell] = centroid_cloud->size() - 1;
662
663 q.push(best_robot_cell);
664 expandGrid(q);
665}
666
667template<typename PointT>
668void PointCloudGrid<PointT>::expandGrid(std::queue<Index3D> q)
669{
670 // Wrap the PCL point cloud with nanoflann adaptor
671 PCLPointCloudAdaptor<PointT> pclAdaptor(*centroid_cloud);
672
673#if NANOFLANN_VERSION >= 0x150
674 nanoflann::SearchParameters search_params;
675#else
676 nanoflann::SearchParams search_params;
677#endif
678
679 search_params.eps = 0.0; // Larger tolerance for faster results
680 search_params.sorted = false; // No need to sort
681
682 nanoflann::KDTreeSingleIndexAdaptorParams build_params(10); // leaf size
683 KDTree tree(3, pclAdaptor, build_params);
684 tree.buildIndex();
685
686 const double radius = grid_config.centroidSearchRadius * grid_config.centroidSearchRadius; // nanoflann uses squared radius
687
688 while (!q.empty()) {
689 Index3D idx = q.front();
690 q.pop();
691
692 GridCell<PointT> & current_cell = gridCells[idx];
693 current_cell.in_queue = false; // Mark as not in queue now that we're processing it
694
695 if (current_cell.expanded) {continue;}
696 current_cell.expanded = true;
697
698 // Find current centroid index
699 size_t curr_centroid_idx = index_to_centroid_idx[idx];
700 const PointT & curr_centroid = centroid_cloud->points.at(curr_centroid_idx);
701
702 // Prepare radius search
703#if NANOFLANN_VERSION >= 0x150
704using Neighbor = nanoflann::ResultItem<size_t, double>;
705#else
706using Neighbor = std::pair<size_t, double>;
707#endif
708
709 std::vector<Neighbor> neighbors;
710
711
712 double query_pt[3] = {static_cast<double>(curr_centroid.x),
713 static_cast<double>(curr_centroid.y),
714 static_cast<double>(curr_centroid.z)};
715
716 tree.radiusSearch(query_pt, radius, neighbors, search_params);
717
718 for (const auto & nb : neighbors) {
719 size_t ni = nb.first;
720 if (ni == curr_centroid_idx) {
721 continue; // skip self
722
723 }
724 Index3D neighbor_id = centroid_indices[ni];
725 if (neighbor_id == idx) {
726 continue; // redundant but safe
727 }
728
729 GridCell<PointT> & neighbor = gridCells[neighbor_id];
730 if (neighbor.points->empty() || neighbor.expanded || neighbor.in_queue || neighbor.terrain_type != TerrainType::GROUND) {continue;}
731
732 if (grid_config.processing_phase == 2) {
733 // Reject neighbor if centroid height difference is too large
734 // Height continuity constraint
735 double dz =
736 std::abs(curr_centroid.z - neighbor.centroid[2]);
737
738 if (dz > grid_config.maxGroundHeightDeviation)
739 continue;
740 }
741
742 if (neighbor.terrain_type == TerrainType::GROUND) {
743 q.push(neighbor_id);
744 neighbor.in_queue = true;
745 }
746 }
747 ground_cells.emplace_back(idx);
748 }
749}
750
751template<typename PointT>
753 typename pcl::PointCloud<PointT>::Ptr input,
754 const Eigen::Quaterniond & R_body2World)
755{
756
757 this->clear();
758 orientation = R_body2World;
759 for (typename pcl::PointCloud<PointT>::iterator it = input->begin(); it != input->end(); ++it) {
760 this->addPoint(*it);
761 }
762}
763
764template<typename PointT>
766{
767 if (auto search = gridCells.find(index); search != gridCells.end()) {
768 return true;
769 } else {
770 return false;
771 }
772}
773
774template<typename PointT>
775std::pair<typename pcl::PointCloud<PointT>::Ptr,
776 typename pcl::PointCloud<PointT>::Ptr> PointCloudGrid<PointT>::segmentPoints()
777{
778 ground_points->clear();
779 non_ground_points->clear();
780 ground_inliers->clear();
781 non_ground_inliers->clear();
782
783 pcl::ExtractIndices<PointT> extract_ground;
784
785 getGroundCells();
786 for (auto & cell_id : ground_cells) {
787
788 GridCell<PointT> & cell = gridCells[cell_id];
789
790 if ((cell.points->size() <= 5 || cell.primitive_type == PrimitiveType::LINE) &&
792 {
793 *ground_points += *cell.points;
794 continue;
795 }
796
797 extract_ground.setInputCloud(cell.points);
798 extract_ground.setIndices(cell.inliers);
799
800 extract_ground.setNegative(false);
801 extract_ground.filter(*ground_inliers);
802
803 extract_ground.setNegative(true);
804 extract_ground.filter(*non_ground_inliers);
805
806 if (ground_inliers->size() == 0) {
807 continue;
808 }
809
810 auto score1 = classifySparsityBoundingBox(cell, ground_inliers);
811 auto score2 = classifySparsityBoundingBox(cell, non_ground_inliers);
812
813 if (score1 == score2) {
814 Eigen::Vector4d centroid;
815 pcl::compute3DCentroid(*(ground_inliers), centroid);
816
817 // For each candidate ground cell:
818 double cell_z = centroid[2];
819 std::vector<double> neighbor_zs;
820 for (const auto & offset : neighbor_offsets) {
821 Index3D nidx = cell_id + offset;
822 if (!checkIndex3DInGrid(nidx)) {continue;}
823 const auto & ncell = gridCells[nidx];
824 if (ncell.terrain_type == TerrainType::GROUND) {
825 neighbor_zs.push_back(ncell.centroid[2]);
826 }
827 }
828
829 if (neighbor_zs.empty()) {
830 *non_ground_points += *cell.points;
831 continue;
832 }
833
834 double local_ref = *std::min_element(neighbor_zs.begin(), neighbor_zs.end());
835
836 if (std::abs(cell_z - local_ref) > grid_config.maxGroundHeightDeviation) {
837 *non_ground_points += *cell.points;
838 continue;
839 }
840
841 bool reject_as_floating = false;
842 int bz = cell_id.z - 1;
843 while (true) {
844 Index3D below(cell_id.x, cell_id.y, bz);
845 if (!checkIndex3DInGrid(below)) {
846 break; // Out of bound: stop looping
847 }
848 const auto & bcell = gridCells[below];
849 if (!bcell.points->empty() && bcell.terrain_type != TerrainType::GROUND) {
850 // Found an occupied non-ground cell below—reject as floating!
851 reject_as_floating = true;
852 break;
853 }
854 --bz;
855 }
856 if (reject_as_floating) {
857 *non_ground_points += *cell.points;
858 continue;
859 }
860 }
861 *ground_points += *ground_inliers;
862 *non_ground_points += *non_ground_inliers;
863 }
864
865 if (grid_config.processing_phase == 1)
866 {
867 for (const auto & cell_id : non_ground_cells)
868 {
869 const GridCell<PointT> & cell = gridCells[cell_id];
870
871 Index3D nearest_ground_id;
872
873 if (!findNearestGroundNeighbor(cell_id, nearest_ground_id)) {
874 *non_ground_points += *cell.points;
875 continue;
876 }
877
878 const auto & gcell = gridCells[nearest_ground_id];
879
880 for (const auto & pt : cell.points->points)
881 {
882 if (pointIsGroundWrtCell(pt, gcell))
883 ground_points->push_back(pt);
884 else
885 non_ground_points->push_back(pt);
886 }
887 }
888 }
889 else
890 {
891 for (const auto & cell_id : non_ground_cells) {
892 *non_ground_points += *gridCells[cell_id].points;
893 }
894 }
895 return std::make_pair(ground_points, non_ground_points);
896}
897
898} //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