相机雷达时间同步(基于ROS)

运行环境:

ubuntu20.04 noetic
usb_cam
速腾Robosense 16线
宏基暗影骑士笔记本

思路:

软同步:订阅相机和雷达原始数据,然后进行时间同步,最后将同步后的数据发布出去,通过rosbag record进行录制

同步前和同步后效果对比

同步前的话题:
/rslidar_packets
/usb_cam/image_raw

 # 录制命令
 rosbag record -O lidar_camera /usb_cam/image_raw /rslidar_points

# 查看bag时间戳
rqt_bag lidar_camera.bag

在这里插入图片描述

同步后的话题:
/sync/img
/sync/lidar

 # 录制命令
 rosbag record -O lidar_camera_syn_time /sync/img /sync/lidar

在这里插入图片描述

1.1创建工作空间

mkdir -p sys_time_ws/src
cd sys_time_ws
catkin_make
code .

1.2创建功能包

1) 在工作空间src目录下创建功能包

sys_time
roscpp rospy std_msgs

2.1编写源文件

1)在 src 目录下新建文件 sub_and_pub.cpp

代码解释:
this 关键字指向类创建的对象
registerCallback 绑定回调函数,触发回调函数发布同步后的数据

#include "sys_time/sub_and_pub.h"
 
# 重写头文件中的构造函数subscriberANDpublisher()
# main函数初始化对象(subscriberANDpublisher sp)时自动调用构造函数
subscriberANDpublisher::subscriberANDpublisher()
{
    //订阅话题
    lidar_sub.subscribe(nh, "/rslidar_points", 1);
    camera_sub.subscribe(nh, "/usb_cam/image_raw", 1);

    //消息过滤器,使用 ApproximateTime 进行时间同步(允许一定程度的时间误差)
    sync_.reset(new message_filters::Synchronizer<syncpolicy>(syncpolicy(10), camera_sub, lidar_sub));
    sync_->registerCallback(boost::bind(&subscriberANDpublisher::callback, this, _1, _2));

    //发布者
    camera_pub = nh.advertise<sensor_msgs::Image>("sync/img", 10);
    lidar_pub = nh.advertise<sensor_msgs::PointCloud2>("sync/lidar", 10);
}

void subscriberANDpublisher::callback(const sensor_msgs::ImageConstPtr& image, const sensor_msgs::PointCloud2ConstPtr& pointcloud) {
    ROS_INFO("Received synchronized message!");
    camera_pub.publish(image);
    lidar_pub.publish(pointcloud);
}

2.2编写头文件

1)在功能包下的 include/功能包名 目录下新建头文件 sub_and_pub.h
2)配置 includepath 详情见

#ifndef SUB_AND_PUB_H
#define SUB_AND_PUB_H
//ros头文件
#include <ros/ros.h>
//时间同步
#include <message_filters/subscriber.h>
#include <message_filters/sync_policies/approximate_time.h>
#include <message_filters/synchronizer.h>
//传感器消息
#include <sensor_msgs/Image.h>
#include <sensor_msgs/image_encodings.h>
#include <sensor_msgs/PointCloud2.h>
 
class subscriberANDpublisher{
public:
    subscriberANDpublisher();
    void callback(const sensor_msgs::ImageConstPtr &image, const sensor_msgs::PointCloud2ConstPtr &pointcloud);
 
private:
    ros::NodeHandle nh;
    ros::Publisher camera_pub;
    ros::Publisher lidar_pub;
    message_filters::Subscriber<sensor_msgs::PointCloud2> lidar_sub;//雷达订阅
    message_filters::Subscriber<sensor_msgs::Image> camera_sub;//相机订阅
 
    typedef message_filters::sync_policies::ApproximateTime<sensor_msgs::Image, sensor_msgs::PointCloud2> syncpolicy;//时间戳对齐规则
    typedef message_filters::Synchronizer<syncpolicy> Sync;
    boost::shared_ptr<Sync> sync_;//时间同步器
 
};
#endif

2.3编写可执行文件

