-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathground_plane_fitting.cpp
149 lines (124 loc) · 5.37 KB
/
ground_plane_fitting.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include "ground_plane_fitting.h"
GroundPlaneFit::GroundPlaneFit() {
num_seg_ = 1; // Divide evenly the point cloud into a number of segments // TODO Write this implementation
num_iter_ = 3; // Number of iterations
num_lpr_ = 250; // number of points used to estimate the LPR
th_seeds_ = 1.2; // Threshold for points to be considered initial seeds
th_dist_ = 0.3; // Threshold distance from the plane
sensor_height_ = 2.0; // LiDAR sensor height to ground
}
GroundPlaneFit::~GroundPlaneFit() {}
/*
* @brief Use the set of seed points to estimate the initial plane model
* of the ground surface.
*/
model_t GroundPlaneFit::estimatePlane(const pcl::PointCloud<pcl::PointXYZI>& seed_points) {
Eigen::Matrix3f cov_matrix(3, 3);
Eigen::Vector4f points_mean;
model_t model;
// Compute covariance matrix in single pass
pcl::computeMeanAndCovarianceMatrix(seed_points, cov_matrix, points_mean);
// Singular Value Decomposition: SVD
Eigen::JacobiSVD<Eigen::MatrixXf> svd(cov_matrix, Eigen::DecompositionOptions::ComputeFullU);
// Use the least singular vector as normal
model.normal_n = (svd.matrixU().col(2));
// Mean ground seeds value
Eigen::Vector3f seeds_mean = points_mean.head<3>();
// According to normal.T * [x,y,z] = -d
model.d = -(model.normal_n.transpose() * seeds_mean)(0, 0);
return model;
}
/*
* @brief Extract a set of seed points with low height values.
*/
void GroundPlaneFit::extractInitialSeeds(const pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_in,
const pcl::PointCloud<pcl::PointXYZI>::Ptr seed_points) {
// Sort on z-axis values (sortOnHeight)
std::vector<pcl::PointXYZI> cloud_sorted((*cloud_in).points.begin(), (*cloud_in).points.end());
sort(cloud_sorted.begin(), cloud_sorted.end(),
[](pcl::PointXYZI p1, pcl::PointXYZI p2) {
// Similar to defining a bool function
return p1.z < p2.z;
}
);
// Negative outlier error point removal.
// As there might be some error mirror reflection under the ground
std::vector<pcl::PointXYZI>::iterator it = cloud_sorted.begin();
for(size_t i = 0; i < cloud_sorted.size(); ++i) {
// We define the outlier threshold -1.5 times the height of the LiDAR sensor
if (cloud_sorted[i].z < -1.5 * sensor_height_) {
it++;
} else {
// Points are in incremental order. Therefore, break loop if here
break;
}
}
// Remove the outlier points
cloud_sorted.erase(cloud_sorted.begin(), it);
// Find the Lowest Point Representive (LPR) of the sorted point cloud
double LPR_height = 0.;
for(int i = 0; i < num_lpr_; i ++) {
LPR_height += cloud_sorted[i].z;
}
LPR_height /= num_lpr_;
// Iterate, filter for height less than LPR_height + th_seeds_
(*seed_points).clear();
for (size_t i = 0; i < cloud_sorted.size(); ++i) {
if (cloud_sorted[i].z < LPR_height + th_seeds_) {
(*seed_points).points.push_back(cloud_sorted[i]);
}
}
}
/**
* @brief Ground Removal based on Ground Plane Fitting (GPF).
* @refer
* Fast Segmentation of 3D Point Clouds: A Paradigm on LiDAR Data for
* Autonomous Vehicle Applications (ICRA, 2017)
*/
void GroundPlaneFit::mainLoop(const pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_in,
pcl::PointCloud<pcl::PointXYZI>::Ptr notground_points,
pcl::PointCloud<pcl::PointXYZI>::Ptr ground_points) {
pcl::PointCloud<pcl::PointXYZI>::Ptr seed_points(new pcl::PointCloud<pcl::PointXYZI>());
/// 1. Extract initial ground seeds
extractInitialSeeds(cloud_in, seed_points);
/// 2. Ground plane fit mainloop
// The points belonging to the ground surface are used as seeds
// for the refined estimation of a new plane model and the process
// repeats for num_iter_ number of times
for (int i = 0; i < num_iter_; ++i) {
/// 3. Estimate the plane model
model_t model = estimatePlane(*seed_points);
ground_points->clear();
notground_points->clear();
// Pointcloud to matrix
Eigen::MatrixXf points_matrix(cloud_in->points.size(), 3);
size_t j = 0u;
for (auto p : (*cloud_in).points) {
points_matrix.row(j++) << p.x, p.y, p.z;
}
// Ground plane model
Eigen::VectorXf result = points_matrix * model.normal_n;
// Threshold filter: N^T xi + d = dist < th_dist ==> N^T xi < th_dist - d
double th_dist_d_ = th_dist_ - model.d;
/// 4. Identify which of the points belong to the ground and non ground
for (int k = 0; k < result.rows(); ++k) {
if (result[k] < th_dist_d_) {
// TODO think about a more optimized code for this part
pcl::PointXYZI point;
point.x = cloud_in->points[k].x;
point.y = cloud_in->points[k].y;
point.z = cloud_in->points[k].z;
point.intensity = cloud_in->points[k].intensity;
ground_points->points.push_back(point);
}
else {
pcl::PointXYZI point;
point.x = cloud_in->points[k].x;
point.y = cloud_in->points[k].y;
point.z = cloud_in->points[k].z;
point.intensity = cloud_in->points[k].intensity;
notground_points->points.push_back(point);
}
}
}
}