ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

速腾与禾赛激光雷达点云格式转换:适配LIO-SAM与FAST-LIO2建图实战

速腾与禾赛激光雷达点云格式转换:适配LIO-SAM与FAST-LIO2建图实战 激光雷达点云格式转换这件事说大不大说小也真不小。我最早接触这个问题是在一个园区巡检机器人项目上手头一台速腾聚创的16线雷达后来项目扩展又进了禾赛的32线结果发现两家的点云数据往LIO-SAM和FAST-LIO2里一喂要么直接报错要么建出来的图飘得没法看。当时排查了两天最后定位到根因就是点云格式不匹配——速腾和禾赛在ROS消息层面的字段定义、时间戳组织方式、坐标系约定都有差异而LIO-SAM和FAST-LIO2这两个主流激光SLAM框架对输入点云的字段要求又各不相同。这篇文章就把我踩过的坑和最终跑通的方案完整梳理一遍涵盖速腾与禾赛激光雷达点云格式转换的核心逻辑、适配LIO-SAM与FAST-LIO2建图的关键配置、实操步骤以及常见问题排查。不管你是刚拿到雷达的新手还是已经在调SLAM但被格式问题卡住的开发者应该都能从中找到可以直接抄作业的内容。1. 搞懂点云格式差异为什么不能直接拿来用1.1 速腾与禾赛的点云消息结构对比速腾聚创的激光雷达在ROS驱动层面通常输出sensor_msgs/PointCloud2格式的消息字段一般包含x、y、z、intensity、ring、timestamp这几项。禾赛的雷达同样输出PointCloud2但字段命名和排列顺序有区别常见的是x、y、z、intensity、ring、time或者timestamp部分型号还会带azimuth、elevation、distance等额外字段。表面上看都是PointCloud2但字段名称、数据类型、偏移量布局不一样直接丢给SLAM框架就会出问题。我拿手头的速腾16线和禾赛32线做过对比用rostopic echo看原始消息速腾的ring字段是UINT16类型禾赛的ring是UINT8速腾的时间戳字段叫timestamp单位是秒FLOAT64禾赛的叫time或者timestamp单位可能是秒也可能是微秒取决于驱动版本和配置。这些差异看起来琐碎但SLAM框架在反序列化点云时是按字段名和偏移量去取的名字对不上或者类型不匹配轻则丢字段重则整个点云解析失败。还有一个容易被忽略的点是点云的排列方式。速腾的驱动通常按ring顺序组织点云禾赛的驱动可能按水平角度顺序排列。LIO-SAM和FAST-LIO2对点云顺序的敏感度不同FAST-LIO2相对宽容一些但LIO-SAM在特征提取阶段会依赖ring信息做线束分离顺序乱了会影响特征匹配质量。1.2 LIO-SAM与FAST-LIO2对输入点云的硬性要求LIO-SAM的核心依赖是ring和timestamp或time字段。它在imageProjection模块里会把点云按ring拆分成多条线束然后对每条线束做平滑度计算和特征提取。如果ring字段缺失或者类型不对特征提取直接失效。时间戳字段用于去畸变LIO-SAM要求每个点都有独立时间戳且时间戳要能对应到扫描起始和结束时间。如果时间戳字段名不对或者单位不对去畸变就会算错建图时表现为墙面弯曲或者地面起伏。FAST-LIO2的要求略有不同。它同样需要ring和time字段但对时间戳的精度要求更高因为FAST-LIO2用的是迭代扩展卡尔曼滤波IEKF去畸变是在滤波过程中完成的。如果时间戳精度不够或者单位搞错滤波会发散表现为建图飘移或者直接跟丢。另外FAST-LIO2对点云的intensity字段没有硬性要求但如果有的话会用于可视化。两个框架都要求点云在雷达坐标系下且外参标定要准确。如果点云格式转换过程中把坐标系搞错了比如把x和y对调建图会直接失败。我遇到过有人把禾赛的点云直接喂给LIO-SAM结果因为ring字段类型不匹配导致特征提取全为零建出来的图就是一团乱麻。1.3 格式转换的核心思路统一到SLAM框架能吃的格式格式转换的核心思路其实很简单把不同雷达的点云统一成SLAM框架期望的字段名、数据类型和排列方式。具体来说就是确保转换后的点云包含x、y、z、intensity、ring、time或timestamp这几个字段且类型和单位符合框架要求。我一般会写一个ROS节点做实时转换订阅原始点云话题在回调函数里重新组织点云结构然后发布到新话题。这样做的好处是不用改雷达驱动也不用改SLAM框架源码转换层独立换雷达只需要改转换节点的配置。另一种做法是离线转换把录好的bag包用脚本处理一遍再跑SLAM适合调试阶段。实时转换对性能有要求点云频率高的时候要注意CPU占用。转换过程中有几个关键决策点时间戳用秒还是微秒、ring用UINT8还是UINT16、点云是按ring排序还是按角度排序、是否需要保留intensity。这些决策取决于你用的SLAM框架和雷达型号下面会详细展开。2. 实操环境搭建与工具选型2.1 ROS环境与依赖安装我用的环境是Ubuntu 20.04 ROS Noetic这是目前最稳定的组合。如果你用ROS2思路一样但API和消息类型有差异需要对应调整。安装完ROS基础包之后还需要装pcl_ros、pcl_conversions、sensor_msgs这些点云处理相关的包。命令如下sudo apt install ros-noetic-pcl-ros ros-noetic-pcl-conversions ros-noetic-sensor-msgsLIO-SAM和FAST-LIO2的安装这里不展开按照官方仓库的README走就行。需要注意的是LIO-SAM依赖GTSAMFAST-LIO2依赖Eigen和PCL版本兼容性要提前确认。我遇到过GTSAM版本太新导致LIO-SAM编译失败的情况后来降到4.0.2才通过。2.2 速腾与禾赛驱动配置要点速腾的ROS驱动一般是rslidar_sdk禾赛的是hesai_lidar或者hesai_ros_driver。两个驱动都支持配置输出点云的字段和格式。速腾驱动在config.yaml里可以设置point_cloud_type一般选XYZI或者XYZIRT。如果你需要ring和timestamp必须选XYZIRT。禾赛驱动在配置里可以设置publish_pointcloud_type同样要选带ring和timestamp的类型。这里有个坑速腾的XYZIRT格式里timestamp是每个点的绝对时间戳单位是秒类型是FLOAT64。禾赛的对应字段可能叫timestamp也可能叫time单位可能是秒也可能是微秒取决于驱动版本。我建议在驱动配置里统一设置成秒和FLOAT64这样后续转换节点不用做单位换算。如果驱动不支持就在转换节点里处理。另一个坑是坐标系。速腾的雷达坐标系一般是rslidar禾赛的是hesai或者lidar。SLAM框架通常要求点云在lidar或者base_link坐标系下需要在转换节点里做坐标变换或者用tf静态变换。我一般直接在转换节点里把frame_id改成SLAM框架期望的值省得配tf。2.3 转换节点的框架选择Python还是C转换节点可以用Python写也可以用C写。Python开发快调试方便但性能差一些点云频率高的时候容易丢帧。C性能好但开发周期长。我的建议是如果雷达频率在10Hz以下点云点数在5万以内Python够用如果频率20Hz以上或者点数超过10万最好用C。我一开始用Python写速腾16线10Hz跑着没问题后来换禾赛32线20HzPython节点CPU占用直接飙到80%偶尔丢帧。后来改成CCPU降到15%左右稳定运行。所以如果你打算长期用直接上C省事。转换节点的核心逻辑是订阅原始点云话题在回调里创建一个新的PointCloud2消息按照目标格式填充字段然后发布。关键是要正确设置PointField的name、offset、datatype、count以及point_step和row_step。这些参数算错了点云就是乱的。3. 核心转换逻辑与代码实现3.1 点云字段映射与类型转换先明确目标格式。LIO-SAM期望的字段是x、y、z、intensity、ring、time其中time是FLOAT32或FLOAT64单位秒表示相对于扫描起始的时间偏移。FAST-LIO2期望的字段是x、y、z、intensity、ring、timetime是FLOAT64单位秒表示绝对时间戳或相对时间偏移具体看配置。速腾原始点云的字段通常是x、y、z、intensity、ring、timestamp。映射关系是x→xy→yz→zintensity→intensityring→ringtimestamp→time。如果速腾的ring是UINT16而目标要求UINT8需要做类型转换。如果速腾的timestamp是绝对时间而目标要求相对时间需要减去扫描起始时间。禾赛原始点云的字段可能是x、y、z、intensity、ring、time或者x、y、z、intensity、ring、timestamp。映射关系类似但要注意禾赛的time可能是微秒需要除以1e6转成秒。禾赛的ring可能是UINT8直接可用。下面是一个Python版的字段映射核心代码片段import sensor_msgs.point_cloud2 as pc2 from sensor_msgs.msg import PointCloud2, PointField import numpy as np def convert_cloud(msg): # 读取原始点云 points list(pc2.read_points(msg, field_names(x,y,z,intensity,ring,timestamp), skip_nansTrue)) # 构建新点云 new_points [] for p in points: x, y, z, intensity, ring, timestamp p # 类型转换和单位换算 ring_u8 np.uint8(ring) time_sec np.float64(timestamp) # 如果原始是微秒这里除以1e6 new_points.append([x, y, z, intensity, ring_u8, time_sec]) # 定义新字段 fields [ PointField(x, 0, PointField.FLOAT32, 1), PointField(y, 4, PointField.FLOAT32, 1), PointField(z, 8, PointField.FLOAT32, 1), PointField(intensity, 12, PointField.FLOAT32, 1), PointField(ring, 16, PointField.UINT8, 1), PointField(time, 17, PointField.FLOAT64, 1), ] # 创建新消息 new_msg pc2.create_cloud(msg.header, fields, new_points) return new_msg这段代码的关键是PointField的offset要按字段顺序累加point_step会自动计算。如果offset算错了点云解析就会错位。我建议用pc2.create_cloud而不是手动拼二进制省得出错。3.2 时间戳对齐与去畸变处理时间戳对齐是格式转换里最容易出问题的环节。LIO-SAM和FAST-LIO2都依赖时间戳做去畸变如果时间戳不对建图必飘。速腾的timestamp通常是每个点的绝对时间单位秒精度到微秒。禾赛的time可能是相对于扫描起始的时间偏移单位秒或微秒。你需要根据雷达型号和驱动配置确认。如果原始时间戳是绝对时间而SLAM框架期望相对时间你需要找到扫描起始时间然后每个点减去起始时间。扫描起始时间可以从点云消息的header.stamp获取但要注意header.stamp可能是扫描结束时间而不是起始时间。速腾的驱动一般把header.stamp设为扫描起始时间禾赛的可能设为结束时间。这个差异会导致去畸变方向反了建图时表现为运动方向上的拉伸或压缩。我一般会在转换节点里加一个参数time_is_absolute如果是绝对时间就减去header.stamp如果是相对时间就直接用。另外还要加一个time_scale参数用于微秒到秒的换算。这两个参数配对了时间戳就稳了。去畸变本身是SLAM框架做的转换节点只需要保证时间戳正确。但如果你在转换节点里做了坐标变换要注意变换后的点云时间戳不能变否则去畸变会错。3.3 ring字段处理与线束排序ring字段用于标识每个点属于哪条线束。速腾16线的ring范围是0到15禾赛32线是0到31。LIO-SAM在特征提取时会按ring分组如果ring值超出范围或者类型不对分组会失败。我遇到过禾赛的ring是UINT8但某些驱动版本会输出UINT16直接喂给LIO-SAM会导致ring值被截断高线束的点被误认为低线束。转换时要把ring统一成UINT8并确保值在有效范围内。如果雷达线束超过256UINT8不够用但一般激光雷达最多128线UINT8够用。如果原始ring是UINT16直接强转UINT8就行前提是值小于256。线束排序方面LIO-SAM不强制要求点云按ring排序但按ring排序能提高特征提取效率。FAST-LIO2对排序不敏感。我一般会在转换节点里按ring做一次稳定排序代价不大但能提升SLAM的稳定性。排序用numpy.argsort就行注意要保持每个点的其他字段跟着一起排。# 按ring排序 rings np.array([p[4] for p in new_points]) order np.argsort(rings, kindstable) new_points [new_points[i] for i in order]排序之后点云的row_step和point_step不变只是点的顺序变了不影响解析。4. 适配LIO-SAM与FAST-LIO2的配置实战4.1 LIO-SAM的params.yaml关键配置LIO-SAM的配置文件params.yaml里有几个关键参数跟点云格式直接相关。pointCloudTopic要设成转换后的话题名比如/converted_cloud。imuTopic设成IMU话题。lidarFrame设成点云的frame_id比如lidar。sensor设成lidar或velodyne这个参数影响特征提取的策略速腾和禾赛的雷达建议设成lidar。N_SCAN要设成雷达线束数速腾16线设16禾赛32线设32。Horizon_SCAN设成水平分辨率一般是1800或2000具体看雷达型号。downsampleRate设成1或2点云密的时候可以设2降采样。lidarMinRange和lidarMaxRange根据实际场景设一般设1和50。还有一个容易忽略的参数是imuAccNoise和imuGyrNoise这两个是IMU噪声参数跟点云格式无关但影响建图精度。如果建图飘除了查点云格式也要查这两个参数。LIO-SAM启动后用rostopic hz /converted_cloud确认点云频率正常用rostopic echo /converted_cloud/fields确认字段名和类型正确。如果字段不对LIO-SAM会在imageProjection阶段报错或者静默失败。4.2 FAST-LIO2的config.yaml配置要点FAST-LIO2的配置文件config.yaml里lid_topic设成转换后的话题imu_topic设成IMU话题。scan_line设成雷达线束数。time_sync_en设成false除非你做硬件时间同步。extrinsic_T和extrinsic_R是雷达到IMU的外参这个必须标定准确否则建图会飘。FAST-LIO2对时间戳的要求比LIO-SAM更严格。如果时间戳单位错了滤波会直接发散。我建议在转换节点里把时间戳统一成秒和FLOAT64然后在FAST-LIO2配置里确认time_offset参数。time_offset用于补偿雷达和IMU之间的时间差一般设0如果建图有运动畸变再调。FAST-LIO2还有一个point_filter_num参数用于降采样一般设3或4。点云密的时候设大一点点云稀的时候设小一点。这个参数不影响格式但影响建图效果。4.3 两个框架的建图效果对比与调参经验LIO-SAM和FAST-LIO2在建图效果上有差异。LIO-SAM依赖回环检测适合大场景建图但计算量大对点云质量要求高。FAST-LIO2计算量小适合实时建图但回环检测弱大场景容易累积误差。我在同一个园区场景下用速腾16线分别跑过两个框架。LIO-SAM建出来的图细节更丰富但跑一圈下来CPU占用高偶尔卡顿。FAST-LIO2建图流畅但跑久了会有轻微飘移。后来我把两个框架的点云格式统一成一样的切换框架只需要改话题名和配置文件省了很多事。调参方面LIO-SAM的edgeThreshold和surfThreshold影响特征提取数量点云稀的时候调小一点。FAST-LIO2的filter_size_surf和filter_size_map影响降采样建图飘的时候调小一点。这些参数没有万能值需要根据场景调。5. 常见问题排查与避坑指南5.1 点云字段缺失或类型不匹配的排查方法最常见的报错是点云字段缺失。用rostopic echo /converted_cloud/fields看字段列表确认x、y、z、intensity、ring、time都在。如果缺字段检查转换节点的PointField定义。如果字段在但类型不对检查datatype参数。FLOAT32对应PointField.FLOAT32FLOAT64对应PointField.FLOAT64UINT8对应PointField.UINT8。如果SLAM框架报“Failed to find match for field ring”说明字段名不对。速腾的ring可能叫ring禾赛的可能叫ring也可能叫laser_ring需要确认。如果报“Point cloud is not dense”说明点云排列方式不对需要检查height和width参数。一般雷达点云是height1width点数。还有一个隐蔽的问题是is_dense标志。如果点云里有NaN或Infis_dense要设false否则SLAM框架解析会出错。转换时用skip_nansTrue过滤掉无效点然后把is_dense设true。5.2 建图飘移与时间戳错误的关联分析建图飘移的原因很多但时间戳错误是最常见的一个。如果建图时墙面弯曲、地面起伏或者运动方向上有拉伸大概率是时间戳单位或基准错了。排查方法是录一段静止的点云看time字段的值是否在0到0.1秒之间10Hz雷达。如果值在0到100000之间说明单位是微秒需要除以1e6。如果值是绝对时间戳比如1.6e9需要减去header.stamp。另一个排查方法是看去畸变效果。在LIO-SAM里去畸变后的点云话题是/lio_sam/deskew/cloud_deskewed用rviz看这个点云如果墙面是直的说明时间戳对了如果墙面是弯的说明时间戳错了。如果时间戳对了但建图还飘检查外参标定。雷达和IMU之间的外参如果错了建图会飘。我一般用lidar_align或者手动标定确保外参准确。5.3 性能优化降低转换节点CPU占用转换节点的CPU占用主要来自点云解析和重建。Python版用pc2.read_points逐点读取效率低。优化方法是改用numpy直接解析二进制数据或者用C写。C版用pcl::fromROSMsg和pcl::toROSMsg效率高很多。另一个优化点是减少不必要的数据拷贝。转换时尽量原地修改不要反复创建新列表。如果只需要改字段名可以用PointCloud2的fields直接改不用重建整个消息。但字段类型变了就必须重建。如果雷达频率高可以考虑降频。比如20Hz的雷达转换后发布10HzSLAM框架一般能接受。降频用ros::Rate或者message_filters都行。5.4 常见问题速查表问题现象可能原因排查方法解决方案SLAM启动报字段缺失转换后点云缺ring或timerostopic echo /converted_cloud/fields检查转换节点PointField定义建图墙面弯曲时间戳单位或基准错误看time字段值范围统一成秒减去header.stamp建图飘移严重外参标定不准检查extrinsic_T和extrinsic_R重新标定雷达IMU外参点云解析失败is_dense标志错误检查点云是否有NaN过滤无效点设is_densetrueCPU占用过高Python逐点解析效率低top看节点CPU改用C或numpy解析ring值超出范围类型截断看ring字段最大值统一成UINT8确保值256去畸变方向反了header.stamp是结束时间对比扫描起始和结束时间调整时间戳基准注意转换节点发布的话题名不要跟原始话题重名否则会形成自循环导致点云无限转发。我一般用/converted_cloud或者/lidar_points。6. 进阶技巧与扩展思路6.1 多雷达融合时的格式统一策略如果你同时用速腾和禾赛或者多台同型号雷达格式统一就更重要了。我的做法是写一个通用的转换节点用配置文件指定输入话题、输出话题、字段映射规则、时间戳单位、ring偏移量。每台雷达一个配置启动多个节点实例。这样换雷达只需要改配置不用改代码。多雷达融合时还要注意ring偏移。比如两台16线雷达第二台的ring要加上16变成16到31这样SLAM框架才能区分不同雷达的线束。时间戳也要统一到同一个时钟源否则融合会错。6.2 离线bag转换与批量处理调试阶段我一般用离线bag转换。用rosbag record录原始点云然后用Python脚本读bag逐帧转换再写回新bag。这样可以在不启动雷达的情况下反复调试转换逻辑。脚本用rosbag.Bag的API读一帧转一帧写一帧效率还行。批量处理的时候注意bag的时间戳要保留否则SLAM框架的时间同步会乱。写bag的时候用原始消息的header.stamp作为bag时间戳。6.3 从格式转换延伸到点云预处理格式转换只是第一步实际项目里还需要做点云预处理比如降采样、去地面、ROI裁剪。这些可以在转换节点里一起做也可以单独做。我一般把格式转换和降采样放在一个节点里减少话题数量。降采样用pcl::VoxelGrid体素大小根据场景设一般0.1到0.5米。去地面用pcl::SACSegmentation或者简单的z轴阈值。ROI裁剪用pcl::PassThrough。这些预处理能显著提升SLAM的稳定性和速度尤其是大场景。我在实际项目里踩过最大的坑是时间戳单位搞错导致建图飘了两天才定位到。后来养成了一个习惯拿到新雷达第一件事就是用rostopic echo看原始点云的字段名、类型、单位然后写一个最小转换节点只做字段映射跑通了再加其他功能。这个习惯帮我省了很多时间。另外转换节点的配置文件一定要版本管理不同雷达不同配置混了就容易出问题。
返回列表