#include <ros/ros.h>
#include "sys_time/sub_and_pub.h"

 
int main(int argc, char **argv) {
    ros::init(argc, argv, "node");
    subscriberANDpublisher sp;
    ROS_INFO("main done! ");
    ros::spin();
}

2.4配置文件

1)修改CMakeLists.txt

# 添加message_filters ROS包依赖
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_filters
)

# 头文件路径
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)

# 注意:根据include文件夹位置去修改
# 新建c++库,将头文件和源文件添加到新库里面
add_library(sys_time_lib
  include /sys_time/sub_and_pub.h
  src/sub_and_pub.cpp
)

# 将src目录下的main.cpp编译成可执行文件
add_executable(main.cpp src/main.cpp)

# 将新库和ros库链接起来
target_link_libraries(sys_time_lib
  ${catkin_LIBRARIES}
)

# 将可执行文件和新库链接起来
target_link_libraries(main.cpp
  sys_time_lib
  ${catkin_LIBRARIES}
)

2)修改package.xml (实际上不修改也可以通过编译)

  <exec_depend>message_filters</exec_depend>

3.1编译运行

1)运行时间同步节点

# 编译
ctrl+shift+b

roscore
source ./devel/setup.bash
rosrun sys_time main.cpp

在这里插入图片描述

2)启动相机

cd usb_cam_ws
source ./devel/setup.bash
roslaunch usb_cam usb_cam-test.launch

3)启动雷达

cd robosense_ws
source ./devel/setup.bash
roslaunch rslidar_sdk start.launch

当相机雷达全部启动后,显示接受到时间同步消息
在这里插入图片描述

4.1录制时间同步后的rosbag

lidar_camera_syn_time 是保存的rosbag名称
/sync/img 和 /sync/lidar 是录制的话题名

rosbag record -O lidar_camera_syn_time /sync/img /sync/lidar
# 查看录制好的rosbag
rosbag info lidar_camera_syn_time.bag

在这里插入图片描述

4.2rviz可视化rosbag

配置文件详细步骤见:Robosense激光雷达录制rosbag

1)打开rviz

rviz

2)导入配置文件 file–open cofig
在这里插入图片描述

3)添加相机话题
在这里插入图片描述

在这里插入图片描述

4)播放rosbag

rosbag play lidar_camera_syn_time.bag

在这里插入图片描述


