拿到Livox Mid-360的第一周我一直在跟点云格式较劲。这台雷达的话题默认不是PointCloud2而是CustomMsg里面的点也不是简单的xyz加强度还有offset_time、tag、line这些看起来陌生却不难理解的字段。一开始我也没当回事直到做数据提取时发现坐标和强度对不上才静下心回头啃字段定义。这篇文章就把我从驱动配置、IP设置、字段拆解到写代码把点云提取成PCD/CSV的完整过程记录下来顺便解答几个高频问题Mid-360 IP怎么改、建图为什么会飘、CloudCompare怎么把点云保存成tif格式。适合刚拿到Mid-360、正在被点云格式折磨的工程师和SLAM学习者。1. Mid-360点云格式究竟特殊在哪1.1 先搞清楚它输出什么话题和消息在使用前先认识一下这台雷达。Mid-360是Livox推出的非重复扫描激光雷达水平视场接近360°垂直视场约59°探测距离在40米左右。它和传统机械雷达最大的区别是扫描方式不是固定线束而是通过棱镜摆动形成花瓣状覆盖点云分布会随着时间越来越密。这样的设计带来了不错的近处覆盖也带来一个麻烦点云没有固定线束编号也没有均匀的角度间隔如果沿用机械雷达那套“按线束扫描一圈”的思路去解析几乎处处别扭。所以Livox驱动没有把原始数据硬塞进PointCloud2而是提供了自定义消息。以ROS2下的livox_ros_driver2为例默认发布的点云话题是/livox/lidar消息类型为livox_ros_driver2/msg/CustomMsg。如果从ROS1迁移过来消息名是livox_ros_driver/CustomMsg字段结构基本一致。用ros2 interface show可以看到完整定义livox_ros_driver2/msg/CustomMsg: std_msgs/Header header uint32 timebase uint32 point_num uint8 lidar_id livox_ros_driver2/msg/CustomPoint[] pointsCustomPoint包含uint32 offset_time float32 x float32 y float32 z uint8 reflectivity uint8 tag uint8 line对不熟悉的人第一眼会觉得字段没比标准点云多多少实际上每个字段都有用途。timebase是这一帧点云的基准时间戳offset_time是每个点相对基准时间的时间偏移这为运动畸变校正留下了关键信息reflectivity是反射率tag是点属性标记line在非重复扫描雷达上并不是传统线号。1.2 为什么Livox非要自定义消息理解了自定义消息的意义会比抄代码重要得多。标准PointCloud2虽然通用但扩展能力很弱想为每个点再加一个“采集时刻相对基准的偏移”字段要么塞进intensity的高位要么干脆再造消息还要同步维护坐标和字段顺序。Livox自定义消息直接把这类细节暴露出来至少保证了三点。一是保留逐点时间信息。传统毫米波雷达、机械雷达中一帧点云通常认为近似同一时刻采集但Mid-360是非重复扫描一帧内部不同点的采集时刻差异能达到几十毫秒如果不做补偿车辆运动稍快一点点云就会畸变建图自然飘。offset_time就是为这个准备的。二是可以通过tag快速判断点是否有效。雷达在雨雾、太阳直射、边缘扫描等条件下会产生异常点靠纯距离滤波很难滤干净。tag可以直接告诉你这个点是否正常这比从坐标上猜要可靠得多。三是方便下游算法直接使用。Livox团队自己的Fast-LIO、Livox-SDK都基于这套自定义格式处理避免了转一次PointCloud2再转一次的重复劳动。所以不要急着把所有数据转成标准格式至少在初始分析阶段先把CustomMsg理解透后面滤波、去畸变、时间同步都会顺很多。2. 环境准备驱动安装、IP修改与消息类型确认2.1 驱动安装与选择在开始提取数据前先确保雷达能稳定出点。官方现在推荐的是livox_ros_driver2仓库里同时支持ROS1和ROS2建议直接源码编译。以Ubuntu ROS2 humble为例基本流程是mkdir -p livox_ws/src cd livox_ws/src git clone -b ros2 https://github.com/Livox-SDK/livox_ros_driver2.git cd .. colcon build --symlink-install source install/setup.bash编译完成后驱动节点需要配置文件一般是config/livox_lidar_config.json。里面和接收点云直接相关的是广播码、雷达IP、主机IP这几个字段。如果雷达是刚开封的先用默认配置启动再用Livox Viewer扫描设备。这里我说一下通常连接方式雷达通过千兆网口直连电脑电脑网卡配置成192.168.1.x网段比如192.168.1.50掩码255.255.255.0雷达的静态IP要跟这个网段一致否则扫描不到。不要急着改雷达IP先确认网卡和雷达在同一网段。很多“收不到点云”的问题其实不是驱动问题而是网卡IP没配对。2.2 Mid-360雷达IP怎么修改搜索关键词里有很多人问“Mid-360s的激光雷达怎么修改IP地址”这里顺便记录一下我的操作路径。修改IP本身不是技术难点难在别弄错网络关系。步骤如下先用网线直连雷达和电脑给电脑网卡配一个静态IP例如192.168.1.50/24。打开Livox Viewer点击搜索设备。软件刷新后能看到雷达列表中会显示设备当前IP、广播码、固件版本。在设备右侧的“设置”或“属性”里修改IP改成你规划好的静态地址例如192.168.1.101。保存后雷达会重启等几十秒重新搜索一次确认新IP生效。如果你不想每次都开图形工具也可以在livox_lidar_config.json里修改lidar_ip和host_ip字段。但要特别注意修改后的雷达IP必须和主机IP在同一网段不然连接就会中断还得把雷达恢复出厂或者重新改成默认IP这个来回折腾很费时间。2.3 用ros2命令确认话题和消息类型环境跑起来后先不要急着写代码先用ROS2自带工具观察一下ros2 topic list ros2 topic info /livox/lidar ros2 interface show livox_ros_driver2/msg/CustomMsgtopic info会显示发布者类型和消息类型如果看到的是livox_ros_driver2/msg/CustomMsg说明数据通路正常。这时候再运行一个最简单的订阅打印把前几个点打出来确认x、y、z不是NaN也不是全0ros2 run your_pkg test_subscriber我在这一步踩过一个坑当时为了图快订阅了另一个驱动直接提供的PointCloud2话题结果所有点都显示强度为0连坐标单位都不对。后来才发现那个PointCloud2的字段顺序和原始CustomMsg并不一致不能直接拿来做精细分析。所以建议以CustomMsg为准。3. 点云格式逐字段拆解与提取要点3.1 x、y、z坐标单位、坐标系和非重复扫描特性CustomPoint中的x、y、z是浮点数单位是米采用Livox自身定义的笛卡尔坐标系x轴指向前方y轴指向左侧z轴指向上方。这个坐标系和多数机械雷达的“x前、y左、z上”一致方便直接接入SLAM和感知框架但和相机坐标系的“z前、x右、y下”差别很大融合前必须先对齐。因为是非重复扫描Mid-360的单帧点云内部并不均匀中心区域被棱镜反复扫过点密度高边缘区域扫描速度相对较快点密度低。你如果拿一帧点云和传统64线雷达比“线间距离”会发现完全比不了。这种非均匀性会直接影响体素降采样参数——体素太大会把中心细节都吃掉太小又会导致边缘点稀疏、计算量暴增。我一般先统计一帧的点数和空间分布再决定体素尺寸而不是直接套默认值。3.2 reflectivity反射率不是强度也不是物理反射率reflectivity字段类型是uint8取值范围0到255。很多教程直接把它当成“强度”但这其实是经过雷达内部处理的反射率值不是原始光子数也不是物理意义上的反射率百分比。它的作用更像是“物体反光能力的相对等级”用来区分高反条、金属表面、植被、地面这些不同材质。我在实际提取中遇到过一个问题同一块白色标定板不同距离下reflectivity从220降到110变化很大。所以如果要做目标检测或阈值分割建议别直接用原始值卡绝对值而是先做距离归一化或者干脆只拿它做辅助特征主要用几何信息。保存点云时想保留反射率常见的做法是放到PCD的intensity字段也可以单独存一个通道。CloudCompare里默认用intensity通道给点云上色比较直观。3.3 tag标记位先统计分布再决定过滤策略tag是点属性标记。官方文档里定义了很多位标志正常有效点通常tag 0某些非零点可能是异常点、噪声点也可能是特殊回波模式。实战中最安全的做法是先打印tag的分布比如统计0、1、2、4各有多少再决定过滤策略。有的新手一上来就写if p.tag ! 0: continue结果把雨雾噪声滤掉的同时也把很多弱回波点滤掉了点云明显变稀。我的建议是先做一次离线bag回放统计tag分布再利用ros2 bag play配合可视化工具看下过滤效果确认无误后再放进在线流程。过滤本身很简单if p.tag 0: valid_points.append(p)如果tag分布中有大量非零不要急着全删先查一下这些点长什么样是不是集中在某个区域、某类材质上再调整策略。3.4 offset_time运动畸变校正的关键钥匙offset_time是我觉得这个自定义格式里最值钱的字段。它是每个点相对timebase的时间偏移通常单位是纳秒。因为一帧点云内不同点的采集时刻并不相同如果你想做精确的去畸变、或者把点云和IMU做时间对齐就必须知道每个点是在哪个时刻采的。使用其实很直接整帧的参考时刻是timebase第i个点的真实采集时刻约等于timebase points[i].offset_time / 1e9。在我自己的代码里我会用一个中间结构保存每个点的(t, x, y, z, reflectivity, tag)后续不管是输给紧耦合SLAM还是做插值都方便。需要提醒的是不同驱动版本的offset_time单位可能不一样有纳秒也有微秒。拿到代码后先打印几个数值看看量级如果是接近十位数的数值说明是纳秒如果是几千、几万说明可能是微秒千万别直接用否则时间戳会错得非常离谱。4. 数据提取实战从CustomMsg到PCD/CSV4.1 写一个ROS2订阅节点解析、过滤、保存现在开始实战。我以ROS2 Python为例写一个最简单的节点订阅/livox/lidar把CustomMsg里有效点提取出来再保存成PCD文件。这样不需要额外装太多依赖打开rclpy就能跑。import rclpy from rclpy.node import Node from livox_ros_driver2.msg import CustomMsg class Livox2PCD(Node): def __init__(self): super().__init__(livox2pcd) self.sub self.create_subscription( CustomMsg, /livox/lidar, self.lidar_callback, 10) self.frame_id livox_frame def lidar_callback(self, msg): points [] for p in msg.points: # 只保留正常点tag非0按需过滤 if p.tag ! 0: continue points.append((p.x, p.y, p.z, p.reflectivity)) if not points: return self.save_pcd(points, /tmp/livox_frame.pcd) self.get_logger().info(fsave {len(points)} points) def save_pcd(self, points, filename): n len(points) with open(filename, w) as f: f.write(# .PCD v0.7 - Point Cloud Data file format\n) f.write(VERSION 0.7\n) f.write(FIELDS x y z intensity\n) f.write(SIZE 4 4 4 4\n) f.write(TYPE F F F F\n) f.write(COUNT 1 1 1 1\n) f.write(fWIDTH {n}\n) f.write(HEIGHT 1\n) f.write(VIEWPOINT 0 0 0 1 0 0 0\n) f.write(fPOINTS {n}\n) f.write(DATA ascii\n) for x, y, z, i in points: f.write(f{x:.6f} {y:.6f} {z:.6f} {i}\n) def main(argsNone): rclpy.init(argsargs) node Livox2PCD() rclpy.spin(node) rclpy.shutdown() if __name__ __main__: main()这段代码的核心就两步遍历msg.points过滤tag然后保存。如果你想把数据导出成CSV只需把PCD头部删掉按x,y,z,reflectivity逐行写就行。代码里没有做运动畸变补偿因为静态调试时影响不大等到了移动平台再把offset_time换算出逐点时间戳配合IMU做插值这是下一步的事情。4.2 把提取后的点云在CloudCompare里可视化保存成PCD后我习惯丢进CloudCompare看一遍。CloudCompare能直接打开PCD把分辨率拉高后可以看到Mid-360非重复扫描的覆盖效果。也可以用ros2 run rviz2 rviz2添加PointCloud2显示设置固定坐标系为livox_frame直接观察实时点云。我当时在CloudCompare里做得最多的一件事是检查提取后的点云是否还带着运动畸变。把雷达放在桌面静止扫描看到的墙面应该是笔直的如果墙面出现明显的弧度和拖影基本就是时间戳没处理对或者雷达本身在上电瞬间有抖动。如果你更习惯用open3d保存点云只要一行代码但字段会被映射成固定结构不如手写ASCII PCD保留反射率直观。4.3 顺带回答CloudCompare怎么把点云保存成tif格式搜索热词里有人问“CloudCompare怎么把点云保存成tif格式”我在这里插一嘴。CloudCompare本身不直接提供“另存为tif”这种点云格式选项点云要保存成tif得先做栅格化。我常用的路径是加载点云后选中图层选择Edit - Cloud - Rasterize设置栅格尺寸、插值方式得到一张深度图或高度图再在生成的栅格图层上右键Save as选TIFF格式。如果你想要的是带反射率信息的tif在Rasterize时要选对输出字段比如intensity否则导出的是纯深度图。这个操作本质上是把三维点云投影到二维格子所以栅格尺寸要按你的实际分辨率需求定别直接给0.01m不然文件巨大还一堆空洞。5. 数据提取背后的工程问题时间同步与滤波策略5.1 时间同步为什么建图会“飘”Mid-360的点云格式解析完成后下一步通常就是接SLAM。不少人在ROS2环境里做Cartographer或者Fast-LIO时会看到地图越建越飘。排除外参没标定的情况后很大概率是时间戳没同步好。原因在于CustomMsg里的timebase并不是ROS时间戳而offset_time的单位又容易搞错。如果你转成PointCloud2时只把header.stamp设成当前接收时间而忽略了每个点内部的采集时间差那么在高动态场景下运动畸变会原封不动地进入建图优化地图自然飘。正确的做法是使用官方驱动里的时间基准ROS消息的header.stamp用雷达中断时间填充逐点偏移交给offset_time。像Fast-LIO这类紧耦合算法本身就会读取offset_time完成点云补偿所以尽量让上游保持CustomMsg原始格式不要过早转PointCloud2。我在做Cartographer时也踩过类似的坑。因为机器人转场速度快边扫描边转向点云畸变明显导致回环检测一直失败。后来把点云按时间分为两组用offset_time做运动补偿后建图质量一下子正常了。5.2 滤波策略提取点云时不能只按tag过滤数据提取并不只是把字段读出来还要在保留有效信息的前提下降低噪声。我常用的策略分三步。第一步先按几何范围过滤去掉雷达自身附近的离群点。Mid-360盲区虽然小但近距离支架、安装面反射很强容易产生一堆干扰点用ROI框选有效区域直接切掉。第二步配合反射率做二次筛选。比如在地面场景中低反射率的点可能是灰尘或远处低反物体可以先统计历史帧的反射率分布找到分界阈值。不要用固定值我见过不同环境光下反射率分布差异很大固定阈值总会误伤。第三步如果噪声还是很明显用统计滤波或半径滤波。这不是逐点解析的范畴了但和提取流程经常放在一起。要点在于滤波参数设置要保守通常统计滤波的meanK取10到30标准差倍数取1.0到2.0可以先把明显的离群点去掉。5.3 用ROS2 bag保存原始数据方便离线分析最后分享一个工程习惯。我在提取点云和调SLAM的过程中会把原始CustomMsg用ros2 bag record /livox/lidar保存下来。别只存PointCloud2格式因为转过的数据已经丢了逐点时间信息后面想重新去畸变或改字段阈值还得再录一次雷达点。离线分析时用ros2 bag play重放bag再配合同一个解析节点跑数据整个流程可以反复试错。比对着在线雷达一遍遍刷屏要高效得多。实测中ros2 bag对自定义消息类型的支持很完整只要你在同一环境下先source了livox_ros_driver2的包读取bag里的CustomMsg就非常顺。6. 常见问题与排查技巧实录6.1 常见问题速查表我把这段时间实际遇到和帮助别人排查过的几个问题整理成表对照一下能省很多时间现象可能原因排查方法雷达无法被扫描到电脑网卡IP和雷达不在同一网段把主机IP改成192.168.1.x检查网线是否千兆能扫描到设备但没有点云广播码错误或设备状态异常在Livox Viewer里确认设备状态重新搜索驱动启动后话题无数据消息类型不一致或端口被占用ros2 topic list检查是否同时启动多个驱动点云坐标明显错乱坐标系没对齐或把字段读错打印前几个点对照雷达位置判断方向reflectivity全是0误把PointCloud2字段顺序当成CustomMsg直接订阅CustomMsg按uint8读取reflectivity建图地图飘时间戳没同步或IMU外参不准确先静止标定再检查offset_time处理CloudCompare无法打开PCDPCD头字段数量与实际数据不一致检查FIELDS数量和每行数据列数是否一致6.2 独家避坑心得多啰嗦几句踩过的坑。第一不要一上来就改雷达IP。我之前想当然地把雷达IP改成和公司网段一致的地址结果半天连不上后来才发现主机网卡必须和雷达在同一个物理网段尤其是直连的时候Windows和Ubuntu的防火墙经常自动拦截UDP广播。建议用一根网线物理直连再在网卡上手动配置静态IP比走路由器稳定得多。第二tag不为0不一定都是坏点。我看到Livox的tag位定义中有一些是“为后续扩展预留”在某个固件版本里你甚至会发现正常墙壁点tag也是2。所以前面建议的“先统计tag分布再决定过滤”不是废话是真能避免误删。第三如果要在ROS1和ROS2之间切换注意自定义消息类型完全不一样livox_ros_driver2的消息不能直接用于ROS1两者编译后类型不同。老项目的ROS1消息文件也别直接copy到ROS2里用字段一样但不通用跨版本回放bag时尤其明显。第四处理时间戳时千万别拍脑袋。拿到的offset_time单位不确定一定要先做一个简单实验看雷达静止时打印的一组offset_time数值如果最大值在八位数以上大概率是纳秒如果只有五位数就是微秒。标错单位后所有时间相关算法都会崩。最后说一句整套点云格式解析的核心思路并不复杂不要急着套标准格式先把自定义消息的字段定义吃透再把时间信息保留好。很多看似玄学的建图“飘”和提取噪音问题其实都能在字段解析阶段找到答案。我自己做完这轮提取后接下来最想干的是把反射率通道接入目标检测网络点云基础既然打好了后面越做越顺。
阅读完成 · 觉得有帮助?