3、激光雷达基础:工作原理、数据格式、常用型号介绍

各位同学,欢迎来到第三章。从这一章开始,我们正式进入传感器的世界。激光雷达,英文叫 LiDAR,是咱们这套联调系统的「眼睛」。没有它,机械臂就是个瞎子。

我最早接触激光雷达是在 2016 年做 AGV 导航项目的时候。当时被一堆点云数据搞得晕头转向,后来才慢慢摸清门道。今天我就把这些经验掰开揉碎讲给你听。

3.1 工作原理:它到底是怎么看见世界的?

说白了,激光雷达的原理特别简单:发射激光 → 碰到物体 → 反射回来 → 计算时间差 → 算出距离。就这么个循环,每秒重复几万到几十万次。

你想想看,光速是固定的(约 3×10⁸ m/s),只要测出激光从发射到返回的时间 Δt,距离 d 就能算出来:

d = c × Δt / 2

除以 2 是因为光走了个来回。这个公式看着简单,但实际工程里坑不少。我曾在室外强光环境下吃过亏——太阳光里的红外成分会干扰接收器,导致测距跳变。后来加了带通滤光片才解决。

根据扫描方式,市面上主流方案分三种:

类型 原理 典型代表 我个人的看法
机械旋转式 激光收发模块整体旋转 Velodyne VLP-16 技术成熟,但怕震动
固态式(MEMS) 微振镜反射激光 Livox Mid-40 体积小,寿命长
固态式(Flash) 面阵闪光一次成像 LeddarTech 系列 适合短距,无运动畸变
我的建议:做 MoveIt 联调实验,优先选机械旋转式。ROS 驱动最全,数据格式最标准,出了问题网上能找到一堆踩坑记录。

3.2 数据格式:ROS 里的两种「语言」

激光雷达把原始数据发出来,ROS 节点要能读懂。ROS 里封装了两种标准消息类型:LaserScan 和 PointCloud2。我习惯把前者叫「一维扫描线」,后者叫「三维点云」。两者可以互相转换。

3.2.1 LaserScan:单线雷达的「母语」

这种消息结构特别直观。想象你站在一个圆心上,朝四周均匀发射激光,每个角度测一个距离值。把这些距离按角度顺序排成一列,就是 LaserScan。

核心字段如下:

Header header          # 时间戳和坐标系ID
float32 angle_min      # 扫描起始角度(弧度)
float32 angle_max      # 扫描结束角度(弧度)
float32 angle_increment # 角度分辨率(弧度)
float32 time_increment  # 每束激光的时间间隔(秒)
float32 scan_time       # 整圈扫描时间(秒)
float32 range_min       # 最小有效测距(米)
float32 range_max       # 最大有效测距(米)
float32[] ranges        # 距离数组,长度 = (angle_max - angle_min) / angle_increment
float32[] intensities   # 反射强度数组(可选)

避坑指南:我曾经在调试时发现 rviz 里显示的距离全是 inf(无穷大)。查了半天,原来是 frame_id 写成了 laser 而不是 laser_frame。ROS 的 TF 树找不到这个坐标系,直接罢工。记住:header.frame_id 必须和 TF 树里发布的坐标系名称完全一致。

3.2.2 PointCloud2:三维世界的「通用语言」

单线雷达只能扫一个平面。但如果你用多线雷达(比如 16 线、32 线),或者想把多次扫描拼成三维数据,就得用 PointCloud2。

它的结构比 LaserScan 复杂一点:

Header header
uint32 height          # 点云高度(1 表示无序点云)
uint32 width           # 点云宽度(点数)
PointField[] fields    # 每个点的字段定义(x, y, z, intensity...)
bool is_bigendian      # 字节序
uint32 point_step      # 单个点的字节数
uint32 row_step        # 一行点的字节数
uint8[] data           # 原始二进制数据
bool is_dense          # 是否包含无效点

你可能会问:为什么不用 LaserScan 直接传三维数据?嗯,这里有个历史原因。LaserScan 设计时假设雷达是「单线 + 水平旋转」,数据天然是一维数组。多线雷达的数据是二维网格(线号 × 角度),硬塞进一维数组会丢失结构信息。

3.2.3 两者互转:laser_geometry 包

ROS 官方提供了 laser_geometry 包,里面有个 LaserProjection 类,能把 LaserScan 转成 PointCloud2。用法很简单:

#include <laser_geometry/laser_geometry.h>

laser_geometry::LaserProjection projector;
sensor_msgs::PointCloud2 cloud;
projector.projectLaser(scan_msg, cloud);

但要注意:这个转换默认假设激光束是从原点发出的。如果你的雷达安装位置有偏移,记得先设置好 TF 变换,否则点云位置会错位。我见过有人把雷达装在机械臂末端,没配 TF,结果点云全飘到天花板上了。

3.3 常用型号介绍:选对雷达,少走弯路

市面上的激光雷达品牌多如牛毛。我按应用场景给你梳理几个典型型号,都是 ROS 生态里验证过的:

型号 线数 测距 精度 ROS 支持 适合场景
RPLIDAR A1/A2 单线 12m / 16m ±1% 官方驱动 室内建图、避障
YDLIDAR X4 单线 10m ±2% 社区驱动 入门学习、低成本方案
Velodyne VLP-16 16 线 100m ±3cm 官方驱动 室外导航、三维感知
Livox Mid-40 固态 90m ±2cm 官方驱动 高动态、抗震动
Hesai PandarXT-16 16 线 80m ±2cm 官方驱动 工业场景、户外作业
注意:买雷达前一定确认 ROS 版本兼容性。比如 Velodyne 的驱动在 ROS 1 Melodic 上跑得很稳,但换到 ROS 2 Humble 可能需要额外编译。我去年帮学生调试,发现 Livox 的 ROS 2 驱动在 Ubuntu 22.04 上有段内存泄漏,折腾了两天才 patch 掉。

3.4 知识体系总览

下面这张图把本章的核心脉络串起来了。你可以把它当作一个「雷达知识地图」:

激光雷达知识体系 工作原理:ToF 测距 机械旋转式 MEMS 固态式 Flash 固态式 LaserScan PointCloud2 laser_geometry 互转 RPLIDAR A1/A2 Velodyne VLP-16 Livox Mid-40 Hesai PandarXT-16 核心思路 理解 ToF 原理 → 选择扫描方式 → 掌握 ROS 数据格式 → 选对硬件

3.5 动手验证:在 ROS 里「听」雷达说话

光看理论不过瘾。我建议你立刻打开终端,跑一下这个命令(假设你已经接好雷达并启动了驱动):

# 查看 LaserScan 消息
rostopic echo /scan

# 查看 PointCloud2 消息(如果驱动支持)
rostopic echo /velodyne_points

你会看到终端里刷刷刷地往外蹦数据。注意观察 ranges 数组的长度和数值变化。拿本书在雷达前面晃一晃,距离值会跟着变。这就是激光雷达在「看见」物体。

一个小实验:用 rostopic hz /scan 看看雷达的发布频率。如果是 10Hz,说明每 0.1 秒扫一圈。如果频率突然掉到 5Hz 以下,检查一下 CPU 占用——我遇到过 USB 带宽不够导致丢包的情况,换个 USB 3.0 口就好了。