Skip to content

Commit 71ddb01

Browse files
yukkysaitopre-commit-ci[bot]mitsudome-r
authored andcommitted
feat(pointcloud_preprocessor): add pickup based voxel grid filter (autowarefoundation#6138)
* add pickup based voxel grid filter Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * style(pre-commit): autofix * add comment Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * add readme Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * add comment Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * Update sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/downsample_filter/pickup_based_voxel_grid_downsample_filter.hpp Co-authored-by: Ryohsuke Mitsudome <43976834+mitsudome-r@users.noreply.github.com> * Update sensing/pointcloud_preprocessor/src/downsample_filter/pickup_based_voxel_grid_downsample_filter.cpp * style(pre-commit): autofix * tuning for processing time Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * change to robin_hood Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * style(pre-commit): autofix * fix: ignore cpplint for external file Signed-off-by: Ryohsuke Mitsudome <ryohsuke.mitsudome@tier4.jp> * fix cspell Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> * style(pre-commit): autofix * fix cspell error Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> --------- Signed-off-by: Yukihito Saito <yukky.saito@gmail.com> Signed-off-by: Ryohsuke Mitsudome <ryohsuke.mitsudome@tier4.jp> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ryohsuke Mitsudome <43976834+mitsudome-r@users.noreply.github.com> Co-authored-by: Ryohsuke Mitsudome <ryohsuke.mitsudome@tier4.jp>
1 parent cc79cb8 commit 71ddb01

File tree

5 files changed

+3027
-1
lines changed

5 files changed

+3027
-1
lines changed

sensing/pointcloud_preprocessor/CMakeLists.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ ament_auto_add_library(pointcloud_preprocessor_filter SHARED
6565
src/downsample_filter/voxel_grid_downsample_filter_nodelet.cpp
6666
src/downsample_filter/random_downsample_filter_nodelet.cpp
6767
src/downsample_filter/approximate_downsample_filter_nodelet.cpp
68+
src/downsample_filter/pickup_based_voxel_grid_downsample_filter.cpp
6869
src/outlier_filter/ring_outlier_filter_nodelet.cpp
6970
src/outlier_filter/voxel_grid_outlier_filter_nodelet.cpp
7071
src/outlier_filter/radius_search_2d_outlier_filter_nodelet.cpp
@@ -114,6 +115,11 @@ rclcpp_components_register_node(pointcloud_preprocessor_filter
114115
PLUGIN "pointcloud_preprocessor::VoxelGridDownsampleFilterComponent"
115116
EXECUTABLE voxel_grid_downsample_filter_node)
116117

118+
# -- Pickup Based Voxel Grid Downsample Filter --
119+
rclcpp_components_register_node(pointcloud_preprocessor_filter
120+
PLUGIN "pointcloud_preprocessor::PickupBasedVoxelGridDownsampleFilterComponent"
121+
EXECUTABLE pickup_based_voxel_grid_downsample_filter_node)
122+
117123
# -- Random Downsample Filter --
118124
rclcpp_components_register_node(pointcloud_preprocessor_filter
119125
PLUGIN "pointcloud_preprocessor::RandomDownsampleFilterComponent"
@@ -214,7 +220,6 @@ ament_auto_package(INSTALL_TO_SHARE
214220
config
215221
)
216222

217-
218223
if(BUILD_TESTING)
219224
add_ros_test(
220225
test/test_distortion_corrector.py

sensing/pointcloud_preprocessor/docs/downsample-filter.md

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ The `downsample_filter` is a node that reduces the number of points.
1818

1919
`pcl::VoxelGrid` is used, which points in each voxel are approximated with their centroid.
2020

21+
### Pickup Based Voxel Grid Downsample Filter
22+
23+
This algorithm samples a single actual point existing within the voxel, not the centroid. The computation cost is low compared to Centroid Based Voxel Grid Filter.
24+
2125
## Inputs / Outputs
2226

2327
These implementations inherit `pointcloud_preprocessor::Filter` class, please refer [README](../README.md).
@@ -52,8 +56,20 @@ These implementations inherit `pointcloud_preprocessor::Filter` class, please re
5256
| `voxel_size_y` | double | 0.3 | voxel size y [m] |
5357
| `voxel_size_z` | double | 0.1 | voxel size z [m] |
5458

59+
### Pickup Based Voxel Grid Downsample Filter
60+
61+
| Name | Type | Default Value | Description |
62+
| -------------- | ------ | ------------- | ---------------- |
63+
| `voxel_size_x` | double | 1.0 | voxel size x [m] |
64+
| `voxel_size_y` | double | 1.0 | voxel size y [m] |
65+
| `voxel_size_z` | double | 1.0 | voxel size z [m] |
66+
5567
## Assumptions / Known limits
5668

69+
<!-- cspell: ignore martinus -->
70+
71+
This implementation uses the `robin_hood.h` hashing library by martinus, available under the MIT License at [martinus/robin-hood-hashing](https://github.com/martinus/robin-hood-hashing) on GitHub. Special thanks to martinus for this contribution.
72+
5773
## (Optional) Error detection and handling
5874

5975
## (Optional) Performance characterization
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2023 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef POINTCLOUD_PREPROCESSOR__DOWNSAMPLE_FILTER__PICKUP_BASED_VOXEL_GRID_DOWNSAMPLE_FILTER_HPP_
16+
#define POINTCLOUD_PREPROCESSOR__DOWNSAMPLE_FILTER__PICKUP_BASED_VOXEL_GRID_DOWNSAMPLE_FILTER_HPP_
17+
18+
#include "pointcloud_preprocessor/filter.hpp"
19+
20+
#include <Eigen/Core>
21+
22+
#include <pcl_conversions/pcl_conversions.h>
23+
#include <sensor_msgs/msg/point_cloud2.h>
24+
25+
#include <algorithm>
26+
#include <cmath>
27+
#include <vector>
28+
29+
namespace pointcloud_preprocessor
30+
{
31+
/**
32+
* @class PickupBasedVoxelGridDownsampleFilterComponent
33+
* @brief A filter component for downsampling point clouds using a voxel grid approach.
34+
*
35+
* This component reduces the number of points in a point cloud by grouping them into voxels
36+
* and picking a representative point for each voxel. It's useful for reducing computational
37+
* load when processing large point clouds.
38+
*/
39+
class PickupBasedVoxelGridDownsampleFilterComponent : public pointcloud_preprocessor::Filter
40+
{
41+
protected:
42+
void filter(
43+
const PointCloud2ConstPtr & input, const IndicesPtr & indices, PointCloud2 & output) override;
44+
45+
private:
46+
float voxel_size_x_; ///< The size of the voxel in the x dimension.
47+
float voxel_size_y_; ///< The size of the voxel in the y dimension.
48+
float voxel_size_z_; ///< The size of the voxel in the z dimension.
49+
50+
/** \brief Parameter service callback result : needed to be hold */
51+
OnSetParametersCallbackHandle::SharedPtr set_param_res_;
52+
53+
/** \brief Parameter service callback */
54+
rcl_interfaces::msg::SetParametersResult paramCallback(const std::vector<rclcpp::Parameter> & p);
55+
56+
public:
57+
PCL_MAKE_ALIGNED_OPERATOR_NEW
58+
explicit PickupBasedVoxelGridDownsampleFilterComponent(const rclcpp::NodeOptions & options);
59+
};
60+
} // namespace pointcloud_preprocessor
61+
62+
// clang-format off
63+
#endif // POINTCLOUD_PREPROCESSOR__DOWNSAMPLE_FILTER__PICKUP_BASED_VOXEL_GRID_DOWNSAMPLE_FILTER_HPP_ // NOLINT
64+
// clang-format on
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright 2024 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "pointcloud_preprocessor/downsample_filter/pickup_based_voxel_grid_downsample_filter.hpp"
16+
17+
#include "robin_hood.h"
18+
19+
namespace
20+
{
21+
/**
22+
* @brief Hash function for voxel keys.
23+
* Utilizes prime numbers to calculate a unique hash for each voxel key.
24+
*/
25+
struct VoxelKeyHash
26+
{
27+
std::size_t operator()(const std::array<int, 3> & k) const
28+
{
29+
// Primes based on the following paper: 'Investigating the Use of Primes in Hashing for
30+
// Volumetric Data'.
31+
return (k[0] * 73856093 ^ k[1] * 19349663 ^ k[2] * 83492791);
32+
// In general, the performance of the search may be improved by restricting the hashkey to the
33+
// following However, the risk of key collisions also increases, so the value must be
34+
// appropriate. Enable the following code depending on the situation. return ((1 << 16) - 1) &
35+
// (k[0] * 73856093 ^ k[1] * 19349663 ^ k[2] * 83492791);
36+
}
37+
};
38+
39+
/**
40+
* @brief Equality function for voxel keys.
41+
* Checks if two voxel keys are equal.
42+
*/
43+
struct VoxelKeyEqual
44+
{
45+
bool operator()(const std::array<int, 3> & a, const std::array<int, 3> & b) const
46+
{
47+
return a == b;
48+
}
49+
};
50+
} // namespace
51+
52+
namespace pointcloud_preprocessor
53+
{
54+
PickupBasedVoxelGridDownsampleFilterComponent::PickupBasedVoxelGridDownsampleFilterComponent(
55+
const rclcpp::NodeOptions & options)
56+
: Filter("PickupBasedVoxelGridDownsampleFilterComponent", options)
57+
{
58+
// initialize debug tool
59+
{
60+
using tier4_autoware_utils::DebugPublisher;
61+
using tier4_autoware_utils::StopWatch;
62+
stop_watch_ptr_ = std::make_unique<StopWatch<std::chrono::milliseconds>>();
63+
debug_publisher_ = std::make_unique<DebugPublisher>(this, this->get_name());
64+
stop_watch_ptr_->tic("cyclic_time");
65+
stop_watch_ptr_->tic("processing_time");
66+
}
67+
68+
// Initialization of voxel sizes from parameters
69+
voxel_size_x_ = static_cast<float>(declare_parameter("voxel_size_x", 1.0));
70+
voxel_size_y_ = static_cast<float>(declare_parameter("voxel_size_y", 1.0));
71+
voxel_size_z_ = static_cast<float>(declare_parameter("voxel_size_z", 1.0));
72+
73+
using std::placeholders::_1;
74+
set_param_res_ = this->add_on_set_parameters_callback(
75+
std::bind(&PickupBasedVoxelGridDownsampleFilterComponent::paramCallback, this, _1));
76+
}
77+
78+
void PickupBasedVoxelGridDownsampleFilterComponent::filter(
79+
const PointCloud2ConstPtr & input, [[maybe_unused]] const IndicesPtr & indices,
80+
PointCloud2 & output)
81+
{
82+
std::scoped_lock lock(mutex_);
83+
84+
stop_watch_ptr_->toc("processing_time", true);
85+
86+
using VoxelKey = std::array<int, 3>;
87+
// std::unordered_map<VoxelKey, size_t, VoxelKeyHash, VoxelKeyEqual> voxel_map;
88+
robin_hood::unordered_map<VoxelKey, size_t, VoxelKeyHash, VoxelKeyEqual> voxel_map;
89+
90+
voxel_map.reserve(input->data.size() / input->point_step);
91+
92+
constexpr float large_num_offset = 100000.0;
93+
const float inverse_voxel_size_x = 1.0 / voxel_size_x_;
94+
const float inverse_voxel_size_y = 1.0 / voxel_size_y_;
95+
const float inverse_voxel_size_z = 1.0 / voxel_size_z_;
96+
97+
const int x_offset = input->fields[pcl::getFieldIndex(*input, "x")].offset;
98+
const int y_offset = input->fields[pcl::getFieldIndex(*input, "y")].offset;
99+
const int z_offset = input->fields[pcl::getFieldIndex(*input, "z")].offset;
100+
101+
// Process each point in the point cloud
102+
for (size_t global_offset = 0; global_offset + input->point_step <= input->data.size();
103+
global_offset += input->point_step) {
104+
const float & x = *reinterpret_cast<const float *>(&input->data[global_offset + x_offset]);
105+
const float & y = *reinterpret_cast<const float *>(&input->data[global_offset + y_offset]);
106+
const float & z = *reinterpret_cast<const float *>(&input->data[global_offset + z_offset]);
107+
108+
// The reason for adding a large value is that when converting from float to int, values around
109+
// -1 to 1 are all rounded down to 0. Therefore, to prevent the numbers from becoming negative,
110+
// a large value is added. It has been tuned to reduce computational costs, and deliberately
111+
// avoids using round or floor functions.
112+
VoxelKey key = {
113+
static_cast<int>((x + large_num_offset) * inverse_voxel_size_x),
114+
static_cast<int>((y + large_num_offset) * inverse_voxel_size_y),
115+
static_cast<int>((z + large_num_offset) * inverse_voxel_size_z)};
116+
117+
voxel_map.emplace(key, global_offset);
118+
}
119+
120+
// Populate the output point cloud
121+
size_t output_global_offset = 0;
122+
output.data.resize(voxel_map.size() * input->point_step);
123+
for (const auto & kv : voxel_map) {
124+
std::memcpy(
125+
&output.data[output_global_offset + x_offset], &input->data[kv.second + x_offset],
126+
sizeof(float));
127+
std::memcpy(
128+
&output.data[output_global_offset + y_offset], &input->data[kv.second + y_offset],
129+
sizeof(float));
130+
std::memcpy(
131+
&output.data[output_global_offset + z_offset], &input->data[kv.second + z_offset],
132+
sizeof(float));
133+
output_global_offset += input->point_step;
134+
}
135+
136+
// Set the output point cloud metadata
137+
output.header.frame_id = input->header.frame_id;
138+
output.height = 1;
139+
output.fields = input->fields;
140+
output.is_bigendian = input->is_bigendian;
141+
output.point_step = input->point_step;
142+
output.is_dense = input->is_dense;
143+
output.width = static_cast<uint32_t>(output.data.size() / output.height / output.point_step);
144+
output.row_step = static_cast<uint32_t>(output.data.size() / output.height);
145+
146+
// add processing time for debug
147+
if (debug_publisher_) {
148+
const double cyclic_time_ms = stop_watch_ptr_->toc("cyclic_time", true);
149+
const double processing_time_ms = stop_watch_ptr_->toc("processing_time", true);
150+
debug_publisher_->publish<tier4_debug_msgs::msg::Float64Stamped>(
151+
"debug/cyclic_time_ms", cyclic_time_ms);
152+
debug_publisher_->publish<tier4_debug_msgs::msg::Float64Stamped>(
153+
"debug/processing_time_ms", processing_time_ms);
154+
155+
auto pipeline_latency_ms =
156+
std::chrono::duration<double, std::milli>(
157+
std::chrono::nanoseconds((this->get_clock()->now() - input->header.stamp).nanoseconds()))
158+
.count();
159+
160+
debug_publisher_->publish<tier4_debug_msgs::msg::Float64Stamped>(
161+
"debug/pipeline_latency_ms", pipeline_latency_ms);
162+
}
163+
}
164+
165+
rcl_interfaces::msg::SetParametersResult
166+
PickupBasedVoxelGridDownsampleFilterComponent::paramCallback(
167+
const std::vector<rclcpp::Parameter> & p)
168+
{
169+
std::scoped_lock lock(mutex_);
170+
171+
// Handling dynamic updates for the voxel sizes
172+
if (get_param(p, "voxel_size_x", voxel_size_x_)) {
173+
RCLCPP_DEBUG(get_logger(), "Setting new distance threshold to: %f.", voxel_size_x_);
174+
}
175+
if (get_param(p, "voxel_size_y", voxel_size_y_)) {
176+
RCLCPP_DEBUG(get_logger(), "Setting new distance threshold to: %f.", voxel_size_y_);
177+
}
178+
if (get_param(p, "voxel_size_z", voxel_size_z_)) {
179+
RCLCPP_DEBUG(get_logger(), "Setting new distance threshold to: %f.", voxel_size_z_);
180+
}
181+
182+
rcl_interfaces::msg::SetParametersResult result;
183+
result.successful = true;
184+
result.reason = "success";
185+
186+
return result;
187+
}
188+
189+
} // namespace pointcloud_preprocessor
190+
191+
#include <rclcpp_components/register_node_macro.hpp>
192+
RCLCPP_COMPONENTS_REGISTER_NODE(
193+
pointcloud_preprocessor::PickupBasedVoxelGridDownsampleFilterComponent)

0 commit comments

Comments
 (0)