ROS传感器时间同步

博客介绍多传感器融合中因采集频率不同导致的数据同步问题。以激光雷达为核心传感器,以其采集时刻为插入时间点,对IMU等其他传感器数据进行插值同步。详细说明了从ros缓冲区取数据、传感器同步插值及剔除未插值雷达点云帧等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多传感器融合过程中由于传感器之间的采集频率不同,导致无法保证传感器数据同步。这里以激光雷达为核心传感器,每次收到一次雷达数据,便以当前雷达数据采集时刻作为要插入的时间点,该时刻另一传感器IMU的数据通过插值获得。这里同样可以参考VINS里相机和IMU时间同步的函数代码getMeasurements()。

主程序在front_end_flow.cpp文件中的ReadData()函数添加。

ReadData()

  1. 从ros缓冲区中传感器数据取出来,并放进XXX_data_buff_容器中
  2. 传感器同步,另外三种传感器做插值
  3. 如果任意传感器没有插值,剔除该雷达点云帧,找下一个点云帧做融合

1、读取传感器数据放进XXX_data_buff_容器中
雷达点云作为主传感器,其他传感器做插值。包含IMU信息(unsynced_imu_)、速度信息(unsynced_velocity_)、GNSS信息(unsynced_gnss_)
// 雷达点云不需要插值,直接放进cloud_data_buff_容器中
 

   cloud_sub_ptr_->ParseData(cloud_data_buff_);
    // 其他传感器原始数据放进未做同步的临时容器中unsynced_XXX_
    static std::deque<IMUData> unsynced_imu_;
    static std::deque<VelocityData> unsynced_velocity_;
    static std::deque<GNSSData> unsynced_gnss_;
    // 放进XXX_data_buff_容器中
    imu_sub_ptr_->ParseData(unsynced_imu_);
    velocity_sub_ptr_->ParseData(unsynced_velocity_);
    gnss_sub_ptr_->ParseData(unsynced_gnss_);
    // 点云容器不能为空
    if (cloud_data_buff_.size() == 0)
        return false;

以imu的ParseData()函数为例:

ParseData()
读取数据,将新的传感器数据new_imu_data_填充进imu容器imu_data_buff中。

void IMUSubscriber::ParseData(std::deque<IMUData>& imu_data_buff) {
    if (new_imu_data_.size() > 0) {
        imu_data_buff.insert(imu_data_buff.end(), new_imu_data_.begin(), new_imu_data_.end());
        new_imu_data_.clear();
    }
}



2、传感器同步,另外三种传感器做插值
cloud_time为主传感器激光雷达的参考时间,然后后面通过SyncData()函数对三个传感器进行插值实现同步操作。具体就是索引和插值。

 double cloud_time = cloud_data_buff_.front().time;

    bool valid_imu = IMUData::SyncData(unsynced_imu_, imu_data_buff_, cloud_time);
    bool valid_velocity = VelocityData::SyncData(unsynced_velocity_, velocity_data_buff_, cloud_time);
    bool valid_gnss = GNSSData::SyncData(unsynced_gnss_, gnss_data_buff_, cloud_time);


3、如果任意传感器没有插值,剔除该雷达点云帧,找下一个点云帧做融合

static bool sensor_inited = false;
    if (!sensor_inited) {
        if (!valid_imu || !valid_velocity || !valid_gnss) {
            cloud_data_buff_.pop_front();
            return false;
        }
        sensor_inited = true;
    }

插值SyncData()
插值的流程是先获得主传感器时间,然后根据索引结果获得这一同步时间前后的两帧数据,根据前后两帧数据的采集时刻,以及要插入的时刻,根据比例获得权重得到另一传感器在同步时间的结果。这里以IMU数据为例,三种传感器流程类似。

找到与同步时间相邻的左右两个数据,在UnsyncedData.at(0)和.at(1)之间
根据时间差计算权重线性插值
输入原始数据,输出线性插值后的数据。

