Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create velodyne cloud separator #47

Merged
merged 7 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/perception/velodyne_cloud_separator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.5)
project(velodyne_cloud_separator)

if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()

ament_auto_add_library(velodyne_cloud_separator SHARED
src/velodyne_cloud_separator.cpp
)

rclcpp_components_register_node(velodyne_cloud_separator
PLUGIN "velodyne_cloud_separator::VelodyneCloudSeparator"
EXECUTABLE velodyne_cloud_separator_node
)

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()

ament_auto_package(INSTALL_TO_SHARE
config
launch
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**:
ros__parameters:
update_rate_hz: 20.0
sensor_height_m: 0.68
radius_coef_close: 0.2
radius_coef_far: 0.7
max_slope_deg: 10.0
gap_threshold_m: 0.2
min_points_per_cluster: 2
18 changes: 18 additions & 0 deletions src/perception/velodyne_cloud_separator/include/point_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef POINT_TYPES_HPP_
#define POINT_TYPES_HPP_

#include <pcl/point_types.h>

struct PointXYZIR
{
PCL_ADD_POINT4D;
float intensity;
uint16_t ring;
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT(
PointXYZIR,
(float, x, x)(float, y, y)(float, z, z)(float, intensity, intensity)(uint16_t, ring, ring))

#endif // POINT_TYPES_HPP_
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#ifndef VELODYNE_CLOUD_SEPARATOR__VELODYNE_CLOUD_SEPARATOR_HPP_
#define VELODYNE_CLOUD_SEPARATOR__VELODYNE_CLOUD_SEPARATOR_HPP_

#include "point_types.hpp"

#include <rclcpp/rclcpp.hpp>
#include <wheel_stuck_utils/ros/no_callback_subscription.hpp>

#include <sensor_msgs/msg/point_cloud2.hpp>

#include <pcl/point_cloud.h>

#ifndef LASERS_NUM
#define LASERS_NUM 32
#endif

namespace velodyne_cloud_separator
{

using PointCloud2 = sensor_msgs::msg::PointCloud2;

using PointCloud2Subscription = wheel_stuck_utils::ros::NoCallbackSubscription<PointCloud2>;
using PointCloud2Publisher = rclcpp::Publisher<PointCloud2>;

class VelodyneCloudSeparator : public rclcpp::Node
{
private:
enum class PointType { UNKNOWN = 0, GROUND = 1, OBSTACLE = 2 };

public:
explicit VelodyneCloudSeparator(const rclcpp::NodeOptions & options);

private:
void init(
const double sensor_height, const double radius_coef_close, const double radius_coef_far);
void update();
bool try_subscribe_pc();

rclcpp::TimerBase::SharedPtr update_timer_;

PointCloud2Subscription::SharedPtr pc_sub_;
PointCloud2Publisher::SharedPtr pc_ground_pub_;
PointCloud2Publisher::SharedPtr pc_obstacle_pub_;

pcl::PointCloud<PointXYZIR>::Ptr pc_;

static const int height_ = LASERS_NUM;

double radius_array_[LASERS_NUM];

double limiting_ratio_;
double gap_threshold_;
int min_points_num_;
};
} // namespace velodyne_cloud_separator

#endif // VELODYNE_CLOUD_SEPARATOR__VELODYNE_CLOUD_SEPARATOR_HPP_
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<launch>
<arg name="points_topic" default="/velodyne_points"/>
<arg name="ground_points_topic" default="/velodyne_points/ground"/>
<arg name="obstacle_points_topic" default="/velodyne_points/obstacle"/>

<arg name="config_file" default="$(find-pkg-share velodyne_cloud_separator)/config/velodyne_cloud_separator.param.yaml"/>

<node pkg="velodyne_cloud_separator" name="velodyne_cloud_separator" exec="velodyne_cloud_separator_node" output="screen">
<remap from="~/input/points" to="$(var points_topic)"/>
<remap from="~/output/ground_points" to="$(var ground_points_topic)"/>
<remap from="~/output/obstacle_points" to="$(var obstacle_points_topic)"/>
<param from="$(var config_file)"/>
</node>
</launch>
24 changes: 24 additions & 0 deletions src/perception/velodyne_cloud_separator/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>velodyne_cloud_separator</name>
<version>0.0.0</version>
<description>The velodyne_cloud_separator package</description>
<maintainer email="[email protected]">Akiro Harada</maintainer>

<license>Apache 2</license>

<buildtool_depend>ament_cmake_auto</buildtool_depend>

<depend>pcl_conversions</depend>
<depend>pcl_ros</depend>
<depend>rclcpp</depend>
<depend>rclcpp_components</depend>
<depend>sensor_msgs</depend>
<depend>wheel_stuck_utils</depend>
<test_depend>ament_lint_auto</test_depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#include "velodyne_cloud_separator/velodyne_cloud_separator.hpp"

#include <pcl_conversions/pcl_conversions.h>

#include <limits>
#include <vector>

namespace velodyne_cloud_separator
{
VelodyneCloudSeparator::VelodyneCloudSeparator(const rclcpp::NodeOptions & options)
: Node("velodyne_cloud_separator", options)
{
// Declare Parameters
double update_rate_hz = this->declare_parameter("update_rate_hz", 20.0);
double sensor_height = this->declare_parameter("sensor_height_m", 1.0);
double radius_coef_close = this->declare_parameter("radius_coef_close", 0.2);
double radius_coef_far = this->declare_parameter("radius_coef_far", 0.7);
float max_slope_deg = this->declare_parameter("max_slope_deg", 15.0);
limiting_ratio_ = tan(max_slope_deg * M_PI / 180.0);
gap_threshold_ = this->declare_parameter("gap_threshold_m", 0.1);
min_points_num_ = this->declare_parameter("min_points_per_cluster", 2);

// Declare Subscriptions
{
pc_sub_ = PointCloud2Subscription::create_subscription(this, "~/input/points");
}

// Declare Publishers
{
pc_ground_pub_ = this->create_publisher<PointCloud2>("~/output/ground_points", 1);
pc_obstacle_pub_ = this->create_publisher<PointCloud2>("~/output/obstacle_points", 1);
}

// Declare timer for update function
{
auto update_callback = [this]() { this->update(); };
auto period = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::duration<double>(1.0 / update_rate_hz));
update_timer_ = std::make_shared<rclcpp::GenericTimer<decltype(update_callback)>>(
this->get_clock(), period, std::move(update_callback),
this->get_node_base_interface()->get_context());
this->get_node_timers_interface()->add_timer(update_timer_, nullptr);
}

// Initialize
init(sensor_height, radius_coef_close, radius_coef_far);
}

void VelodyneCloudSeparator::init(
const double sensor_height, const double radius_coef_close, const double radius_coef_far)
{
// HDL-32E unique parameters
double angle_start = 92.0 / 3.0 * M_PI / 180.0;
double angle_diff = 4.0 / 3.0 * M_PI / 180.0;

radius_array_[0] = std::numeric_limits<double>::max();
for (int i = 1; i < height_; i++) {
if (i > 20) {
radius_array_[i] = radius_array_[20];
continue;
}

double angle = angle_start - angle_diff * i;
radius_array_[i] = sensor_height * (1.0 / tan(angle) - 1.0 / tan(angle + angle_diff));
radius_array_[i] *= (i <= 12 ? radius_coef_close : radius_coef_far);
}
}

void VelodyneCloudSeparator::update()
{
if (!try_subscribe_pc()) return;

int points_size = pc_->points.size();

const int width = static_cast<int>(points_size * 1.8 / height_);

// Create index map
std::vector<std::vector<int>> index_map(height_, std::vector<int>(width, -1));
for (int i = 0; i < points_size; i++) {
const auto & point = pc_->points[i];
float angle = atan2(point.y, point.x) * 180.0 / M_PI;
if (angle < 0.0) angle += 360.0;
int row = height_ - 1 - point.ring;
int col = width - 1 - static_cast<int>(static_cast<double>(width) * angle / 360.0);
index_map[row][col] = i;
}

// Separate points
pcl::PointCloud<pcl::PointXYZ>::Ptr ground_points(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr obstacle_points(new pcl::PointCloud<pcl::PointXYZ>);
for (int i = 0; i < width; i++) {
PointType point_types[height_] = {PointType::UNKNOWN};
int point_indices[height_] = {0};
int point_indices_size = 0;
int unknown_indices[height_] = {0};
int unknown_indices_size = 0;
double z_ref = 0.0;
double r_ref = 0.0;

for (int j = height_ - 1; j >= 0; j--) {
auto & point = pc_->points[index_map[j][i]];
if (index_map[j][i] > -1 && point_types[j] == PointType::UNKNOWN) {
const float x0 = point.x;
const float y0 = point.y;
const float z0 = point.z;
const float r0 = sqrt(x0 * x0 + y0 * y0);
const float r_diff = r0 - r_ref;
const float z_diff = fabs(z0 - z_ref);
const float angle = z_diff / r_diff;

if (
!(angle > 0 && angle < limiting_ratio_ && z_diff < gap_threshold_) ||
point_indices_size != 0) {
for (int k = 0; k < point_indices_size; k++) {
int index = index_map[point_indices[k]][i];
if (point_indices_size > min_points_num_) {
auto & p = pc_->points[index];
pcl::PointXYZ p_xyz;
p_xyz.x = p.x;
p_xyz.y = p.y;
p_xyz.z = p.z;
ground_points->push_back(p_xyz);
point_types[point_indices[k]] = PointType::GROUND;
} else {
unknown_indices[unknown_indices_size++] = index;
}
}
point_indices_size = 0;
}
point_indices[point_indices_size++] = j;
z_ref = z0;
r_ref = r0;
}
if (j != 0) continue;

if (point_indices_size != 0) {
for (int k = 0; k < point_indices_size; k++) {
int index = index_map[point_indices[k]][i];
if (point_indices_size > min_points_num_) {
auto & p = pc_->points[index];
pcl::PointXYZ p_xyz;
p_xyz.x = p.x;
p_xyz.y = p.y;
p_xyz.z = p.z;
ground_points->push_back(p_xyz);
point_types[point_indices[k]] = PointType::GROUND;
} else {
unknown_indices[unknown_indices_size++] = index;
}
}
point_indices_size = 0;
}

double centroid = 0;
int centroid_ring = 0;
int cluster_indices[height_] = {0};
int cluster_indices_size = 0;
for (int k = unknown_indices_size - 1; k >= 0; k--) {
float x0 = pc_->points[unknown_indices[k]].x;
float y0 = pc_->points[unknown_indices[k]].y;
float r0 = sqrt(x0 * x0 + y0 * y0);
float r_diff = fabs(r0 - centroid);
float dist = radius_array_[centroid_ring];
if (r_diff >= dist && cluster_indices_size != 0) {
for (int l = 0; l < cluster_indices_size; l++) {
auto & p = pc_->points[cluster_indices[l]];
pcl::PointXYZ p_xyz;
p_xyz.x = p.x;
p_xyz.y = p.y;
p_xyz.z = p.z;
(cluster_indices_size > 1 ? obstacle_points : ground_points)->push_back(p_xyz);
}
cluster_indices_size = 0;
}
cluster_indices[cluster_indices_size++] = unknown_indices[k];
centroid = r0;
centroid_ring = pc_->points[unknown_indices[k]].ring;
if (k != 0) continue;
for (int l = 0; l < cluster_indices_size; l++) {
auto & p = pc_->points[cluster_indices[l]];
pcl::PointXYZ p_xyz;
p_xyz.x = p.x;
p_xyz.y = p.y;
p_xyz.z = p.z;
(cluster_indices_size > 1 ? obstacle_points : ground_points)->push_back(p_xyz);
}
cluster_indices_size = 0;
}
}
}

// Publish
{
sensor_msgs::msg::PointCloud2 pc_ground_msg;
pcl::toROSMsg(*ground_points, pc_ground_msg);
pc_ground_msg.header.frame_id = pc_->header.frame_id;
pc_ground_msg.header.stamp = this->now();
pc_ground_pub_->publish(pc_ground_msg);
}
{
sensor_msgs::msg::PointCloud2 pc_obstacle_msg;
pcl::toROSMsg(*obstacle_points, pc_obstacle_msg);
pc_obstacle_msg.header.frame_id = pc_->header.frame_id;
pc_obstacle_msg.header.stamp = this->now();
pc_obstacle_pub_->publish(pc_obstacle_msg);
}
}

bool VelodyneCloudSeparator::try_subscribe_pc()
{
auto pc_msg = pc_sub_->getData();
if (!pc_msg) return false;
try {
pc_ = pcl::PointCloud<PointXYZIR>::Ptr(new pcl::PointCloud<PointXYZIR>);
pcl::fromROSMsg(*pc_msg, *pc_);
} catch (const std::exception & e) {
RCLCPP_ERROR(get_logger(), "Failed to convert PointCloud2 to pcl::PointCloud: %s", e.what());
return false;
}
return true;
}
} // namespace velodyne_cloud_separator

#include "rclcpp_components/register_node_macro.hpp"
RCLCPP_COMPONENTS_REGISTER_NODE(velodyne_cloud_separator::VelodyneCloudSeparator)
Loading