Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
himmelsbach.cpp
Go to the documentation of this file.
2
3Himmelsbach::Himmelsbach(const double grid_angle, const double max_slope,
4 const double initial_alpha, const double alpha_augmentation_m,
5 const double start_augmentation, TrimmingParameters trim_params)
6 : grid_angle_(grid_angle),
7 max_slope_(max_slope),
8 initial_alpha_(initial_alpha),
9 alpha_augmentation_m_(alpha_augmentation_m),
10 start_augmentation_(start_augmentation),
11 trim_params_(trim_params) {
12 // Preallocate slices
13 const int num_slices = static_cast<int>(std::ceil(trim_params.fov / grid_angle));
14 slices_ = std::make_shared<std::vector<Slice>>(num_slices);
15}
16
18 const sensor_msgs::msg::PointCloud2::SharedPtr& trimmed_point_cloud,
19 sensor_msgs::msg::PointCloud2::SharedPtr& ground_removed_point_cloud,
20 GroundGrid& ground_grid) const {
21 // Reset ground grid
22 ground_grid.reset_grid();
23
24 // Initialize output cloud
25 ground_removed_point_cloud->header = trimmed_point_cloud->header;
26 ground_removed_point_cloud->height = 1;
27 ground_removed_point_cloud->is_bigendian = trimmed_point_cloud->is_bigendian;
28 ground_removed_point_cloud->is_dense = true;
29 ground_removed_point_cloud->point_step = trimmed_point_cloud->point_step;
30 ground_removed_point_cloud->row_step = 0;
31 ground_removed_point_cloud->fields = trimmed_point_cloud->fields;
32 ground_removed_point_cloud->width = 0;
33 ground_removed_point_cloud->data.resize(trimmed_point_cloud->width *
34 trimmed_point_cloud->point_step);
35
36 // Split point cloud into slices and rings
37 split_point_cloud(trimmed_point_cloud);
38
39 // Process each slice
40 for (size_t slice_idx = 0; slice_idx < slices_->size(); ++slice_idx) {
41 process_slice(trimmed_point_cloud, ground_removed_point_cloud, slice_idx, ground_grid);
42 }
43
44 // Resize output data to its final size
45 ground_removed_point_cloud->data.resize(ground_removed_point_cloud->width *
47 ground_removed_point_cloud->row_step = ground_removed_point_cloud->width * LidarPoint::POINT_STEP;
48}
49
51 const sensor_msgs::msg::PointCloud2::SharedPtr& trimmed_point_cloud,
52 sensor_msgs::msg::PointCloud2::SharedPtr& ground_removed_point_cloud, size_t slice_idx,
53 GroundGrid& ground_grid) const {
54 // The first ground point is assumed to be directly below the car
55 double previous_ground_point_x = 0.0;
56 double previous_ground_point_y = 0.0;
57 double previous_ground_point_z = -trim_params_.lidar_height;
58
59 auto& cloud_data = trimmed_point_cloud->data;
60 auto& output_data = ground_removed_point_cloud->data;
61
62 std::vector<int> ground_indices;
63 std::vector<double> ground_distances;
64
65 // For each ring in the slice, compare points to previous ground point
66 for (int ring_idx = ::NUM_RINGS - 1; ring_idx >= 0; --ring_idx) {
67 const auto& ring = slices_->at(slice_idx).rings[ring_idx];
68 if (ring.indices.empty()) {
69 continue;
70 }
71
72 int lowest_ground_idx_in_ring = -1;
73 float previous_ground_point_distance =
74 std::hypot(previous_ground_point_x, previous_ground_point_y);
75
76 for (int idx : ring.indices) {
77 float pt_x = *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointX(idx)]);
78 float pt_y = *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointY(idx)]);
79 float pt_z = *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointZ(idx)]);
80
81 float current_ground_point_distance = std::hypot(pt_x, pt_y);
82
83 if (!this->trim_params_.is_raining &&
84 (current_ground_point_distance - previous_ground_point_distance <
85 (-5 - (0.15 * ring_idx)) ||
86 current_ground_point_distance - previous_ground_point_distance >
87 (5 + (0.15 * ring_idx)))) {
88 // It is noise, ignore
89 continue;
90 } else if (current_ground_point_distance <= previous_ground_point_distance) {
91 // Write to the output cloud
92 size_t write_idx = ground_removed_point_cloud->width;
93 static_cast<void>(std::memcpy(&output_data[write_idx * LidarPoint::POINT_STEP],
94 &cloud_data[idx * LidarPoint::POINT_STEP],
96 ground_removed_point_cloud->width++;
97 } else {
98 // Compute slope to previous ground point
99 float dz = pt_z - previous_ground_point_z;
100 float dr = current_ground_point_distance - previous_ground_point_distance;
101 if (dr < 0.001) {
102 dr = 0.001;
103 }
104
105 float slope = std::abs(dz / dr);
106
107 if (slope < this->max_slope_) {
108 // Point is a ground candidate
109 ground_indices.push_back(idx);
110 ground_distances.push_back(current_ground_point_distance);
111 } else {
112 // Point is non-ground, write to output cloud
113 size_t write_idx = ground_removed_point_cloud->width;
114 static_cast<void>(std::memcpy(&output_data[write_idx * LidarPoint::POINT_STEP],
115 &cloud_data[idx * LidarPoint::POINT_STEP],
117 ground_removed_point_cloud->width++;
118 }
119 }
120 }
121
122 // Evaluate ground candidates: compare each to the median distance of the farthest 10%.
123 // Points much closer than this reference are obstacles, not ground.
124 if (!ground_indices.empty()) {
125 int num_points = static_cast<int>(ground_distances.size());
126 int top_k = std::max<int>(1, num_points / 10);
127
128 // Partition so that the top 10% farthest values are at the end
129 std::nth_element(ground_distances.begin(), ground_distances.end() - top_k,
130 ground_distances.end());
131
132 // Median
133 std::vector<double> top_farthest(ground_distances.end() - top_k, ground_distances.end());
134 std::sort(top_farthest.begin(), top_farthest.end());
135 double r_ref = top_farthest[top_farthest.size() / 2];
136
137 // Evaluate each ground candidate compared to the reference distance
138 for (int idx : ground_indices) {
139 float gx = *reinterpret_cast<const float*>(&cloud_data[LidarPoint::PointX(idx)]);
140 float gy = *reinterpret_cast<const float*>(&cloud_data[LidarPoint::PointY(idx)]);
141 float gz = *reinterpret_cast<const float*>(&cloud_data[LidarPoint::PointZ(idx)]);
142 double r = std::hypot(gx, gy);
143
144 double threshold = this->initial_alpha_;
145 if (r > this->start_augmentation_) {
146 threshold += this->alpha_augmentation_m_ * (r - this->start_augmentation_);
147 }
148
149 if (r < r_ref - threshold) {
150 // too far from reference, non-ground
151 size_t write_idx = ground_removed_point_cloud->width;
152 static_cast<void>(std::memcpy(&output_data[write_idx * LidarPoint::POINT_STEP],
153 &cloud_data[idx * LidarPoint::POINT_STEP],
155 ground_removed_point_cloud->width++;
156 } else {
157 // Ground, update the ground grid and lowest ground point
158 ground_grid.set_ground_height(gx, gy, gz);
159 if (lowest_ground_idx_in_ring == -1 ||
160 gz < *reinterpret_cast<const float*>(
161 &cloud_data[LidarPoint::PointZ(lowest_ground_idx_in_ring)])) {
162 lowest_ground_idx_in_ring = idx;
163 }
164 }
165 }
166
167 if (lowest_ground_idx_in_ring != -1) {
168 previous_ground_point_x =
169 *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointX(lowest_ground_idx_in_ring)]);
170 previous_ground_point_y =
171 *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointY(lowest_ground_idx_in_ring)]);
172 previous_ground_point_z =
173 *reinterpret_cast<float*>(&cloud_data[LidarPoint::PointZ(lowest_ground_idx_in_ring)]);
174 }
175
176 ground_indices.clear();
177 ground_distances.clear();
178 }
179 }
180}
181
183 const sensor_msgs::msg::PointCloud2::SharedPtr& input_cloud) const {
184 // Clear previous indices
185 for (auto& slice : *slices_) {
186 for (auto& ring : slice.rings) {
187 ring.indices.clear();
188 }
189 }
190
191 const size_t num_points = input_cloud->width * input_cloud->height;
192 if (num_points == 0) {
193 return;
194 }
195
196 const auto& cloud = input_cloud->data;
197 double slice_angle_rad = grid_angle_ * (M_PI / 180.0);
198
199 // Calculate the starting azimuth based on FOV
200 double fov_rad = trim_params_.fov * (M_PI / 180.0);
201 double min_azimuth = -(fov_rad / 2.0);
202
203 for (size_t i = 0; i < num_points; ++i) {
204 float x = *reinterpret_cast<const float*>(&cloud[LidarPoint::PointX(i)]);
205 float y = *reinterpret_cast<const float*>(&cloud[LidarPoint::PointY(i)]);
206 uint16_t ring = *reinterpret_cast<const uint16_t*>(&cloud[LidarPoint::PointRing(i)]);
207
208 double azimuth = std::atan2(y, x);
209 double relative_azimuth = azimuth - min_azimuth;
210
211 if (relative_azimuth < 0 || relative_azimuth >= fov_rad) {
212 continue;
213 }
214
215 int slice_idx = static_cast<int>(relative_azimuth / slice_angle_rad);
216
217 if (slice_idx >= 0 && slice_idx < static_cast<int>(slices_->size())) {
218 slices_->at(slice_idx).rings[ring].indices.push_back(i);
219 }
220 }
221}
Class to represent a ground height grid for ground proximity checks.
void set_ground_height(const float x, const float y, const float height)
Set the ground height at a specific (x, y) location.
void reset_grid()
Reset the ground grid to initial state.
Himmelsbach()=default
double initial_alpha_
double alpha_augmentation_m_
std::shared_ptr< std::vector< Slice > > slices_
double max_slope_
TrimmingParameters trim_params_
double start_augmentation_
void ground_removal(const sensor_msgs::msg::PointCloud2::SharedPtr &trimmed_point_cloud, sensor_msgs::msg::PointCloud2::SharedPtr &ground_removed_point_cloud, GroundGrid &ground_grid) const override
Perform ground removal on a point cloud.
double grid_angle_
void process_slice(const sensor_msgs::msg::PointCloud2::SharedPtr &trimmed_point_cloud, sensor_msgs::msg::PointCloud2::SharedPtr &ground_removed_point_cloud, size_t slice_idx, GroundGrid &ground_grid) const
Process a single slice for ground removal.
void split_point_cloud(const sensor_msgs::msg::PointCloud2::SharedPtr &input_cloud) const
Split the input point cloud into slices and rings.
int constexpr NUM_RINGS
constexpr size_t PointZ(size_t idx)
constexpr size_t POINT_STEP
constexpr size_t PointX(size_t idx)
constexpr size_t PointY(size_t idx)
constexpr size_t PointRing(size_t idx)
Structure to hold parameters for trimming point cloud data.
bool is_raining
Whether it is raining.
double fov
Field of view.
double lidar_height
LIDAR current height.