bool IMUData::SyncData(std::deque<IMUData>& UnsyncedData, std::deque<IMUData>& SyncedData, double sync_time) {
    // 1.保证数据大于2个,同步时间在两个传感器数据UnsyncedData.at(0)和.at(1)中间
    while (UnsyncedData.size() >= 2) {
        // a. 同步时间sync小于传感器第一个数据,失败
        if (UnsyncedData.front().time > sync_time) 
            return false;
        // b. 同步时间sync大于第二个数据,将第二个设置为.at(0)
        if (UnsyncedData.at(1).time < sync_time) {
            UnsyncedData.pop_front();
            continue;
        }
        // c. 距离前一个时间差值较大,说明数据有丢失,前一个不能用
        if (sync_time - UnsyncedData.front().time > 0.2) {
            UnsyncedData.pop_front();
            break;
        }
        // d. 距离后一个时间差值较大,说明数据有丢失,后一个不能用
        if (UnsyncedData.at(1).time - sync_time > 0.2) {
            UnsyncedData.pop_front();
            break;
        }
        break;
    }
    if (UnsyncedData.size() < 2)
        return false;
    // 同步数据synced,前后两帧数据front、back
    IMUData front_data = UnsyncedData.at(0);
    IMUData back_data = UnsyncedData.at(1);
    IMUData synced_data;
    // 2、根据时间差计算权重线性插值
    double front_scale = (back_data.time - sync_time) / (back_data.time - front_data.time);
    double back_scale = (sync_time - front_data.time) / (back_data.time - front_data.time);
    synced_data.time = sync_time;// 同步时间
    // 线速度
    synced_data.linear_acceleration.x = front_data.linear_acceleration.x * front_scale + back_data.linear_acceleration.x * back_scale;
    synced_data.linear_acceleration.y = front_data.linear_acceleration.y * front_scale + back_data.linear_acceleration.y * back_scale;
    synced_data.linear_acceleration.z = front_data.linear_acceleration.z * front_scale + back_data.linear_acceleration.z * back_scale;
    // 角速度
    synced_data.angular_velocity.x = front_data.angular_velocity.x * front_scale + back_data.angular_velocity.x * back_scale;
    synced_data.angular_velocity.y = front_data.angular_velocity.y * front_scale + back_data.angular_velocity.y * back_scale;
    synced_data.angular_velocity.z = front_data.angular_velocity.z * front_scale + back_data.angular_velocity.z * back_scale;
    // 四元数插值有线性插值和球面插值,球面插值更准确,但是两个四元数差别不大是,二者精度相当
    // 由于是对相邻两时刻姿态插值,姿态差比较小,所以可以用线性插值
    synced_data.orientation.x = front_data.orientation.x * front_scale + back_data.orientation.x * back_scale;
    synced_data.orientation.y = front_data.orientation.y * front_scale + back_data.orientation.y * back_scale;
    synced_data.orientation.z = front_data.orientation.z * front_scale + back_data.orientation.z * back_scale;
    synced_data.orientation.w = front_data.orientation.w * front_scale + back_data.orientation.w * back_scale;
    synced_data.orientation.Normlize();// 线性插值之后要归一化
    
    // 最后插入即可
    SyncedData.push_back(synced_data);

    return true;
}


四种同步时间关系如下图所示。

在这里插入图片描述


原文链接:https://blog.youkuaiyun.com/try_again_later/article/details/105809478

### ROS 中的数据同步方法 在 ROS(Robot Operating System)中,数据同步通常通过 `message_filters` 库来实现。该库提供了多种同步策略,用于处理来自多个话题的消息流并确保它们的时间戳一致。以下是关于如何在 ROS 中实现数据同步的具体说明: #### 使用 message_filters 实现同步 `message_filters` 是 ROS 提供的一个功能强大的工具集,专门用来管理多条消息流之间的关系。它支持基于时间戳的同步以及回调函数触发。 - **创建 NodeHandle Subscriber** 需要定义两个订阅者分别监听 `/camera/image` `/camera/info` 这两个话题[^1]。 ```cpp #include <ros/ros.h> #include <sensor_msgs/Image.h> #include <sensor_msgs/CameraInfo.h> #include <message_filters/subscriber.h> #include <message_filters/synchronizer.h> #include <message_filters/sync_policies/approximate_time.h> typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::Image, sensor_msgs::CameraInfo> MySyncPolicy; int main(int argc, char** argv){ ros::init(argc, argv, "image_sync_node"); ros::NodeHandle nh; // 定义两个Subscriber对象 message_filters::Subscriber<sensor_msgs::Image> image_sub(nh, "/camera/image", 1); message_filters::Subscriber<sensor_msgs::CameraInfo> info_sub(nh, "/camera/info", 1); // 创建Synchronizer对象,并传入MySyncPolicy两个Subscriber作为参数 typedef message_filters::Synchronizer<MySyncPolicy> Sync; boost::shared_ptr<Sync> sync(new Sync(MySyncPolicy(10), image_sub, info_sub)); sync->registerCallback(boost::bind(&callback, _1, _2)); ros::spin(); } ``` 上述代码片段展示了如何设置近似时间同步器 (approximate time synchronizer) 来匹配图像与相机信息的时间戳。 - **选择合适的同步策略** 可以选用精确时间同步 (`ExactTime`) 或近似时间同步 (`ApproximateTime`) 策略。前者要求两者的头文件中的时间字段完全相同;后者则允许一定范围内的误差存在,从而提高灵活性。 #### 关于计算图的理解 为了更好地理解整个系统的运作方式,在设计节点间通信之前应该熟悉 ROS 的核心概念——即所谓的 “计算图”。这包括但不限于以下几个方面: - 节点(Node): 执行特定任务的小型程序单元; - 主题(Topic): 发布/订阅模式下的异步消息传递渠道; - 消息(Message): 在各组件之间传输的信息载体; - 服务(Service): 请求响应式的同步调用接口[^2]。 这些基本组成部分共同构成了复杂的机器人应用框架。 #### 录制回放带有时序依赖性的数据 当涉及到长时间运行实验或者离线分析场景时,可能还需要考虑利用 rosbag 工具保存现场采集到的所有传感器读数以便后续重播测试之需。此时可以通过命令行选项控制录制长度,例如限定最大持续时间为30秒、5分钟或是2小时不等[^4]: ```bash rosbag record --duration=30 /chatter rosbag record --duration=5m /chatter rosbag record --duration=2h /chatter ``` 以上操作能够帮助开发者更便捷地管理调试涉及多方协作的任务流程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值