forked from zbw/yiliao2026
添加了第一版雷达数据->障碍物坐标的功能包
This commit is contained in:
55
src/obstacle_scanner/CMakeLists.txt
Normal file
55
src/obstacle_scanner/CMakeLists.txt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
project(obstacle_scanner)
|
||||||
|
|
||||||
|
if(NOT CMAKE_CXX_STANDARD)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(ament_cmake REQUIRED)
|
||||||
|
find_package(rclcpp REQUIRED)
|
||||||
|
find_package(sensor_msgs REQUIRED)
|
||||||
|
find_package(std_msgs REQUIRED)
|
||||||
|
find_package(cv_bridge REQUIRED)
|
||||||
|
find_package(OpenCV REQUIRED)
|
||||||
|
find_package(Eigen3 REQUIRED)
|
||||||
|
find_package(rosidl_default_generators REQUIRED)
|
||||||
|
|
||||||
|
rosidl_generate_interfaces(${PROJECT_NAME}
|
||||||
|
"msg/Obstacle.msg"
|
||||||
|
"msg/ObstacleArray.msg"
|
||||||
|
DEPENDENCIES std_msgs
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(obstacle_scanner_node src/obstacle_scanner_node.cpp)
|
||||||
|
target_compile_features(obstacle_scanner_node PUBLIC cxx_std_17)
|
||||||
|
target_include_directories(obstacle_scanner_node PUBLIC ${EIGEN3_INCLUDE_DIRS})
|
||||||
|
ament_target_dependencies(obstacle_scanner_node
|
||||||
|
rclcpp
|
||||||
|
sensor_msgs
|
||||||
|
std_msgs
|
||||||
|
cv_bridge
|
||||||
|
OpenCV
|
||||||
|
)
|
||||||
|
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp")
|
||||||
|
target_link_libraries(obstacle_scanner_node ${cpp_typesupport_target})
|
||||||
|
|
||||||
|
install(TARGETS obstacle_scanner_node
|
||||||
|
DESTINATION lib/${PROJECT_NAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(DIRECTORY
|
||||||
|
launch
|
||||||
|
config
|
||||||
|
DESTINATION share/${PROJECT_NAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
ament_export_dependencies(rosidl_default_runtime)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
find_package(ament_lint_auto REQUIRED)
|
||||||
|
set(ament_cmake_copyright_FOUND TRUE)
|
||||||
|
set(ament_cmake_cpplint_FOUND TRUE)
|
||||||
|
ament_lint_auto_find_test_dependencies()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ament_package()
|
||||||
12
src/obstacle_scanner/config/params.yaml
Normal file
12
src/obstacle_scanner/config/params.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
obstacle_scanner:
|
||||||
|
ros__parameters:
|
||||||
|
scan_topic: "/scan"
|
||||||
|
frame_id: "laser_frame"
|
||||||
|
cluster_gap: 0.1
|
||||||
|
max_cluster_points: 20
|
||||||
|
radius_min: 0.03
|
||||||
|
radius_max: 0.05
|
||||||
|
merge_wrap: true
|
||||||
|
debug: true
|
||||||
|
debug_image_size: 500
|
||||||
|
debug_resolution: 0.01
|
||||||
22
src/obstacle_scanner/launch/obstacle_scanner.launch.py
Executable file
22
src/obstacle_scanner/launch/obstacle_scanner.launch.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from launch import LaunchDescription
|
||||||
|
from launch_ros.actions import Node
|
||||||
|
from ament_index_python.packages import get_package_share_directory
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def generate_launch_description():
|
||||||
|
config = os.path.join(
|
||||||
|
get_package_share_directory('obstacle_scanner'),
|
||||||
|
'config',
|
||||||
|
'params.yaml',
|
||||||
|
)
|
||||||
|
return LaunchDescription([
|
||||||
|
Node(
|
||||||
|
package='obstacle_scanner',
|
||||||
|
executable='obstacle_scanner_node',
|
||||||
|
name='obstacle_scanner',
|
||||||
|
parameters=[config],
|
||||||
|
output='screen',
|
||||||
|
),
|
||||||
|
])
|
||||||
3
src/obstacle_scanner/msg/Obstacle.msg
Normal file
3
src/obstacle_scanner/msg/Obstacle.msg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
float64 center_x
|
||||||
|
float64 center_y
|
||||||
|
float64 radius
|
||||||
2
src/obstacle_scanner/msg/ObstacleArray.msg
Normal file
2
src/obstacle_scanner/msg/ObstacleArray.msg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
std_msgs/Header header
|
||||||
|
Obstacle[] obstacles
|
||||||
30
src/obstacle_scanner/package.xml
Normal file
30
src/obstacle_scanner/package.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
|
<package format="3">
|
||||||
|
<name>obstacle_scanner</name>
|
||||||
|
<version>0.0.0</version>
|
||||||
|
<description>LiDAR obstacle detection via angular clustering and circle fitting.</description>
|
||||||
|
<maintainer email="2314753575@qq.com">sunrise</maintainer>
|
||||||
|
<license>Apache-2.0</license>
|
||||||
|
|
||||||
|
<buildtool_depend>ament_cmake</buildtool_depend>
|
||||||
|
|
||||||
|
<depend>rclcpp</depend>
|
||||||
|
<depend>sensor_msgs</depend>
|
||||||
|
<depend>std_msgs</depend>
|
||||||
|
<depend>cv_bridge</depend>
|
||||||
|
<depend>libopencv-dev</depend>
|
||||||
|
<depend>eigen</depend>
|
||||||
|
|
||||||
|
<build_depend>rosidl_default_generators</build_depend>
|
||||||
|
<exec_depend>rosidl_default_runtime</exec_depend>
|
||||||
|
|
||||||
|
<member_of_group>rosidl_interface_packages</member_of_group>
|
||||||
|
|
||||||
|
<test_depend>ament_lint_auto</test_depend>
|
||||||
|
<test_depend>ament_lint_common</test_depend>
|
||||||
|
|
||||||
|
<export>
|
||||||
|
<build_type>ament_cmake</build_type>
|
||||||
|
</export>
|
||||||
|
</package>
|
||||||
357
src/obstacle_scanner/src/obstacle_scanner_node.cpp
Normal file
357
src/obstacle_scanner/src/obstacle_scanner_node.cpp
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
#include <rclcpp/rclcpp.hpp>
|
||||||
|
#include <sensor_msgs/msg/laser_scan.hpp>
|
||||||
|
#include <sensor_msgs/msg/image.hpp>
|
||||||
|
#include <cv_bridge/cv_bridge.h>
|
||||||
|
#include <opencv2/opencv.hpp>
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
|
#include "obstacle_scanner/msg/obstacle.hpp"
|
||||||
|
#include "obstacle_scanner/msg/obstacle_array.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <numeric>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace obstacle_scanner
|
||||||
|
{
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Ported from scan_circle_demo.py:211-227
|
||||||
|
// Least-squares algebraic circle fitting (Al-Sharadqah & Chernov)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
std::tuple<double, double, double> fit_circle(const std::vector<Eigen::Vector2d>& pts)
|
||||||
|
{
|
||||||
|
const int n = static_cast<int>(pts.size());
|
||||||
|
if (n < 3) {
|
||||||
|
throw std::runtime_error("Need at least 3 points to fit a circle.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::MatrixXd A(n, 3);
|
||||||
|
Eigen::VectorXd b(n);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
const double x = pts[i].x();
|
||||||
|
const double y = pts[i].y();
|
||||||
|
A(i, 0) = 2.0 * x;
|
||||||
|
A(i, 1) = 2.0 * y;
|
||||||
|
A(i, 2) = 1.0;
|
||||||
|
b(i) = x * x + y * y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
|
||||||
|
if (svd.rank() < 3) {
|
||||||
|
throw std::runtime_error("Points are nearly collinear; no stable circle exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Eigen::Vector3d sol = svd.solve(b);
|
||||||
|
const double cx = sol(0);
|
||||||
|
const double cy = sol(1);
|
||||||
|
const double c = sol(2);
|
||||||
|
const double r2 = c + cx * cx + cy * cy;
|
||||||
|
|
||||||
|
if (r2 <= 0.0 || !std::isfinite(r2)) {
|
||||||
|
throw std::runtime_error("Circle fitting produced an invalid radius.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {cx, cy, std::sqrt(r2)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Ported from demo4.py:149-177
|
||||||
|
// Sort points by angle, split clusters where Euclidean gap > threshold,
|
||||||
|
// optionally merge first and last cluster across 360°.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
std::vector<std::vector<int>> cluster_full_scan(
|
||||||
|
const std::vector<Eigen::Vector2d>& points,
|
||||||
|
const std::vector<double>& point_angles,
|
||||||
|
double cluster_gap,
|
||||||
|
bool merge_wrap)
|
||||||
|
{
|
||||||
|
const int n = static_cast<int>(points.size());
|
||||||
|
if (n == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort indices by angle
|
||||||
|
std::vector<int> order(n);
|
||||||
|
std::iota(order.begin(), order.end(), 0);
|
||||||
|
std::sort(order.begin(), order.end(),
|
||||||
|
[&](int a, int b) { return point_angles[a] < point_angles[b]; });
|
||||||
|
|
||||||
|
std::vector<std::vector<int>> clusters;
|
||||||
|
clusters.push_back({order[0]});
|
||||||
|
|
||||||
|
for (size_t k = 1; k < order.size(); ++k) {
|
||||||
|
int prev_idx = order[k - 1];
|
||||||
|
int cur_idx = order[k];
|
||||||
|
double dx = points[cur_idx].x() - points[prev_idx].x();
|
||||||
|
double dy = points[cur_idx].y() - points[prev_idx].y();
|
||||||
|
double dist = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (dist > cluster_gap) {
|
||||||
|
clusters.emplace_back();
|
||||||
|
}
|
||||||
|
clusters.back().push_back(cur_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional wrap-around merge
|
||||||
|
if (merge_wrap && clusters.size() > 1) {
|
||||||
|
int first_idx = clusters.front().front();
|
||||||
|
int last_idx = clusters.back().back();
|
||||||
|
double dx = points[first_idx].x() - points[last_idx].x();
|
||||||
|
double dy = points[first_idx].y() - points[last_idx].y();
|
||||||
|
if (std::sqrt(dx * dx + dy * dy) <= cluster_gap) {
|
||||||
|
auto& last = clusters.back();
|
||||||
|
const auto& first = clusters.front();
|
||||||
|
last.insert(last.end(), first.begin(), first.end());
|
||||||
|
clusters.erase(clusters.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Ported from demo4.py:180-238
|
||||||
|
// Cluster → fit circle → filter by radius range.
|
||||||
|
// Returns only qualified obstacles (radius_min <= R <= radius_max).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
std::vector<msg::Obstacle> detect_circles(
|
||||||
|
const std::vector<Eigen::Vector2d>& points,
|
||||||
|
const std::vector<double>& point_angles,
|
||||||
|
double cluster_gap,
|
||||||
|
int max_cluster_points,
|
||||||
|
double radius_min,
|
||||||
|
double radius_max,
|
||||||
|
bool merge_wrap,
|
||||||
|
int& out_total_clusters,
|
||||||
|
int& out_fitted,
|
||||||
|
int& out_discarded_large,
|
||||||
|
int& out_skipped_small,
|
||||||
|
int& out_failed)
|
||||||
|
{
|
||||||
|
auto clusters = cluster_full_scan(points, point_angles, cluster_gap, merge_wrap);
|
||||||
|
|
||||||
|
out_total_clusters = static_cast<int>(clusters.size());
|
||||||
|
|
||||||
|
std::vector<msg::Obstacle> obstacles;
|
||||||
|
|
||||||
|
for (const auto& cluster : clusters) {
|
||||||
|
const int sz = static_cast<int>(cluster.size());
|
||||||
|
|
||||||
|
if (sz > max_cluster_points) {
|
||||||
|
out_discarded_large++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sz < 3) {
|
||||||
|
out_skipped_small++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Eigen::Vector2d> cluster_pts;
|
||||||
|
cluster_pts.reserve(sz);
|
||||||
|
for (int idx : cluster) {
|
||||||
|
cluster_pts.push_back(points[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto [cx, cy, r] = fit_circle(cluster_pts);
|
||||||
|
out_fitted++;
|
||||||
|
|
||||||
|
if (r >= radius_min && r <= radius_max) {
|
||||||
|
msg::Obstacle obs;
|
||||||
|
obs.center_x = cx;
|
||||||
|
obs.center_y = cy;
|
||||||
|
obs.radius = r;
|
||||||
|
obstacles.push_back(obs);
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
out_failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obstacles;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace obstacle_scanner
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ROS2 Node
|
||||||
|
// ============================================================================
|
||||||
|
class ObstacleScannerNode : public rclcpp::Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ObstacleScannerNode()
|
||||||
|
: rclcpp::Node("obstacle_scanner")
|
||||||
|
{
|
||||||
|
using std::placeholders::_1;
|
||||||
|
|
||||||
|
// --- Parameters ---
|
||||||
|
scan_topic_ = declare_parameter<std::string>("scan_topic", "/scan");
|
||||||
|
frame_id_ = declare_parameter<std::string>("frame_id", "laser_frame");
|
||||||
|
cluster_gap_ = declare_parameter("cluster_gap", 0.1);
|
||||||
|
max_cluster_points_ = declare_parameter("max_cluster_points", 20);
|
||||||
|
radius_min_ = declare_parameter("radius_min", 0.03);
|
||||||
|
radius_max_ = declare_parameter("radius_max", 0.05);
|
||||||
|
merge_wrap_ = declare_parameter("merge_wrap", true);
|
||||||
|
debug_ = declare_parameter("debug", false);
|
||||||
|
debug_image_size_ = declare_parameter("debug_image_size", 500);
|
||||||
|
debug_resolution_ = declare_parameter("debug_resolution", 0.01);
|
||||||
|
|
||||||
|
// --- Publishers ---
|
||||||
|
obstacles_pub_ = create_publisher<obstacle_scanner::msg::ObstacleArray>(
|
||||||
|
"/obstacles", 10);
|
||||||
|
|
||||||
|
if (debug_) {
|
||||||
|
debug_pub_ = create_publisher<sensor_msgs::msg::Image>(
|
||||||
|
"/processed_scan", 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Subscriber ---
|
||||||
|
scan_sub_ = create_subscription<sensor_msgs::msg::LaserScan>(
|
||||||
|
scan_topic_, rclcpp::SensorDataQoS(),
|
||||||
|
std::bind(&ObstacleScannerNode::scan_callback, this, _1));
|
||||||
|
|
||||||
|
RCLCPP_INFO(get_logger(), "ObstacleScannerNode started");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ==========================================================================
|
||||||
|
// LaserScan callback
|
||||||
|
// ==========================================================================
|
||||||
|
void scan_callback(const sensor_msgs::msg::LaserScan::ConstSharedPtr& msg)
|
||||||
|
{
|
||||||
|
// 1. Filter valid ranges, convert polar → Cartesian
|
||||||
|
const double angle_increment = msg->angle_increment;
|
||||||
|
std::vector<Eigen::Vector2d> points;
|
||||||
|
std::vector<double> angles;
|
||||||
|
|
||||||
|
points.reserve(msg->ranges.size());
|
||||||
|
angles.reserve(msg->ranges.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < msg->ranges.size(); ++i) {
|
||||||
|
const double r = msg->ranges[i];
|
||||||
|
if (!std::isfinite(r) || r <= 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double a = msg->angle_min + static_cast<double>(i) * angle_increment;
|
||||||
|
const double x = r * std::cos(a);
|
||||||
|
const double y = r * std::sin(a);
|
||||||
|
points.emplace_back(x, y);
|
||||||
|
angles.push_back(std::fmod(std::atan2(y, x) + 2.0 * M_PI, 2.0 * M_PI));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Detect circles
|
||||||
|
int total_clusters = 0, fitted = 0, discarded_large = 0;
|
||||||
|
int skipped_small = 0, failed = 0;
|
||||||
|
|
||||||
|
auto obstacles = obstacle_scanner::detect_circles(
|
||||||
|
points, angles,
|
||||||
|
cluster_gap_, max_cluster_points_,
|
||||||
|
radius_min_, radius_max_,
|
||||||
|
merge_wrap_,
|
||||||
|
total_clusters, fitted, discarded_large, skipped_small, failed);
|
||||||
|
|
||||||
|
RCLCPP_DEBUG(get_logger(),
|
||||||
|
"points=%zu clusters=%d fitted=%d qualified=%zu " // 有效点数、聚类个体数、拟合圆数、障碍物数
|
||||||
|
"discarded_large=%d skipped_small=%d failed=%d", // 过大、过小、拟合失败个数
|
||||||
|
points.size(), total_clusters, fitted,
|
||||||
|
obstacles.size(), discarded_large, skipped_small, failed);
|
||||||
|
|
||||||
|
// 3. Publish ObstacleArray
|
||||||
|
auto out_msg = obstacle_scanner::msg::ObstacleArray();
|
||||||
|
out_msg.header.stamp = msg->header.stamp;
|
||||||
|
out_msg.header.frame_id = frame_id_;
|
||||||
|
out_msg.obstacles = std::move(obstacles);
|
||||||
|
obstacles_pub_->publish(out_msg);
|
||||||
|
|
||||||
|
// 4. Debug image
|
||||||
|
if (debug_) {
|
||||||
|
auto img_msg = render_debug_image(points, out_msg.obstacles,
|
||||||
|
msg->header.stamp);
|
||||||
|
debug_pub_->publish(*img_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Debug: render scan points + obstacles to cv::Mat, publish as Image
|
||||||
|
// ==========================================================================
|
||||||
|
sensor_msgs::msg::Image::SharedPtr render_debug_image(
|
||||||
|
const std::vector<Eigen::Vector2d>& points,
|
||||||
|
const std::vector<obstacle_scanner::msg::Obstacle>& obstacles,
|
||||||
|
const builtin_interfaces::msg::Time& stamp)
|
||||||
|
{
|
||||||
|
const int size = debug_image_size_;
|
||||||
|
const double res = debug_resolution_;
|
||||||
|
const int origin = size / 2;
|
||||||
|
|
||||||
|
cv::Mat img(size, size, CV_8UC3, cv::Scalar(255, 255, 255));
|
||||||
|
|
||||||
|
// Draw scan points in red (small filled circles)
|
||||||
|
for (const auto& pt : points) {
|
||||||
|
int col = static_cast<int>(std::round(origin + pt.x() / res));
|
||||||
|
int row = static_cast<int>(std::round(origin - pt.y() / res));
|
||||||
|
if (col >= 0 && col < size && row >= 0 && row < size) {
|
||||||
|
cv::circle(img, cv::Point(col, row), 1,
|
||||||
|
cv::Scalar(0, 0, 255), cv::FILLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw origin (yellow square, 4x4 px)
|
||||||
|
cv::rectangle(img, cv::Point(origin - 2, origin - 2),
|
||||||
|
cv::Point(origin + 2, origin + 2),
|
||||||
|
cv::Scalar(0, 255, 255), cv::FILLED);
|
||||||
|
|
||||||
|
// Draw qualified obstacles in green
|
||||||
|
for (const auto& obs : obstacles) {
|
||||||
|
int col = static_cast<int>(std::round(origin + obs.center_x / res));
|
||||||
|
int row = static_cast<int>(std::round(origin - obs.center_y / res));
|
||||||
|
int r_px = std::max(1, static_cast<int>(std::round(obs.radius / res)));
|
||||||
|
|
||||||
|
// Circle outline
|
||||||
|
cv::circle(img, cv::Point(col, row), r_px,
|
||||||
|
cv::Scalar(0, 255, 0), 1);
|
||||||
|
|
||||||
|
// Radius label
|
||||||
|
char buf[32];
|
||||||
|
std::snprintf(buf, sizeof(buf), "R=%.3g", obs.radius);
|
||||||
|
cv::putText(img, buf,
|
||||||
|
cv::Point(col + r_px + 2, row),
|
||||||
|
cv::FONT_HERSHEY_SIMPLEX, 0.3,
|
||||||
|
cv::Scalar(0, 255, 0), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto image_msg = cv_bridge::CvImage(
|
||||||
|
std_msgs::msg::Header(), "bgr8", img).toImageMsg();
|
||||||
|
image_msg->header.stamp = stamp;
|
||||||
|
image_msg->header.frame_id = frame_id_;
|
||||||
|
return image_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Parameters ----
|
||||||
|
std::string scan_topic_;
|
||||||
|
std::string frame_id_;
|
||||||
|
double cluster_gap_;
|
||||||
|
int max_cluster_points_;
|
||||||
|
double radius_min_;
|
||||||
|
double radius_max_;
|
||||||
|
bool merge_wrap_;
|
||||||
|
bool debug_;
|
||||||
|
int debug_image_size_;
|
||||||
|
double debug_resolution_;
|
||||||
|
|
||||||
|
// ---- ROS2 interfaces ----
|
||||||
|
rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr scan_sub_;
|
||||||
|
rclcpp::Publisher<obstacle_scanner::msg::ObstacleArray>::SharedPtr obstacles_pub_;
|
||||||
|
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr debug_pub_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
rclcpp::init(argc, argv);
|
||||||
|
rclcpp::spin(std::make_shared<ObstacleScannerNode>());
|
||||||
|
rclcpp::shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user