⭐⭐⭐ 嘟嘟崽 ⭐⭐⭐
⭐⭐⭐ 祝你成功 ⭐⭐⭐
  • 11
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
以下是一个基于ROS的激光雷达+IMU畸变补偿的示例代码: ``` #include <ros/ros.h> #include <sensor_msgs/Imu.h> #include <sensor_msgs/LaserScan.h> #include <tf/transform_listener.h> #include <tf/transform_broadcaster.h> class LaserImuCalibration { public: LaserImuCalibration(); void imuCallback(const sensor_msgs::Imu::ConstPtr& imu_msg); void scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan_msg); private: ros::NodeHandle nh_; ros::Subscriber imu_sub_; ros::Subscriber scan_sub_; tf::TransformListener tf_listener_; tf::TransformBroadcaster tf_broadcaster_; ros::Publisher scan_pub_; sensor_msgs::LaserScan scan_msg_; bool imu_received_; bool scan_received_; double imu_roll_, imu_pitch_, imu_yaw_; double imu_gx_, imu_gy_, imu_gz_; double imu_ax_, imu_ay_, imu_az_; double imu_time_; double scan_time_; }; LaserImuCalibration::LaserImuCalibration() : imu_received_(false), scan_received_(false) { imu_sub_ = nh_.subscribe("/imu/data", 1, &LaserImuCalibration::imuCallback, this); scan_sub_ = nh_.subscribe("/scan", 1, &LaserImuCalibration::scanCallback, this); scan_pub_ = nh_.advertise<sensor_msgs::LaserScan>("/scan_calibrated", 1); } void LaserImuCalibration::imuCallback(const sensor_msgs::Imu::ConstPtr& imu_msg) { imu_received_ = true; imu_roll_ = imu_msg->orientation.x; imu_pitch_ = imu_msg->orientation.y; imu_yaw_ = imu_msg->orientation.z; imu_gx_ = imu_msg->angular_velocity.x; imu_gy_ = imu_msg->angular_velocity.y; imu_gz_ = imu_msg->angular_velocity.z; imu_ax_ = imu_msg->linear_acceleration.x; imu_ay_ = imu_msg->linear_acceleration.y; imu_az_ = imu_msg->linear_acceleration.z; imu_time_ = imu_msg->header.stamp.toSec(); } void LaserImuCalibration::scanCallback(const sensor_msgs::LaserScan::ConstPtr& scan_msg) { scan_received_ = true; scan_time_ = scan_msg->header.stamp.toSec(); scan_msg_ = *scan_msg; } int main(int argc, char** argv) { ros::init(argc, argv, "laser_imu_calibration"); LaserImuCalibration node; while (ros::ok()) { ros::spinOnce(); if (node.imu_received_ && node.scan_received_) { // Get the laser scanner transformation tf::StampedTransform laser_transform; try { node.tf_listener_.lookupTransform("laser", "imu", ros::Time(0), laser_transform); } catch (tf::TransformException ex) { ROS_ERROR("%s", ex.what()); continue; } // Compute the rotation matrix from the IMU data tf::Quaternion q(imu_roll_, imu_pitch_, imu_yaw_); tf::Matrix3x3 m(q); double imu_roll, imu_pitch, imu_yaw; m.getRPY(imu_roll, imu_pitch, imu_yaw); // Compute the IMU acceleration in the laser scanner frame tf::Vector3 imu_acceleration(imu_ax_, imu_ay_, imu_az_); imu_acceleration = laser_transform.getBasis() * imu_acceleration; // Compute the IMU angular velocity in the laser scanner frame tf::Vector3 imu_angular_velocity(imu_gx_, imu_gy_, imu_gz_); imu_angular_velocity = laser_transform.getBasis() * imu_angular_velocity; // Compute the time offset between the IMU and the laser scanner double time_offset = scan_time_ - imu_time_; // Update the laser scanner pose using the IMU data tf::Quaternion q_laser = laser_transform.getRotation(); tf::Quaternion q_imu(imu_roll, imu_pitch, imu_yaw); tf::Quaternion q_new = q_imu * q_laser; tf::Transform laser_transform_new(q_new, laser_transform.getOrigin()); laser_transform_new.setOrigin(laser_transform_new.getOrigin() + laser_transform_new.getBasis() * imu_acceleration * time_offset); laser_transform_new.setRotation(laser_transform_new.getRotation() + tf::Quaternion(0, 0, imu_angular_velocity.getZ() * time_offset, 1) * laser_transform_new.getRotation() * 0.5); // Publish the calibrated laser scanner data scan_msg_.header.stamp = ros::Time::now(); tf::transformStampedTFToMsg(tf::StampedTransform(laser_transform_new, scan_msg_.header.stamp, "imu", "laser"), scan_msg_.header.frame_id); scan_pub_.publish(scan_msg_); // Broadcast the laser scanner pose tf_broadcaster_.sendTransform(tf::StampedTransform(laser_transform_new, ros::Time::now(), "imu", "laser")); // Reset the received flags node.imu_received_ = false; node.scan_received_ = false; } } return 0; } ``` 该代码中,首先定义了LaserImuCalibration类,该类订阅IMU数据和激光雷达数据,然后计算IMU数据和激光雷达数据之间的转换关系,最后发布校准后的激光雷达数据。 在imuCallback回调函数中,获取IMU的姿态角、角速度和加速度等数据。在scanCallback回调函数中,获取激光雷达数据。 在计算激光雷达和IMU之间的转换关系时,需要获取激光雷达和IMU之间的变换关系,可以使用tf库中的tf_listener查找二者之间的变换关系。然后,根据IMU的姿态角、角速度和加速度等数据,计算出激光雷达在IMU坐标系下的位姿,并通过发布校准后的激光雷达数据和广播激光雷达位姿,完成畸变补偿。 需要注意的是,该代码仅供参考,具体实现需要根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

圆嘟嘟2019

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值