一、概述
ROS2消息过滤是机器人开发中数据预处理与融合的核心技术 ,通过预定义规则选择性处理消息流,实现带宽优化、时序对齐、数据质量提升等目标。
核心价值:
- 带宽优化:过滤无关数据,减少网络传输与处理负载
- 时序对齐:解决多传感器数据不同步问题,确保融合精度
- 数据质量:剔除异常/无效数据,提升算法鲁棒性
- 逻辑解耦:将过滤逻辑与业务逻辑分离,提高代码可维护性
二、核心组件与架构
2.1 message_filters包
ROS2官方提供的message_filters是消息过滤的核心库,包含一系列标准化过滤器,支持链式组合,遵循统一接口范式。
核心接口:
- 输入连接 :通过构造函数或
connectInput()方法连接上游过滤器/订阅器 - 输出注册 :通过
registerCallback()方法注册回调函数,处理过滤后的数据 - 连接管理 :
registerCallback()返回message_filters::Connection对象,支持disconnect()手动断开
架构特点:
- 过滤器可链式组合,形成数据处理流水线
- 支持单输入/单输出、多输入/单输出、多输入/多输出等多种模式
- C++实现最多支持9路消息同步,满足复杂传感器融合需求
2.2 过滤器分类
| 类型 | 核心功能 | 典型应用 |
|---|---|---|
| 源过滤器 | 消息输入源(如ROS2订阅器封装) | Subscriber |
| 单输入过滤器 | 处理单个消息流(缓存、排序、内容过滤) | Cache、TimeSequencer |
| 多输入过滤器 | 同步/对齐多个消息流 | TimeSynchronizer、Synchronizer、InputAligner |
| 组合过滤器 | 动态管理多个单输入过滤器 | Chain |
三、标准过滤器
3.1 Subscriber:消息源过滤器
功能:ROS2订阅器的封装,作为过滤链的起点,不接收其他过滤器输出,直接从ROS2话题获取数据。
C++实现:
cpp
#include "message_filters/subscriber.hpp"
// 基础构造
message_filters::Subscriber<sensor_msgs::msg::Image> image_sub(
node, "camera/image_raw", rclcpp::SensorDataQoS().keep_last(10)
);
// 简化写法(通过node的subscribe方法)
auto image_sub = node->subscribe<sensor_msgs::msg::Image>(
"camera/image_raw", 10,
[this](const sensor_msgs::msg::Image::ConstSharedPtr& msg) {
// 直接处理消息
}
);
// 注册回调
image_sub.registerCallback(
[this](const sensor_msgs::msg::Image::ConstSharedPtr& msg) {
RCLCPP_INFO(this->get_logger(), "Received image with timestamp: %ld",
msg->header.stamp.sec);
}
);
关键参数:
node:ROS2节点指针topic:订阅话题名qos_profile:QoS配置(必须与发布者匹配相应规则,否则可能收不到消息)- 回调函数:接收
const std::shared_ptr<M const>&类型消息指针
3.2 Cache:消息缓存过滤器
功能:缓存最近N条消息,支持按时间戳检索,同时透传消息至下游过滤器。
消息来了立刻原样转发给下游(透传不阻塞、不延迟、不丢包),同时悄悄在内部缓存最近 N 条,可以随时按时间戳去查询这批缓存的历史消息。
核心特性:
- 基于环形缓冲区实现,固定内存占用
- 支持按时间区间查询消息(
getInterval()) - 支持获取最近消息(
getLatestTime()、getOldestTime())
C++实现:
cpp
#include "message_filters/cache.hpp"
// 构造:缓存100条图像消息
message_filters::Cache<sensor_msgs::msg::Image> image_cache(image_sub, 100);
// 注册回调(缓存更新时触发)
image_cache.registerCallback(
[this, &image_cache](const sensor_msgs::msg::Image::ConstSharedPtr& msg) {
RCLCPP_INFO(this->get_logger(), "Cache size: %zu", image_cache.getSize());
// 检索最近1秒内的消息
auto now = this->get_clock()->now();
auto start = now - rclcpp::Duration::from_seconds(1.0);
std::vector<sensor_msgs::msg::Image::ConstSharedPtr> recent_msgs =
image_cache.getInterval(start, now);
}
);
headerless消息处理 :C++版本不支持allow_headerless参数,需手动为无header消息添加时间戳(如通过自定义消息适配器)。
3.3 时间同步过滤器
3.3.1 TimeSynchronizer:精确时间同步
功能 :仅当所有输入通道都收到完全相同时间戳 的消息时,才触发回调,适合硬件同步的传感器。
工作流程:
- 给每个话题维护一个消息队列
- 每次收到任意一条消息,就去检查所有队列
- 看所有队列里有没有完全相同时间戳的消息
- 有 → 同时输出 ; 没有 → 不输出,继续等
- 一旦输出,所有话题里早于这个时间戳的消息全部丢弃
C++实现:
cpp
#include "message_filters/time_synchronizer.hpp"
// 定义两个订阅器
message_filters::Subscriber<sensor_msgs::msg::Image> image_sub(node, "image", 10);
message_filters::Subscriber<sensor_msgs::msg::LaserScan> laser_sub(node, "scan", 10);
// 时间同步器(最多支持9个模板参数)
message_filters::TimeSynchronizer<sensor_msgs::msg::Image, sensor_msgs::msg::LaserScan>
sync(image_sub, laser_sub, 10); // 队列大小10
// 注册同步回调
sync.registerCallback(
[this](const sensor_msgs::msg::Image::ConstSharedPtr& img_msg,
const sensor_msgs::msg::LaserScan::ConstSharedPtr& laser_msg) {
RCLCPP_INFO(this->get_logger(), "Synced image and laser at time: %ld",
img_msg->header.stamp.sec);
// 执行数据融合
}
);
限制:要求所有消息时间戳完全一致,实际应用中难以满足,建议优先使用近似同步。
3.3.2 PolicyBased Synchronizer:灵活同步策略
功能:基于策略的同步框架,支持三种同步策略,适配不同场景。
三种策略对比:
| 策略 | 匹配条件 | 适用场景 | 关键参数 |
|---|---|---|---|
| ExactTime | 时间戳完全一致 | 硬件同步传感器 | 队列大小 |
| ApproximateEpsilonTime | 时间戳在epsilon范围内一致 | 轻微不同步的传感器 | epsilon值、队列大小 |
| ApproximateTime | 自适应算法匹配时间戳 | 大多数异步传感器 | 队列大小、slop值 |
C++实现(ApproximateTime):
cpp
#include "message_filters/synchronizer.h"
#include "message_filters/sync_policies/approximate_time.h"
// 定义同步策略(最多9个消息类型)
using MySyncPolicy = message_filters::sync_policies::ApproximateTime<
sensor_msgs::msg::Image, sensor_msgs::msg::LaserScan, sensor_msgs::msg::Imu>;
// 创建同步器
message_filters::Synchronizer<MySyncPolicy> sync(
MySyncPolicy(10), // 队列大小10
image_sub, laser_sub, imu_sub // 三个输入订阅器
);
// 配置slop参数(可选,默认0.1秒)
sync.setMaxIntervalDuration(rclcpp::Duration::from_seconds(0.2));
// 注册回调
sync.registerCallback(
[this](const sensor_msgs::msg::Image::ConstSharedPtr& img,
const sensor_msgs::msg::LaserScan::ConstSharedPtr& laser,
const sensor_msgs::msg::Imu::ConstSharedPtr& imu) {
// 处理同步后的三路数据
}
);
关键配置:
- 队列大小:根据传感器频率设置,图像(30Hz)建议5-7,激光(10Hz)建议3-5,IMU(200Hz)建议20-30
- slop值:最大时间差阈值,需大于传感器间的典型延迟,小于数据有效期
- QoS匹配:所有订阅器必须使用与发布者兼容的QoS配置,否则同步失败且无警告
3.4 TimeSequencer:时序排序过滤器
功能 :确保消息按时间戳顺序触发回调,即使乱序到达,适合对时序敏感的算法。
TimeSequencer = 消息排队等一等 → 按时间戳从小到大排好队 → 再按顺序发给下游
- 接收消息后缓存,直到消息时间戳过期至少
delay时间 - 按时间戳顺序触发回调
- 丢弃晚于已处理消息时间戳的旧消息
现实问题:传感器消息经常 乱序到达
比如 IMU 数据:
- 理论发送顺序:t1 → t2 → t3 → t4
- 实际网络到达:t3 → t1 → t4 → t2(乱序)
如果直接给算法用 → 时序错乱 → 滤波/积分/融合全炸。
工作原理:
-
- 设置一个 延迟等待时间(例如 0.1s / 100ms)
- 消息来了不立刻发
- 先存起来,等 100ms
- 给"迟到"的消息一点赶路时间
-
- 所有消息到达后,先缓存进队列
-
- 每隔一小段时间检查一次
- 检查哪些消息的时间戳 已经"过期"
(即:当前时间 - 消息时间 > 延迟时间) - 把这些过期消息按时间戳从小到大排序
-
- 按排序后的顺序,依次发送给回调
早的先走,晚的后走,绝对不插队。
- 按排序后的顺序,依次发送给回调
C++实现:
cpp
#include "message_filters/time_sequencer.hpp"
// 构造:延迟0.1秒处理,定时器周期0.01秒,队列大小10
message_filters::TimeSequencer<sensor_msgs::msg::Imu> imu_sequencer(
imu_sub, // 输入订阅器
rclcpp::Duration::from_seconds(0.1), // 延迟时间
rclcpp::Duration::from_seconds(0.01), // 定时器周期
10 // 队列大小
);
// 注册回调(保证按时间戳顺序触发)
imu_sequencer.registerCallback(
[this](const sensor_msgs::msg::Imu::ConstSharedPtr& msg) {
RCLCPP_INFO(this->get_logger(), "Processing IMU data in order: %ld",
msg->header.stamp.sec);
}
);
参数调优:
delay:应大于消息最大乱序时间,小于数据时效性要求- 定时器周期:建议为delay的1/10,平衡实时性与CPU占用
3.5 InputAligner:多输入时序对齐过滤器
1.功能:
对齐多个输入流的时间戳,为每个输入提供独立回调,确保消息按时间顺序处理,适合多源数据独立处理但需时序一致的场景。
2. 核心特性
-
a. 多输入流时间戳对齐:InputAligner会缓存每个输入流的最近消息 ,根据时间戳(header.stamp) 找到:
- 同一时间段内
- 时间最接近
- 时间顺序正确
的一组消息,把它们配对对齐。
-
b. 每个输入提供独立回调,这是它和 ROS2 自带的
TimeSynchronizer最大的区别-
标准同步器 :所有数据对齐后,只调用一个回调函数,一次性传给你所有数据
-
InputAligner :对齐后,每个输入流单独触发一个回调
cpp// 图像对齐后 → 调用 image_callback(msg) // 雷达对齐后 → 调用 lidar_callback(msg) // 里程对齐后 → 调用 odom_callback(msg)
-
-
c. 确保消息按时间顺序处理
它内部会:
- 给所有消息排序
- 丢弃乱序的旧消息
- 保证回调执行顺序 = 消息时间戳顺序
绝对不会出现:先处理 t2 数据,再处理 t1 数据的情况。
3. 工作流程
- 订阅 N 个话题(/image /lidar /odom)
- 每个消息进入 InputAligner 缓存
- Aligner 寻找时间戳匹配的一组数据
- 对齐成功
- 按时间顺序 分别触发:
- image_callback()
- lidar_callback()
- odom_callback()
- 在各自回调里处理数据,不用管同步逻辑
| 特性 | ROS2 标准 TimeSynchronizer | InputAligner |
|---|---|---|
| 回调数量 | 1个总回调 | 每个输入1个独立回调 |
| 代码结构 | 所有逻辑堆一起 | 模块化、解耦 |
| 时序保证 | 基础同步 | 严格时间顺序+乱序过滤 |
| 适用场景 | 简单数据融合 | 复杂系统、多模块独立处理 |
InputAligner 是工业级、工程化的高级同步方案。
C++实现:
cpp
#include <rclcpp/rclcpp.hpp>
// 对齐器类
class InputAligner {
public:
template <typename MsgT>
void registerInput(const std::string& topic,
std::function<void(const typename MsgT::SharedPtr&)> cb) {
// 内部订阅 + 对齐逻辑
}
};
// 回调声明
void image_callback(const Image::SharedPtr& msg);
void lidar_callback(const Lidar::SharedPtr& msg);
void odom_callback(const Odom::SharedPtr& msg);
// 使用
int main() {
// 1. 创建对齐器
InputAligner aligner;
// 2. 绑定三路输入 + 独立回调
aligner.registerInput<Image>("/camera", image_callback);
aligner.registerInput<Lidar>("/lidar", lidar_callback);
aligner.registerInput<Odom>("/odom", odom_callback);
}
// 3. 完全独立的回调
void image_callback(const Image::SharedPtr& msg) {
// 图像已对齐
}
void lidar_callback(const Lidar::SharedPtr& msg) {
// 雷达已对齐
}
void odom_callback(const Odom::SharedPtr& msg) {
// 里程计已对齐
}
3.6 Chain:动态过滤链
功能:动态组合多个单输入/单输出过滤器,支持运行时调整过滤流程,适合灵活处理不同数据类型的场景。
C++实现:
cpp
// 1. 创建过滤链
Chain chain;
// 2. 动态添加多个过滤器(顺序执行)
chain.addFilter(undistort_filter); // 去畸变
chain.addFilter(denoise_filter); // 去噪
chain.addFilter(resize_filter); // 缩放
// 3. 数据流入 → 自动按顺序过滤
auto output = chain.process(input);
Chain 动态过滤链(运行时动态配置,不用编译)
程序启动后:
cpp
// 程序正在运行中,随时调接口
chain.removeFilter(去噪); // 运行时删掉一环
chain.insertFilter(锐化, 1); // 运行时中间插入一环
chain.swapFilter(1,2); // 运行时调换顺序
优势:
- 动态添加/移除过滤器,无需重新编译
- 支持通过索引获取已添加的过滤器,进行参数调整
四、内容过滤订阅(Content Filtering Subscription)
4.1 原理与优势
内容过滤订阅 是ROS2 Humble及以上版本支持的高级特性,通过DDS层的ContentFilteredTopic 实现,在发布端过滤数据,显著减少网络传输与订阅端处理开销。
核心优势:
- 比消息过滤器更高效(过滤在DDS层完成,无需传输无效数据)
- 支持SQL-like过滤表达式,语法简洁灵活
- 可动态更新过滤条件,适应场景变化
4.2 C++实现
cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
class ContentFilteringSubscriber : public rclcpp::Node {
public:
ContentFilteringSubscriber() : Node("content_filtering_subscriber") {
// 定义过滤选项:只接收温度 < -30 或 > 100 的数据
rclcpp::SubscriptionOptions sub_options;
sub_options.content_filter_options.filter_expression = "data < %0 OR data > %1";
sub_options.content_filter_options.expression_parameters = {
std::to_string(-30.0), // %0参数
std::to_string(100.0) // %1参数
};
// 创建带内容过滤的订阅器
subscription_ = this->create_subscription<std_msgs::msg::Float32>(
"temperature",
10,
[this](const std_msgs::msg::Float32::SharedPtr msg) {
RCLCPP_WARN(this->get_logger(), "Emergency temperature: %.2f°C", msg->data);
},
sub_options // 传递过滤选项
);
}
private:
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr subscription_;
};
int main(int argc, char * argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<ContentFilteringSubscriber>());
rclcpp::shutdown();
return 0;
}
4.3 过滤表达式语法
支持的运算符:
- 比较:
=,<>,>,>=,<,<= - 逻辑:
AND,OR,NOT - 集合:
IN,NOT IN - 字符串:
LIKE(支持%通配符)
示例:
- 过滤特定ID:
id = 100 - 范围查询:
value BETWEEN 0 AND 100 - 字符串匹配:
name LIKE 'robot%' - 多条件组合:
(temperature > 50 AND humidity < 30) OR pressure > 1013
注意:内容过滤依赖DDS实现支持,目前rmw_fastrtps完全支持,CycloneDDS部分支持。
五、自定义过滤器实现
5.1 基于回调的简单过滤
对于简单过滤需求,可直接在回调函数中实现,无需继承过滤器基类:
cpp
// 自定义内容过滤回调
void imageFilterCallback(const sensor_msgs::msg::Image::ConstSharedPtr& msg) {
// 过滤掉分辨率小于640x480的图像
if (msg->width >= 640 && msg->height >= 480) {
// 处理有效图像
}
// 否则丢弃
}
// 注册过滤回调
image_sub.registerCallback(imageFilterCallback);
5.2 继承基类实现复杂过滤器
对于可复用的复杂过滤逻辑,建议继承message_filters::SimpleFilter实现自定义过滤器:
cpp
#include "message_filters/simple_filter.h"
#include "message_filters/connection.h"
template<typename M>
class ThresholdFilter : public message_filters::SimpleFilter<M> {
public:
using MessageType = M;
using Ptr = std::shared_ptr<ThresholdFilter<M>>;
using ConstPtr = std::shared_ptr<const ThresholdFilter<M>>;
// 构造函数:设置阈值
ThresholdFilter(double threshold) : threshold_(threshold) {}
// 输入回调:处理消息并判断是否转发
void update(const std::shared_ptr<const M>& msg) {
// 假设消息类型有value字段(如std_msgs::msg::Float32)
if (msg->data > threshold_) {
this->signalMessage(msg); // 转发符合条件的消息
}
}
// 连接输入过滤器
template<typename F>
message_filters::Connection connectInput(F& f) {
return f.registerCallback(
std::bind(&ThresholdFilter::update, this, std::placeholders::_1)
);
}
private:
double threshold_; // 阈值参数
};
// 使用自定义过滤器
auto threshold_filter = std::make_shared<ThresholdFilter<std_msgs::msg::Float32>>(50.0);
threshold_filter->connectInput(float_sub); // 连接到输入订阅器
threshold_filter->registerCallback([this](const auto& msg) {
RCLCPP_INFO(this->get_logger(), "Value above threshold: %.2f", msg->data);
});
5.3 使用fkie_message_filters库
fkie_message_filters是ROS2社区开发的增强版消息过滤库,提供更现代的C++接口、更强的类型安全和更丰富的功能。
核心优势:
- 支持任意数量的输入/输出
- 内置多种常用过滤器(如阈值、速率限制、消息转换)
- 支持异步处理和多线程
- 完善的错误处理机制
示例:
cpp
#include "fkie_message_filters/subscriber.h"
#include "fkie_message_filters/user_filter.h"
#include "fkie_message_filters/publisher.h"
// 定义过滤流水线
fkie_message_filters::Subscriber<sensor_msgs::msg::LaserScan> laser_sub(node, "scan");
fkie_message_filters::UserFilter<sensor_msgs::msg::LaserScan> range_filter;
fkie_message_filters::Publisher<sensor_msgs::msg::LaserScan> laser_pub(node, "filtered_scan");
// 连接过滤器
laser_sub.connect_to(range_filter);
range_filter.connect_to(laser_pub);
// 设置过滤逻辑:移除距离小于0.5m或大于10m的点
range_filter.set_processing_function([](const sensor_msgs::msg::LaserScan::ConstSharedPtr& msg) {
auto filtered = std::make_shared<sensor_msgs::msg::LaserScan>(*msg);
for (size_t i = 0; i < msg->ranges.size(); ++i) {
if (msg->ranges[i] < 0.5 || msg->ranges[i] > 10.0) {
filtered->ranges[i] = std::numeric_limits<float>::quiet_NaN();
}
}
return filtered; // 返回过滤后的消息
});
六、高级开发实践
6.1 QoS配置关键要点
QoS不匹配是消息过滤失败的首要原因,必须确保订阅器与发布者的QoS配置兼容。
| 传感器类型 | 推荐QoS配置 | 说明 |
|---|---|---|
| 图像/激光 | SensorDataQoS() | 高频率、低延迟、允许丢失旧数据 |
| IMU/里程计 | ReliableQoS() | 关键数据、需确保送达 |
| 状态/日志 | BestEffortQoS() | 非关键数据、允许丢失 |
C++配置示例:
cpp
// 传感器数据QoS(推荐用于图像、激光)
auto sensor_qos = rclcpp::SensorDataQoS()
.keep_last(10)
.durability_volatile();
// 可靠QoS(推荐用于IMU、里程计)
auto reliable_qos = rclcpp::QoS(rclcpp::KeepLast(5))
.reliable()
.durability_transient_local();
6.2 时间同步策略选择指南
| 场景 | 推荐同步策略 | 参数建议 |
|---|---|---|
| 硬件同步传感器 | ExactTime | 队列大小=传感器数量×2 |
| 轻微不同步(<100ms) | ApproximateEpsilonTime | epsilon=0.05秒 |
| 严重不同步(>100ms) | ApproximateTime | slop=0.2秒,队列大小=10 |
| 单源乱序数据 | TimeSequencer | delay=0.1秒 |
| 多源独立处理 | InputAligner | max_delay=0.5秒 |
6.3 性能优化策略
-
队列大小优化:
- 高频传感器(如IMU 200Hz):队列大小20-30
- 中频传感器(如相机 30Hz):队列大小5-7
- 低频传感器(如激光 10Hz):队列大小3-5
-
回调线程管理:
-
使用
rclcpp::CallbackGroup分离过滤回调与其他回调 -
复杂过滤逻辑建议使用多线程执行器:
cpprclcpp::executors::MultiThreadedExecutor executor; executor.add_node(node); executor.spin();
-
-
内存管理:
- 避免在回调中创建大对象,使用对象池复用
- 及时断开不再使用的过滤器连接,释放资源
6.4 错误处理与调试
-
常见问题排查:
- 无回调触发:检查QoS配置是否匹配、消息时间戳是否有效、队列是否溢出
- 同步失败:增加队列大小、调整slop值、确保所有传感器使用同一时间源(use_sim_time一致)
- 性能下降:减少队列大小、优化过滤逻辑、使用多线程执行器
-
调试技巧:
- 使用
rclcpp::Clock打印消息时间戳,分析同步问题 - 启用ROS2日志调试:
export RCL_LOG_LEVEL=DEBUG - 使用
ros2 topic echo验证消息是否正确发布
- 使用
6.5 headerless消息处理方案
对于无header的消息类型,C++中需手动添加时间戳:
cpp
// 自定义消息适配器,添加时间戳
template<typename M>
struct TimestampedMsg {
M msg;
rclcpp::Time stamp;
};
// 订阅原始消息并添加时间戳
auto raw_sub = node->create_subscription<M>(
"raw_topic", 10,
[this](const typename M::ConstSharedPtr& msg) {
auto timestamped = std::make_shared<TimestampedMsg<M>>();
timestamped->msg = *msg;
timestamped->stamp = this->get_clock()->now();
timestamped_pub->publish(*timestamped);
}
);
// 使用带时间戳的消息进行过滤/同步
message_filters::Subscriber<TimestampedMsg<M>> sub(node, "timestamped_topic", 10);
七、实战案例:焊接机器人多传感器融合
7.1 需求分析
焊接机器人需融合:
- 2D相机(30Hz):焊缝检测
- 3D相机(10Hz):工件定位
- FANUC机械臂状态(50Hz):关节角度与速度
- 激光测距(20Hz):焊缝距离测量
7.2 实现方案
cpp
#include "rclcpp/rclcpp.hpp"
#include "message_filters/subscriber.h"
#include "message_filters/synchronizer.h"
#include "message_filters/sync_policies/approximate_time.h"
#include "sensor_msgs/msg/image.h"
#include "sensor_msgs/msg/point_cloud2.h"
#include "fanuc_msgs/msg/joint_state.h"
#include "sensor_msgs/msg/range.h"
class WeldingSensorFusion : public rclcpp::Node {
public:
WeldingSensorFusion() : Node("welding_sensor_fusion") {
// QoS配置(传感器数据)
auto qos = rclcpp::SensorDataQoS().keep_last(10);
// 创建订阅器
image2d_sub_.subscribe(this, "camera/2d/image", qos);
cloud3d_sub_.subscribe(this, "camera/3d/pointcloud", qos);
joint_sub_.subscribe(this, "fanuc/joint_state", qos);
range_sub_.subscribe(this, "laser/range", qos);
// 定义近似时间同步策略(4路消息)
using SyncPolicy = message_filters::sync_policies::ApproximateTime<
sensor_msgs::msg::Image,
sensor_msgs::msg::PointCloud2,
fanuc_msgs::msg::JointState,
sensor_msgs::msg::Range
>;
// 创建同步器,设置最大时间差0.2秒
sync_ = std::make_shared<message_filters::Synchronizer<SyncPolicy>>(
SyncPolicy(10), // 队列大小10
image2d_sub_, cloud3d_sub_, joint_sub_, range_sub_
);
sync_->setMaxIntervalDuration(rclcpp::Duration::from_seconds(0.2));
// 注册同步回调
sync_->registerCallback(
std::bind(&WeldingSensorFusion::fusionCallback, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)
);
}
private:
void fusionCallback(
const sensor_msgs::msg::Image::ConstSharedPtr& image2d,
const sensor_msgs::msg::PointCloud2::ConstSharedPtr& cloud3d,
const fanuc_msgs::msg::JointState::ConstSharedPtr& joint_state,
const sensor_msgs::msg::Range::ConstSharedPtr& range
) {
// 1. 焊缝检测(2D图像)
auto weld_seam = detectWeldSeam(image2d);
// 2. 工件定位(3D点云)
auto workpiece_pose = localizeWorkpiece(cloud3d);
// 3. 机械臂运动学计算
auto end_effector_pose = calculateFK(joint_state);
// 4. 焊缝距离验证(激光)
if (range->range > 0.1 && range->range < 0.5) {
// 5. 融合数据,生成焊接控制指令
auto control_cmd = generateWeldingCmd(
weld_seam, workpiece_pose, end_effector_pose, range->range
);
control_pub_->publish(control_cmd);
}
}
// 订阅器
message_filters::Subscriber<sensor_msgs::msg::Image> image2d_sub_;
message_filters::Subscriber<sensor_msgs::msg::PointCloud2> cloud3d_sub_;
message_filters::Subscriber<fanuc_msgs::msg::JointState> joint_sub_;
message_filters::Subscriber<sensor_msgs::msg::Range> range_sub_;
// 同步器
std::shared_ptr<message_filters::Synchronizer<
message_filters::sync_policies::ApproximateTime<
sensor_msgs::msg::Image, sensor_msgs::msg::PointCloud2,
fanuc_msgs::msg::JointState, sensor_msgs::msg::Range
>
>> sync_;
// 控制指令发布器
rclcpp::Publisher<fanuc_msgs::msg::WeldingCmd>::SharedPtr control_pub_;
};
int main(int argc, char * argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<WeldingSensorFusion>());
rclcpp::shutdown();
return 0;
}
7.3 关键优化点
- 同步策略:使用ApproximateTime处理异步传感器数据,设置合理slop值
- QoS配置:统一使用SensorDataQoS,确保数据传输效率
- 错误处理:激光距离超出有效范围时跳过控制指令生成
- 性能优化:队列大小设置为10,平衡实时性与消息丢失风险
总结
ROS2消息过滤是机器人开发的核心技能,掌握message_filters 与内容过滤订阅能显著提升系统性能与可靠性。
- 同步策略选择:根据传感器特性选择ExactTime/ApproximateTime/InputAligner
- QoS配置:确保订阅器与发布者QoS匹配,避免消息丢失
- 性能调优:合理设置队列大小、slop值与线程模型
- 错误处理:添加日志与调试机制,快速定位同步问题