基于 ROS 的激光雷达(LiDAR)防区监测节点。它的核心功能是订阅激光雷达的扫描数据,在预设的多个"防区"(扇形区域)内检测障碍物,计算最近距离,并根据滤波规则判断是否触发警报,最后将处理后的距离信息以 sensor_msgs/Range 格式发布。
📂 1. 主程序入口 (Laser_node.cpp)
这个文件非常简洁,负责 ROS 节点的初始化和核心类的实例化。
include <Laser.h> // 包含核心功能的头文件
int main(int argc, char** argv) {
// 1. 初始化 ROS 节点,命名为 "Laser_node"
// 这一步是 ROS 程序的标准起手式,负责与 ROS Master 通信
ros::init(argc, argv, "Laser_node");
// 2. 实例化核心类 Laser_CORE
// 构造函数中会完成参数读取、话题订阅和发布者初始化
LOA_CORE Laser;
// 3. 启动节点主循环
// 进入 ros::spin(),开始处理回调函数
Laser.start();
return 0;
}
⚙️ 2. 核心逻辑实现 (Laser.cpp)
这个文件包含了 Laser_CORE 类的具体实现,涵盖了参数配置、数据回调、防区逻辑处理和数据发布。
构造函数:初始化与参数加载
include "Laser.h" // 包含头文件
// 构造函数:负责节点的初始化配置
LOA_CORE::Laser_CORE() {
ros::NodeHandle nh; // 全局句柄,用于访问全局参数和话题
ros::NodeHandle nh_p("~"); // 私有句柄,用于访问该节点特有的参数(~param_name)
ros::Rate rate(25); // 设置循环频率为 25Hz(虽然在此构造函数中未实际使用循环)
// --- 参数获取 ---
// 从参数服务器获取激光雷达话题名称,默认为 "/scan"
nh_p.paramstd::string("scan_topic", scan_topic_, "/scan");
// 获取防区数量,默认为 3 个
nh_p.param("num_of_area", number_of_area_, 3);
std::cout << "Get " << number_of_area_ << " Areas." << std::endl;
// --- 循环读取每个防区的具体配置 ---
for(int temp = 1; temp <= number_of_area_; temp ++) {
std::ostringstream oss_temp; // 用于拼接参数名称的字符串流
// 1. 获取最小角度
oss_temp << "area_" << temp << "min_ang";
nh_p.param(oss_temp.str(), area_temp .min_ang, 0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.min_ang << std::endl;
oss_temp.str(""); // 清空字符串流以便复用
// 2. 获取最大角度
oss_temp << "area_" << temp << "max_ang";
nh_p.param(oss_temp.str(), area_temp .max_ang, 0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.max_ang << std::endl;
oss_temp.str("");
// 3. 获取最大检测距离
oss_temp << "area_" << temp << "max_range";
nh_p.param(oss_temp.str(), area_temp .max_range, 0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.max_range << std::endl;
oss_temp.str("");
// 4. 获取最小检测距离
oss_temp << "area_" << temp << "min_range";
nh_p.param(oss_temp.str(), area_temp .min_range, 0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.min_range << std::endl;
oss_temp.str("");
// 5. 获取滤波器尺寸(连续多少个点满足条件才报警)
oss_temp << "area_" << temp << "filter_size";
nh_p.param(oss_temp.str(), area_temp .filter_size, 0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.filter_size << std::endl;
oss_temp.str("");
// 如果最大距离不为0,认为该防区有效,加入防区组
if(area_temp_.max_range != 0) {
std::cout << "Loading #" << area_group_.size() + 1 << " Denfence Area" << std::endl;
area_group_.push_back(area_temp_); // 将临时结构体存入防区向量
}
// --- 初始化发布者 ---
// 为每个防区创建一个独立的话题发布者,话题名为 "/range" + 编号
oss_temp << "range" << temp;
range_pub_temp_ = nh.advertise<sensor_msgs::Range>(oss_temp.str(), 10);
range_pub_group_.push_back(range_pub_temp_); // 存入发布者向量
oss_temp.str("");
}
std::cout << "Now We Have " << range_pub_group_.size() << " Defennce Range Publisher !" << std::endl;
// --- 订阅激光雷达话题 ---
// 订阅 scan_topic_ 话题,队列长度 1,绑定回调函数 scan_callback
lidar_sub_ = nh.subscribe<sensor_msgs::LaserScan>(scan_topic_, 1, boost::bind(&Laser_CORE::scan_callback, this, 1));
}
回调函数:数据处理流水线
当激光雷达数据到达时,scan_callback 被触发,依次调用四个处理函数。
// 雷达话题的回调函数
void Laser_CORE::scan_callback(const sensor_msgs::LaserScanConstPtr &scan) {
scan = *scan; // 1. 保存原始数据到成员变量
fill_scan_to_zone(); // 2. 根据角度将数据分配到不同防区
filter(); // 3. 滤波处理(去噪、防误报)
find_the_nearest_obs(); // 4. 寻找防区内最近障碍物
publish_result(); // 5. 发布处理结果
}
逻辑处理函数详解
- 数据分配 (fill_scan_to_zone)
将 360 度(或部分角度)的激光点云,根据配置的角度范围,切割到不同的防区向量中。
void Laser_CORE::fill_scan_to_zone(void) {
// 遍历所有防区
for(int i = 0; i < area_group_.size(); i++) {
// 清空上一帧该防区的数据
std::vector().swap(area_group_i.alert_area);
// 计算该防区在激光数组中的起始和结束索引
// 公式:(目标角度 - 激光起始角) / 角分辨率
int start_idx = (int)((area_group_i.min_ang * (PI/180) - scan_.angle_min) / scan_.angle_increment);
int end_idx = (int)(((area_group_i.max_ang * (PI/180)) - scan_.angle_min) / scan_.angle_increment);
// 将该范围内的距离数据提取出来存入防区
for(int j = start_idx; j < end_idx; j++) {
area_group_i.alert_area.push_back(scan_.rangesj);
}
}
} - 滤波逻辑 (filter)
这是一个简单的连续点滤波算法。只有当连续 filter_size 个点都在有效距离内,才判定为报警。
void Laser_CORE::filter(void) {
for(int i = 0; i < area_group_.size(); i++) {
int k = 0; // 计数器:记录连续满足条件的点数
for(int j = 0; j < area_group_i.alert_area.size(); j++) {
// 判断距离是否在设定的 min_range, max_range 之间
if(area_group_i.alert_areaj <= area_group_i.max_range &&
area_group_i.alert_areaj >= area_group_i.min_range) {
k++; // 满足条件,计数加 1
// 如果连续满足条件的点数达到阈值,标记为警告并跳出
if(k >= area_group_i.filter_size) {
area_group_i.warning = true;
break;
}
} else {
k = 0; // 一旦遇到不满足条件的点(如超出范围或无效点),计数清零
}
// 注意:原代码逻辑中,如果循环跑完都没break,这里会不断覆盖warning状态
// 这里的逻辑略显冗余,主要依赖上面的 break
area_group_i.warning = false;
}
}
} - 寻找最近障碍物 (find_the_nearest_obs)
遍历防区内的所有点,找到最小值。
void Laser_CORE::find_the_nearest_obs(void) {
for(int i = 0; i < area_group_.size(); i++) {
// 初始化最近距离为防区的最大范围
area_group_i.nearest_obstacle_distance = area_group_i.max_range;
// 遍历防区内所有点
for(int j = 0; j < area_group_i.alert_area.size(); j++) {
// 更新最小值
area_group_i.nearest_obstacle_distance =
area_group_i.nearest_obstacle_distance > area_group_i.alert_areaj ?
area_group_i.alert_areaj : area_group_i.nearest_obstacle_distance;
// 边界钳位:确保结果不超过最大范围
if(area_group_i.nearest_obstacle_distance > area_group_i.max_range)
area_group_i.nearest_obstacle_distance = area_group_i.max_range;
// 边界钳位:确保结果不小于最小范围
if(area_group_i.nearest_obstacle_distance < area_group_i.min_range)
area_group_i.nearest_obstacle_distance = area_group_i.min_range;
}
}
} - 结果发布 (publish_result)
将计算出的最近距离封装成 ROS 标准消息发布。
void Laser_CORE::publish_result(void) {
for(int i = 0; i < area_group_.size(); i++) {
// 填充 sensor_msgs::Range 消息
range_temp_.radiation_type = sensor_msgs::Range::INFRARED; // 辐射类型(此处仅为占位)
// 设置 frame_id 为 "/range" + 编号
oss_temp << "/range" << i+1;
range_temp_.header.frame_id = oss_temp.str();
oss_temp.str("");
range_temp_.header.stamp = ros::Time::now(); // 时间戳
// 视场角 = 最大角 - 最小角 (转为弧度)
range_temp_.field_of_view = (area_group_i.max_ang * (PI/180)) - (area_group_i.min_ang * (PI/180));
range_temp_.min_range = area_group_i.min_range;
range_temp_.max_range = area_group_i.max_range;
range_temp_.range = area_group_i.nearest_obstacle_distance; // 核心数据:最近距离
// 发布消息
range_pub_group_i.publish(range_temp_);
}
// 注释掉的代码块原本是用于发布自定义的障碍物标志位消息
}
启动函数
void Laser_CORE::start(void) {
ros::spin(); // 进入死循环,等待并处理回调函数
}
1)Laser.h
ifndef Laser_H
define Laser_H
include <sensor_msgs/LaserScan.h>
include <sensor_msgs/Range.h>
include <std_msgs/Bool.h>
include "ros/ros.h"
include
include <stdio.h>
include
include <stdlib.h>
include <math.h>
include
include
using namespace std;
define PI 3.141592653
define myabs(x) (x)>0? (x) : (-(x))
//激光转声纳的数据结果
typedef struct loa_data_struct
{
//最小角度
double min_ang;
//最大角度
double max_ang;
//最小距离
double min_range;
//最大距离
double max_range;
//报警区域
std::vector alert_area;
//滤波器尺寸
int filter_size;
//报警标志
bool warning;
//最近障碍物的距离
double nearest_obstacle_distance;
}
Laser_data;
//核心类
class Laser_CORE
{
public:
Laser_CORE();
~Laser_CORE();
//开始监控防区
void start(void);
private:
//激光雷达话题的名称
std::string scan_topic_;
//雷达话题的订阅器
ros::Subscriber lidar_sub_;
//储存接收的激光雷达数据
sensor_msgs::LaserScan scan_;
//临时通用字符
std::stringstream oss_temp;
//防区的数量
int number_of_area_;
//防区的数据结构
loa_data area_temp_;
//防区的数组
std::vector<loa_data> area_group_;
//模拟出来的声纳防区
sensor_msgs::Range range_temp_;
//单一防区的发布话题
ros::Publisher range_pub_temp_;
//防区数组的发布话题
std::vectorros::Publisher range_pub_group_;
//防区标志的发布话题
ros::Publisher obs_flag_pub_;
/// /// 函数 /// ///
//根据设置的防区范围,填充雷达数据
void fill_scan_to_zone(void);
//根据设置的滤波器大小,过滤选区内的雷达数据
void filter(void);
//搜寻最近障碍物
void find_the_nearest_obs(void);
//发布结果
void publish_result(void);
//绑定激光话题的回调函数
void scan_callback(const sensor_msgs::LaserScan::ConstPtr &scan);
};
endif
2.Laser.cpp
include "laser.h"
//构造函数
Laser_CORE::Laser_CORE()
{
ros::NodeHandle nh;
ros::NodeHandle nh_p("~");
ros::Rate rate(25);
//std_msgs::Bool init_flag_;
//init_flag_.data = false;
//获取激光话题的名称
nh_p.paramstd::string("scan_topic", scan_topic_, "/scan");
//获取防区数量
nh_p.param("num_of_area",number_of_area_, 3);
std::cout << "Get " << number_of_area_ << " Areas." << std::endl;
//获取每个防区结构
for(int temp = 1; temp <= number_of_area_; temp ++)
{
//最小角度
oss_temp << "area_" << temp << "min_ang";
nh_p.param (oss_temp.str(), area_temp .min_ang, 0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.min_ang << std::endl;
oss_temp.str("");
//最大角度
oss_temp << "area_" << temp << "max_ang";
nh_p.param(oss_temp.str(),area_temp .max_ang,0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.max_ang << std::endl;
oss_temp.str("");
//最大距离
oss_temp << "area_" << temp << "max_range";
nh_p.param(oss_temp.str(),area_temp .max_range,0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.max_range << std::endl;
oss_temp.str("");
//最小距离
oss_temp << "area_" << temp << "min_range";
nh_p.param(oss_temp.str(),area_temp .min_range,0.0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.min_range << std::endl;
oss_temp.str("");
//滤波器尺寸
oss_temp << "area_" << temp << "filter_size";
nh_p.param(oss_temp.str(),area_temp .filter_size,0);
std::cout << "get " << oss_temp.str() << ": " << area_temp_.filter_size << std::endl;
oss_temp.str("");
if(area_temp_.max_range != 0)
{
//ROS_INFO("Loading %d Denfence Area",area_group_.size()+1);
std::cout << "Loading #" << area_group_.size() +1 << " Denfence Area" << std::endl;
//压入防区序列
area_group_.push_back(area_temp_);
}
oss_temp << "range" << temp ;
//发布这个单一防区
range_pub_temp_ = nh.advertise<sensor_msgs::Range> (oss_temp.str(),10);
//压入防区发布区序列
range_pub_group_.push_back(range_pub_temp_);
oss_temp.str("");
}
std::cout << "Now We Have " <<range_pub_group_.size() << " Defennce Range Publisher !"<< std::endl;
//激光话题的订阅器
lidar_sub_ = nh.subscribe<sensor_msgs::LaserScan> (scan_topic_,1,boost::bind(&Laser_CORE::scan_callback,this,_1));
//绑定激光话题的回调函数
//obs_flag_pub_ = nh.advertise<LOA_CORE::loa_msgs>("/loa",1);
//obs_flag_.zone.resize(number_of_area_);
}
//解构函数
Laser_CORE::~Laser_CORE()
{
}
//雷达话题的回调函数
void Laser_CORE::scan_callback(const sensor_msgs::LaserScanConstPtr &scan)
{
//储存接收的激光雷达数据
scan_ = *scan;
fill_scan_to_zone();
filter();
find_the_nearest_obs();
publish_result();
}
//根据设置的防区范围,填充雷达数据
void Laser_CORE::fill_scan_to_zone(void)
{
//防区序列
for(int i = 0; i < area_group_.size(); i++)
{
//单一防区
std::vector().swap(area_group_i.alert_area);
for(int j = (int) ((area_group_i.min_ang*(PI/180) - scan_.angle_min) / scan_.angle_increment);
j < (int) (((area_group_i.max_ang* (PI/180)) - scan_.angle_min) / scan_.angle_increment);
j++
)
{
area_group_i.alert_area.push_back(scan_.rangesj);
}
}
}
//根据设置的滤波器大小,过滤选区内的雷达数据
void Laser_CORE::filter(void)
{
for(int i = 0; i < area_group_.size(); i++)
{
int k = 0;
for(int j = 0; j < area_group_i.alert_area.size(); j++)
{
if( area_group_i.alert_areaj <= area_group_i.max_range
&& area_group_i.alert_areaj >= area_group_i.min_range
)
{
k++;
if(k >= area_group_i.filter_size)
{
area_group_i.warning = true;
break;
}
}
else
{
k = 0;
}
area_group_i.warning = false;
}
}
}
//搜寻最近障碍物
void Laser_CORE::find_the_nearest_obs(void)
{
for(int i = 0; i < area_group_.size(); i++)
{
area_group_i.nearest_obstacle_distance = area_group_i.max_range;
for(int j = 0; j < area_group_i.alert_area.size(); j++)
{
area_group_i.nearest_obstacle_distance =
area_group_i.nearest_obstacle_distance > area_group_i.alert_areaj ?
area_group_i.alert_areaj :
area_group_i.nearest_obstacle_distance
;
if(area_group_i.nearest_obstacle_distance > area_group_i.max_range)
area_group_i.nearest_obstacle_distance = area_group_i.max_range;
if(area_group_i.nearest_obstacle_distance < area_group_i.min_range)
area_group_i.nearest_obstacle_distance = area_group_i.min_range;
}
}
}
//发布结果
void Laser_CORE::publish_result(void)
{
for(int i = 0; i < area_group_.size();i++)
{
range_temp_.radiation_type = sensor_msgs::Range::INFRARED;
oss_temp << "/range" << i+1 ;
range_temp_.header.frame_id = oss_temp.str();
oss_temp.str("");
range_temp_.header.stamp = ros::Time::now();
range_temp_.field_of_view = (area_group_i.max_ang*(PI/180)) - (area_group_i.min_ang* (PI/180));
range_temp_.min_range = area_group_i.min_range;
range_temp_.max_range = area_group_i.max_range;
range_temp_.range = area_group_i.nearest_obstacle_distance;
range_pub_group_i.publish(range_temp_);
}
//for(vector::size_type i = 0; i != area_group_.size(); ++i)
/*
for(int i = 0; i < area_group_.size(); i++)
{
if(area_group_i.warning == true)
obs_flag_.zonei.data = true;
else
obs_flag_.zonei.data = false;
}
obs_flag_.header.stamp = ros::Time::now();
obs_flag_.header.frame_id = "laser";
obs_flag_pub_.publish(obs_flag_);
*/
}
void Laser_CORE::start(void)
{
ros::spin();
}
代码逻辑总结:
该驱动验证了以下逻辑:
配置化:通过 ROS 参数服务器灵活配置多个防区,无需修改代码即可改变监测区域。
区域化:将激光雷达数据按角度切分,实现分区监测。
抗干扰:通过 filter_size 参数实现简单的连续点滤波,防止因单个噪点导致的误报。
标准化:输出标准的 sensor_msgs/Range 消息,便于其他导航或避障模块直接调用。
验证建议
在实际运行前,建议检查以下几点:
PI 定义:代码中使用了 PI,需确保 laser.h 中定义了 #define PI 3.1415926... 或包含 并正确使用。
索引越界:fill_scan_to_zone 中计算索引时,未严格检查 start_idx 和 end_idx 是否超出了 scan_.ranges 的实际大小,若配置角度超出雷达物理范围可能导致崩溃。
参数配置:启动节点时,需通过 launch 文件或命令行传入 num_of_area 及各 area_xxx 参数,否则将使用默认值(可能不符合预期)。