做移动机器人感知的同行应该都有这种体会:第一次在 ROS2 里跑通 Livox-Mid-360 的官方驱动,看到/livox/lidar话题里 PointCloud2 消息的字段列表时,第一反应往往是“x、y、z 好说,reflectivity 也能猜个大概,但tag、line、offset_time到底是什么”?如果这些点云格式细节不搞清楚,后面做滤波、时间戳补偿、保存点云、跑 SLAM 建图,都会踩一堆莫名其妙的坑。这篇文章我就把 Mid-360 的点云格式彻底拆开,从硬件特性讲到字段定义,再给出一套能直接搬走的数据提取流程,最后整理连接设备、修改 IP、建图飘移等高频问题的排查方法。手里有 Mid-360、正在做点云处理或 SLAM 的同学,可以直接对照着操作。
1. Mid-360这套点云格式,为什么值得单独拆开讲
1.1 非重复扫描带来的格式差异
Mid-360 算得上是 Livox 家族里比较特殊的一员。它采用非重复扫描方式,水平视场角是完整的 360°,垂直视场角是 59°(-7° 到 +52°),最远探测距离在 90% 反射率目标下能到 90 米,10% 反射率下约 40 米,精度标称 ±3cm,默认 10Hz 帧率下每秒输出约 20 万个点。很多第一次用它的人会下意识拿机械式激光雷达的“线数”去套它,结果发现完全对不上,因为 Mid-360 根本不存在固定的线束编号。
这个硬件特性直接决定了点云格式的设计思路。机械雷达输出点云时,每个点天然携带“第几线”这个信息,所以数据结构里通常就是一个ring或者line就够了;但 Mid-360 是光学棱镜加电机做非重复扫描,每一帧内扫描轨迹会不断改变,点云不再按固定线束排列。为了把点云描述清楚,驱动层就必须额外引入一些字段来记录每个点的时间偏移、分类标签、回波来源等信息,这就形成了我们看到的 Livox 特有的一套点结构。可以说,理解这套格式,本质上是在理解“非重复扫描雷达怎么表达一帧点云”。
这种设计带来的一个直接好处是:单帧点云在空间上的覆盖更均匀,而且只要持续扫描一段时间(比如 0.1 秒到 0.2 秒),视场内的点云密度会显著增加,这对建图、目标检测非常有利。但它也带来一个麻烦:点与点之间的时间戳不是线性的,因为棱镜扫描路径是类利萨茹曲线,每一帧内点的生成顺序和空间位置没有严格的“从上到下”关系。所以驱动用offset_time来表示每个点相对帧起始时刻的偏移,这是 Mid-360 点云格式里最需要重视的字段之一。
1.2 完整工具链选型:ROS2还是SDK直连
做数据提取之前,先要把工具链选型想清楚。我目前的方案是基于 Ubuntu 22.04 + ROS2 Humble +livox_ros_driver2+ PCL 这套组合,原因是实际项目里你大概率要接 Cartographer、FAST-LIO 或者自己的建图算法,而以 ROS2 话题形式接收点云是最省事、最通用的方式。官方驱动已经把 UDP 数据包解析、坐标系定义、IMU 数据发布都处理好了,我们只需要订阅/livox/lidar话题就能拿到组织好的点云。
当然,如果做的是嵌入式或者自研系统,不想依赖 ROS,也可以直接用官方 Livox SDK 2 的 C++ 接口,在回调里拿原始点云结构体。但这样做的话,时间戳同步、帧头拼接、丢包重传这些底层逻辑都要自己处理,开发成本明显更高。对于大多数做导航、建图、感知的同学,我建议直接走 ROS2 加官方驱动这条路线,把精力放在业务算法上,而不是重复造轮子。
另外提醒一句:livox_ros_driver2本身依赖livox_sdk2,编译时还会用到 PCL 和 Eigen,环境一定要提前装好。很多人在colcon build阶段报错,基本都是缺依赖,后面我会给出具体的安装命令。
1.3 解析的整体思路:从字节流到可用的点
从传感器到最终能用的点云,中间经历的过程可以理解成一条数据链:Mid-360 通过网口把自定义协议的 UDP 数据包发出来,驱动里的 SDK 负责解析数据包,把原始字节还原成带坐标、反射率、标签的 Lidar 点结构,再按帧封装成 PointCloud2 消息发布到 ROS2 话题上。我们从 ROS2 拿来 PointCloud2 之后,还需要做一次“格式转换”,因为 PointCloud2 本质上只是一段字节流加一组字段描述,要真正做点云运算,得转成 PCL 点云或者自定义结构体。
解析整体的思路就是三步走:先认识硬件特性,搞清楚数据从哪来;再逐字段拆解 PointCloud2 里每个字段的物理含义;最后写代码把消息转成自己需要的点云类型并保存。后面几节我就按这个思路展开。
2. 点云格式核心细节拆解:字段没搞懂,后面全白搭
2.1 LivoxPointXyzrtl结构体逐字段拆解
livox_ros_driver2里默认使用的是一个叫LivoxPointXyzrtl的结构体,定义大致如下:
struct LivoxPointXyzrtl { float x; // X坐标,单位:m float y; // Y坐标,单位:m float z; // Z坐标,单位:m float reflectivity; // 反射率,范围约0~255 uint8_t tag; // 点分类标签 uint8_t line; // 虚拟线号 uint16_t offset_time; // 相对帧起始时刻的偏移,单位:us };每个字段的含义可以整理成下面这张表:
| 字段 | 类型 | 单位 | 含义 | 使用建议 |
|---|---|---|---|---|
| x / y / z | float | m | 点云三维坐标 | 直接用,注意坐标系方向 |
| reflectivity | float | 0~255 | 目标反射率 | 可视化、滤波,但别指望它是标定后的物理量 |
| tag | uint8_t | 无 | 0正常 1雨 2尘 3地面 4低反射 | 预处理过滤非常好用 |
| line | uint8_t | 无 | 虚拟线号 | 别拿它当机械雷达的ring用 |
| offset_time | uint16_t | us | 相对帧起始时间偏移 | 去畸变和多传感器融合必须用 |
先讲reflectivity。Mid-360 的反射率是驱动层根据回波强度归一化出来的一个值,范围大致在 0 到 255 之间,白色墙、高反射率标靶的值会比较高,黑色车辆、深色织物则很低。但它不是一个严格物理定标后的反射率数值,受距离、入射角影响很大,所以做阈值过滤时不要从某个全局固定值上一刀切。我实测下来,同样一块黑白棋盘格,放在 2 米和 15 米处,反射率值会有明显差异,这在做目标检测数据清洗时要特别注意。
然后是tag字段,这是 Livox 点云里很实用的一个设计。SDK 会尝试对每个点做分类,常见分类包括正常点、雨滴、灰尘、地面点、低反射率点等。实际使用中,用这一位就能快速滤除大部分雨雾噪点,比单纯用反射率阈值更稳,因为雨滴和灰尘在某个距离范围内可能表现出较高反射率,直接按反射率滤会误删目标点。后面问题排查部分我会给出具体过滤代码。
line字段是很多新手容易误解的地方。Mid-360 没有物理线束,这个字段严格来说是驱动为兼容协议而保留的虚拟线号。我遇到的情况是,在某些固件版本下 Mid-360 的line值会一直为 0 或变化规律不明显,所以如果你要跑依赖 ring 编号的地面分割算法,需要先确认当前固件输出是否可靠,不要默认它能像机械雷达那样用。最后是offset_time,它记录了这个点相对当前帧数据起始时刻的时间偏移,单位微秒。这个字段对于运动畸变矫正非常关键,因为一帧 20 万个点并不是同一时刻采集的,而是分布在 100ms 的扫描周期内,如果忽略时间偏移把所有点当成同一时刻的数据,车一动起来点云就会出现轻微拖影。
2.2 时间戳、坐标系与IMU:数据里真正值钱的隐藏信息
坐标字段虽然直观,但真正决定点云能不能用于建图融合的,是时间戳和坐标系。ROS2 的 PointCloud2 消息里有一个header.stamp,它表示帧起始时间;而每个点自己的绝对时间,应该是header.stamp + offset_time * 1us。注意offset_time是uint16_t,最大能表示 65535 微秒,约 65.5ms,刚好够覆盖 10Hz 的一帧周期,这也是为什么帧率变化时它需要重新对齐。
时间戳的来源取决于驱动和硬件配置。如果雷达启用了 PTP 或 PPS 时间同步,header.stamp会基于雷达的硬件时钟,精度很高;如果完全没有外部同步,驱动会用主机收到数据包的时间作为基准。后者在电脑负载高、网卡中断延迟大的时候会有抖动,对低速场景影响不大,但做高速移动或紧耦合融合时最好还是上 PTP 同步。
坐标系同样容易踩坑。Mid-360 默认的frame_id一般是livox_frame,如果你把雷达固定在车体上,使用点云前必须通过 TF 发布从livox_frame到base_link或map的外参变换。外参不准确,后面做的一切都建立在错误坐标上。另一个隐藏信息是 IMU。Mid-360 内置了 IMU,livox_ros_driver2会单独发布/livox/imu话题,IMU 数据在 FAST-LIO 这类紧耦合 SLAM 里会直接参与状态估计,在 Cartographer 里也能作为位姿预测的来源。建图前最好确认 IMU 话题频率正常,并且 IMU 的坐标系和雷达坐标系的变换关系标定无误。
2.3 单回波、双回波与点频:影响占用率和处理时序
Mid-360 默认工作在单回波模式,每秒约 200k 点,10Hz 下每帧大约 2 万个点。如果硬件支持并切换为双回波模式,输出点频会成倍增加,每帧点数可能到 4 万甚至更多。但这里要注意,点云格式本身没有变化,字段还是一样的,只是数据量更大,带宽和 CPU 占用会明显上升。对于只做建图的场景,默认单回波通常就够用;做感知或需要处理雨雾、遮挡边缘时,双回波能提供更多回波信息,但也要接受更高的处理成本。
点频还影响offset_time的时间分布。单回波和双回波模式下,一帧内点的时间跨度都接近 100ms,但点数不同,意味着每两个点之间的时间间隔不同。做运动补偿时,不能假设一帧内点是均匀分布的,最好逐点读取offset_time计算相对运动量。另外从数据带宽角度看,20 万点/秒的 PointCloud2 消息体积约是每秒 20 万乘以 13 字节(x/y/z/reflectivity 各 4 字节,加 tag/line/offset_time 共 4 字节)再加消息头,实际量级在 3~5MB/s,千兆网完全扛得住,但如果你用 ROS2 的默认 QoS 订阅高频大点云,丢帧风险很高,所以订阅时要用SensorDataQoS(),这点后面代码里会体现。
3. 数据提取实战:从ROS2话题到PCD全流程
3.1 环境准备与驱动部署
先把环境搭好。我以 Ubuntu 22.04 + ROS2 Humble 为例,终端里逐条执行:
# 安装基础依赖 sudo apt install ros-humble-pcl-ros ros-humble-pcl-conversions