3、传感器数据处理基础:激光雷达、IMU与里程计

做SLAM这么多年,我越来越觉得传感器数据是整条流水线的“原材料”。原材料不好,后面算法再牛也白搭。今天咱们就聊聊激光雷达、IMU和里程计这三样东西——怎么读数据、怎么看数据、怎么让它们不坑你。

3.1 激光雷达数据格式与可视化

激光雷达分两种:2D和3D。2D的扫一个平面,3D的扫一个球面。你想想看,一个像扫地机器人那样转圈,一个像自动驾驶车顶那个“花盆”一样转。

3.1.1 2D激光雷达:LaserScan

ROS里2D激光雷达的消息类型是 sensor_msgs/LaserScan。说白了就是一个数组,每个元素代表某个角度测到的距离。

# LaserScan 消息结构
std_msgs/Header header        # 时间戳、坐标系
float32 angle_min             # 起始角度(弧度)
float32 angle_max             # 终止角度(弧度)
float32 angle_increment       # 角度分辨率
float32 time_increment        # 每帧时间间隔
float32 scan_time             # 扫描周期
float32 range_min             # 最小有效距离
float32 range_max             # 最大有效距离
float32[] ranges              # 距离数据(核心)
float32[] intensities         # 反射强度(可选)

我在项目中遇到过一个问题:某款雷达的 angle_min 是负值,angle_max 是正值。如果你直接按索引0对应最小角度,那地图就会歪掉。嗯,这里要注意——一定要用角度值去算坐标,别用索引号硬算。

核心转换公式:将极坐标转成笛卡尔坐标

x = range * cos(angle_min + i * angle_increment)
y = range * sin(angle_min + i * angle_increment)

3.1.2 3D激光雷达:PointCloud2

3D雷达的数据量大得多。ROS里用 sensor_msgs/PointCloud2。它不像LaserScan那样规整,每个点可以带xyz坐标、强度、时间戳、甚至RGB颜色。

我个人习惯用 pcl_ros 库来处理点云。它能把ROS消息直接转成PCL的点云对象,方便做滤波、分割、配准。

// C++ 示例:订阅点云并转成PCL格式
void cloudCallback(const sensor_msgs::PointCloud2ConstPtr& msg)
{
    pcl::PointCloud<pcl::PointXYZ> cloud;
    pcl::fromROSMsg(*msg, cloud);
    // 现在可以愉快地使用PCL算法了
}

小技巧:3D点云数据量大,别在回调函数里做复杂计算。我一般用 tf2_ros::Buffer 缓存一下,再开个单独线程处理。

3.2 IMU数据读取与标定

IMU这东西,说白了就是加速度计+陀螺仪。加速度计测重力方向,陀螺仪测角速度。但你要直接拿原始数据用,那肯定翻车——因为IMU有零偏、有噪声、有温漂。

3.2.1 IMU消息类型

ROS里IMU的消息是 sensor_msgs/Imu:

std_msgs/Header header
geometry_msgs/Quaternion orientation     # 姿态(通常由滤波算法给出)
float64[9] orientation_covariance        # 姿态协方差
geometry_msgs/Vector3 angular_velocity   # 角速度(rad/s)
float64[9] angular_velocity_covariance
geometry_msgs/Vector3 linear_acceleration # 线加速度(m/s²)
float64[9] linear_acceleration_covariance

注意:orientation 字段很多IMU驱动不填,或者填的是原始四元数。我建议你拿到数据后先检查一下这个字段是不是真的可用。

3.2.2 IMU标定:别跳过这一步

我曾经偷懒没标定IMU,结果融合出来的姿态每10分钟漂了30度。后来老老实实做了标定,效果立竿见影。

标定主要做两件事:

  • 零偏标定:静止状态下采集一段时间数据,取均值作为零偏
  • 尺度与对齐:加速度计三轴是否正交,陀螺仪是否与重力方向对齐

常用的标定工具有:

  • imu_utils:基于Allan方差分析,能给出噪声密度和零偏稳定性
  • imu_calib:简单易用,适合快速标定

避坑指南:标定时IMU要放在水平面上,远离震动源。我曾经把IMU放在桌子上,旁边有人敲键盘,结果标定出来的零偏全是错的。嗯,后来我学乖了,标定时把IMU放在泡沫垫上。

3.3 里程计(Odometry)消息类型

里程计是SLAM的核心输出之一。ROS里用 nav_msgs/Odometry:

std_msgs/Header header
string child_frame_id           # 通常为 "base_footprint" 或 "base_link"
geometry_msgs/PoseWithCovariance pose     # 位置+姿态+协方差
geometry_msgs/TwistWithCovariance twist   # 线速度+角速度+协方差

这里有个容易搞混的点:header.frame_id 是父坐标系(比如 odom),child_frame_id 是子坐标系(比如 base_link)。说白了,这个消息告诉你“机器人相对于世界坐标系在哪,以及它现在运动得多快”。

协方差矩阵很重要。它告诉滤波器“这个里程计数据有多可信”。我见过有人直接把协方差设成单位矩阵,结果融合出来的轨迹抖得像筛子。

经验值参考:

传感器类型位置协方差(m²)角度协方差(rad²)
轮式里程计0.01 ~ 0.10.001 ~ 0.01
激光里程计0.001 ~ 0.010.0001 ~ 0.001
视觉里程计0.01 ~ 0.50.001 ~ 0.1

3.4 使用RViz进行传感器数据可视化

RViz是ROS的“眼睛”。你写再多代码,不如在RViz里看一眼数据来得直观。

3.4.1 基本配置

启动RViz后,添加以下显示项:

  • LaserScan:显示2D激光雷达数据,选好话题名和颜色
  • PointCloud2:显示3D点云,可以按高度或强度着色
  • Imu:显示IMU的加速度和角速度箭头
  • Odometry:显示机器人位姿和路径

我个人习惯把 Fixed Frame 设为 map 或 odom,这样所有数据都在同一个坐标系下显示。

3.4.2 调试技巧

你想想看,如果激光雷达数据在RViz里显示的位置和实际对不上,那多半是TF树没配好。我一般先检查 tf_echo 看看坐标系变换对不对。

还有一个常见问题:点云显示出来全是黑的。那是因为点云没有颜色信息,或者强度值范围不对。把 Color Transformer 改成 AxisColor 或者 Intensity 就能看到东西了。

调试口诀:数据不对看话题,位置不对看TF,颜色不对看着色器。

3.5 本章知识体系

下面这张图总结了传感器数据处理的整体流程。从原始数据到可视化,每一步都有坑,但每一步也都有套路。

传感器数据处理流程 激光雷达 LaserScan / PointCloud2 IMU sensor_msgs/Imu 里程计 nav_msgs/Odometry 数据解析与转换 极坐标→笛卡尔 / PCL 标定与滤波 零偏标定 / Allan方差 协方差估计 不确定性建模 RViz 可视化 LaserScan / PointCloud2 / Imu / Odometry 所有传感器数据最终汇聚到RViz,用于调试和验证

好了,这一章的内容就这些。传感器数据处理是SLAM的基石,别嫌它基础。你想想看,如果连数据都读不对、看不明,后面建图导航再花哨也是空中楼阁。

可可精品资料库