
1. 为什么偏偏是PandarXT-16和LpmsIG1这套组合先交代一下背景。我手头有一台履带式移动底盘之前一直用的16线老雷达配合纯激光里程计到了室内外过渡场景就开始飘。后来换上了禾赛PandarXT-16做主激光雷达又收了阿努比LpmsIG1这个工业级IMU目标是跑通LIO-SAM把定位精度拉上去。这套组合之所以值得写是因为它比很多人熟悉的Velodyne Xsens方案要折腾一些但实际跑起来之后精度和稳定性并不差。PandarXT-16的测距能力在16线里算很能打的垂直视场也够宽而LpmsIG1作为一颗体积小巧的工业IMU输出频率高、噪声控制不错恰好能满足LIO-SAM预积分对高频惯性数据的需求。当然光看账面参数是不够的。真正让我决定写这篇东西的原因是这套组合在工程对接上有不少暗坑禾赛驱动的消息类型和LIO-SAM默认接收的PointCloud2不一致、LpmsIG1官方没有现成的ROS1驱动、四元数排序和ROS规范对不上、时间同步问题等等。这些坑我在实际调试中一个个趟过来了今天把完整过程整理出来给后面想用这套硬件跑LIO-SAM的人留一份可以直接照做的笔记。2. 硬件层面的关键参数与对接逻辑2.1 禾赛PandarXT-16的性能边界和配置要点PandarXT-16是一颗16线机械旋转激光雷达水平360°扫描垂直视场角约30°测距范围在80%反射率下能到120米典型测距精度±3cm。数据接口是百兆以太网UDP方式输出点云帧率支持5Hz、10Hz、20Hz三档可配。参数数值线束16线水平视场角360°垂直视场角约30°-15° ~ 15°测距范围0.3m ~ 120m80%反射率测距精度±3cm典型值帧率5Hz / 10Hz / 20Hz水平角分辨率约0.18°供电DC 9V ~ 36VLIO-SAM对雷达点云的建模方式是环-点二维网格它需要知道雷达有多少线N_SCAN以及一圈水平方向有多少个点Horizon_SCAN。PandarXT-16的水平角分辨率约0.18°对应一圈约2000个点。LIO-SAM默认配置是Horizon_SCAN1800如果你直接拿默认配置跑网格右侧会多出一部分空列特征提取环节虽然能跳过空列但会造成排序浪费。我建议在params.yaml里把Horizon_SCAN改成2000让网格和雷达实际输出对齐。垂直角度方面XT-16的16根线束不是均匀分布的上下边界约±15°。安装时如果雷达带俯仰角sensorMountAngle参数一定要跟着改否则地面分割会把墙面误判成地面特征提取出来的角点、面点分布会乱掉。2.2 阿努比LpmsIG1的通信与数据特性阿努比LpmsIG1是LP-RESEARCH面向工业场景的一款九轴IMU内置三轴加速度计、三轴陀螺仪和三轴磁力计支持串口UART和USB两种通信方式最高输出频率400Hz。它设备端可以做姿态解算直接输出四元数、欧拉角也可以输出原始角速度和线加速度。参数数值加速度计量程±8g可配置陀螺仪满量程450°/s可配置输出频率最高400Hz通信方式串口/USB供电5V USB或3.3V串口IG1对LIO-SAM最关键的贡献是高频惯性数据。LIO-SAM在雷达两帧点云之间用IMU做运动补偿和预积分IMU输出频率越高预积分对帧间运动的刻画越连续。实际使用中我建议把IG1配成200Hz输出这个频率下串口数据稳定CPU占用也不夸张。但需要注意一个坑IG1官方手册里四元数输出的顺序是w,x,y,z而ROS标准消息sensor_msgs/Imu里orientation字段要求是x,y,z,w。如果直接把手册里的四元数顺序填进ROS消息LIO-SAM的预积分会整体错乱轨迹呈现一种对称性扭曲。这个点后面我会在踩坑清单里展开。3. 软件链路搭建从驱动到LIO-SAM3.1 编译LIO-SAM及前置依赖我使用的环境是Ubuntu 20.04 ROS Noetic。LIO-SAM的依赖主要有GTSAM、PCL、Eigen、OpenCV。其中GTSAM建议源码编译4.0.2版本apt源里的版本太老编译LIO-SAM时会遇到接口不匹配的问题。cd ~ git clone https://github.com/borglab/gtsam.git cd gtsam git checkout 4.0.2 mkdir build cd build cmake -DGTSAM_BUILD_TESTSOFF -DGTSAM_BUILD_UNSTABLEOFF .. make -j8 sudo make install然后是LIO-SAM本体cd ~/catkin_ws/src git clone https://github.com/TixiaoShan/LIO-SAM.git cd ~/catkin_ws catkin_make -j8Noetic下编译可能会遇到usleep未声明的报错这是因为Noetic默认C标准更严格。解决办法是在include/utility.h里补上#include unistd.h重新编译即可。3.2 禾赛ROS驱动的安装与点云话题转换禾赛官方的ROS驱动仓库叫HesaiLidar_ROS它支持PandarXT系列。安装方式cd ~/catkin_ws/src git clone https://github.com/HesaiTechnology/HesaiLidar_ROS.git cd ~/catkin_ws catkin_make -j8驱动启动文件里需要配置雷达IP、电脑IP、端口等参数。下面是针对PandarXT-16的launch配置launch node namehesai_lidar pkghesai_lidar typehesai_lidar_node outputscreen param nameserver_ip value192.168.1.222/ param namelidar_ip value192.168.1.201/ param namelidar_port value2368/ param namepc_port value8321/ param nameframe_id valuehesai_link/ param namemodel valuePandarXT-16/ param namelidar_type valuePandarXT-16/ param nameptc_file value$(find hesai_lidar)/config/PandarXT-16.csv/ param namecoordinate value0/ /node /launchptc_file指向的是角度校准文件PandarXT-16每台设备出厂时有一个专属的角度文件问供应商要或者在雷达外壳标签上找编号千万别混用不同设备的文件。驱动启动后默认发布的点云话题类型是自定义的hesai_pandar/PointCloud不是LIO-SAM能直接消费的sensor_msgs/PointCloud2。这是整套方案里最容易卡住的第一步。解决办法有两个第一种新版Hesai驱动有些版本会额外发布一个标准PointCloud2话题检查launch文件里有没有类似publish_pointcloud2或pointcloud_topic的参数打开后话题名一般是/hesai/pointcloud。第二种写一个轻量转换节点。我采用的是第二种顺手在里面加了一道滤波把PandarXT-16点云里距离小于0.5m的噪声点滤掉#!/usr/bin/env python3 import rospy import sensor_msgs.point_cloud2 as pc2 from sensor_msgs.msg import PointCloud2 from hesai_pandar.msg import PointCloud pub rospy.Publisher(/points_raw, PointCloud2, queue_size10) def callback(msg): points [] for p in msg.points: if p.distance 0.5: continue points.append([p.x, p.y, p.z, p.intensity]) header msg.header header.frame_id hesai_link cloud pc2.create_cloud(header, fields, points) pub.publish(cloud) rospy.init_node(pandar_to_pc2) sub rospy.Subscriber(/hesai/pandar, PointCloud, callback) rospy.spin()这里fields需要按PointCloud2的格式定义x,y,z,intensity四个字段具体写法是from sensor_msgs.msg import PointField 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) ]转换后的/points_raw再喂给LIO-SAM这一步就通了。3.3 阿努比LpmsIG1的ROS驱动封装IG1官方没有现成的ROS1驱动需要自己写一个串口读取节点。好在它的二进制协议有公开文档帧头、帧尾固定解析起来并不复杂。sudo apt install ros-noetic-serial节点核心逻辑#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy import serial from sensor_msgs.msg import Imu def parse_lpms_frame(data): # 按Lpms二进制协议解析 # 帧头0x3A 0x02帧尾0x0D 0x0A # 解析出accel_x, accel_y, accel_z, gyr_x, gyr_y, gyr_z, quat_w, quat_x, quat_y, quat_z pass def main(): rospy.init_node(lpms_ig1_driver) pub rospy.Publisher(/imu/data, Imu, queue_size200) ser serial.Serial(/dev/ttyUSB0, 115200, timeout1) rate rospy.Rate(200) while not rospy.is_shutdown(): data ser.read(ser.in_waiting) if data: imu Imu() imu.header.stamp rospy.Time.now() imu.header.frame_id imu_link accel, gyro, quat parse_lpms_frame(data) imu.orientation.x quat[1] imu.orientation.y quat[2] imu.orientation.z quat[3] imu.orientation.w quat[0] imu.linear_acceleration.x accel[0] imu.linear_acceleration.y accel[1] imu.linear_acceleration.z accel[2] imu.angular_velocity.x gyro[0] imu.angular_velocity.y gyro[1] imu.angular_velocity.z gyro[2] imu.orientation_covariance [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001] imu.angular_velocity_covariance [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001] imu.linear_acceleration_covariance [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001] pub.publish(imu) rate.sleep() if __name__ __main__: main()有两个细节必须强调第一orientation_covariance、angular_velocity_covariance、linear_acceleration_covariance这三个协方差矩阵不能全为零。LIO-SAM在因子图优化里要用它们构造噪声模型全零会直接触发GTSAM的异常程序启动即崩溃。对角线给一个较小的正值比如0.0001是安全的。第二四元数送进ROS消息前一定确认顺序转换正确。IG1输出w,x,y,zROS要求x,y,z,w上面的代码里已经做了这个顺序调整。4. LIO-SAM参数配置针对XT-16与IG1的适配细节4.1 点云输入与扫描参数params.yaml里的核心参数如下pointCloudTopic: /points_raw imuTopic: /imu/data lidarFrame: hesai_link baselinkFrame: base_link mapFrame: map odometryFrame: odom sensor: hesai N_SCAN: 16 Horizon_SCAN: 2000 downsampleRate: 1 lidarMinRange: 1.0 lidarMaxRange: 100.0 groundScan: trueHorizon_SCAN2000这个改动对应前面说的PandarXT-16水平角分辨率0.18°。lidarMinRange设为1.0是因为XT-16在0.5m以内的近距离点云噪声偏大而且近距离点在特征提取里容易产生不稳定角点直接滤掉更干净。4.2 IMU参数与外参imu.yaml里的配置分成两类一类是噪声参数另一类是外参。# 雷达坐标系到IMU坐标系的变换 extrinsicTrans: [0.0, 0.0, 0.0] extrinsicRot: [1, 0, 0, 0, 1, 0, 0, 0, 1] # 加速度计噪声密度单位 m/s^2 / sqrt(Hz) imuAccNoise: 1.0e-3 # 陀螺仪噪声密度单位 rad/s / sqrt(Hz) imuGyrNoise: 1.0e-3 # IMU零偏随机游走 imuAccBiasN: 1.0e-4 imuGyrBiasN: 1.0e-4外参这块很多人理解反了。extrinsicRot和extrinsicTrans描述的是雷达坐标系到IMU坐标系的变换不是IMU到base_link的变换。实际操作中我先把IMU固定在雷达正上方的支架上量出IMU中心在雷达坐标系下的坐标然后用标定板和RViz做手动对齐。精度不用做到毫米级但旋转部分一定要准否则建出来的地图会在转弯时出现明显的扭曲。注意LIO-SAM并不会使用IMU消息里的orientation做初始化姿态。它启动后靠静止状态下IMU加速度计均值去估计重力方向。所以在雷达和IMU都启动之后设备必须静止至少3~5秒再开始移动否则初始重力对齐出错整个轨迹会整体倾斜。4.3 帧率与时间同步IMU频率我用200Hz雷达帧率固定在10Hz。LIO-SAM的IMU回调里会计算相邻两帧IMU的时间差dt用于预积分如果IMU频率超过500Hzdt过小会导致数值稳定性下降如果低于50Hz两帧雷达之间的运动补偿会粗糙很多。200Hz是一个比较稳妥的中间值。时间戳方面雷达和IMU节点都必须使用系统时间use_sim_time保持false。如果工控机有GPS授时或者NTP服务尽量让系统和UTC对齐这样雷达PTP授时后的时间戳和IMU时间戳天然一致LIO-SAM在imageProjection.cpp里缓存点云和IMU数据做时间对齐时不会出现时间倒退的问题。5. 启动流程与验证方法5.1 推荐启动顺序# 终端1禾赛驱动 roslaunch hesai_lidar hesai_lidar.launch # 终端2IMU节点 rosrun lpms_ig1_driver lpms_ig1_node.py # 终端3点云转换节点 rosrun pandar_to_pc2 pandar_to_pc2_node.py # 终端4LIO-SAM roslaunch lio_sam run.launch启动后先检查话题频率rostopic hz /points_raw rostopic hz /imu/data点云频率稳定在10Hz左右、IMU频率稳定在200Hz左右说明底层链路没问题。5.2 静止误差验证把设备放在地上静止60秒记录/odometry的x、y、z输出。理想情况下输出幅度应小于2cm。如果超过10cm优先检查IMU零偏和雷达到IMU的外参是否准确。我在第一次测试时静止漂移到了20cm后来定位到问题是IMU没有做零偏校正IG1在通电后需要等待一段时间让陀螺仪温漂稳定之后漂移降到2cm以内。5.3 小范围往返验证在5m x 5m区域走一个正方形回到起点后看/odometry终点与起点的差值。小于0.1m说明系统基本可用如果超过0.5m大概率是外参标定不准或者雷达和IMU时间戳没对齐。5.4 回环检测验证在大楼走廊走一圈回到原点LIO-SAM的Scan Context回环检测触发后终端会打印回环检测成功的日志。如果始终不触发先看雷达点云是否有强度信息。Scan Context依赖强度生成描述子如果强度字段全为零描述子退化成纯几何特征识别率会明显下降。PandarXT-16默认是输出强度信息的转换节点里一定要保留intensity字段。6. 我踩过的坑XT-16与LpmsIG1的组合实战问题清单6.1 坑一点云消息类型不匹配导致LIO-SAM崩溃这是第一个遇到的坑。禾赛驱动默认发布hesai_pandar/PointCloud自定义消息直接rostopic remap到LIO-SAM的/points_raw程序在反序列化阶段直接崩溃。原因很简单自定义消息的字段布局和PointCloud2不一样LIO-SAM的imageProjection.cpp把它当成标准PointCloud2解析必然出错。我的解决办法就是前面写的转换节点。如果你不想写转换节点也有取巧的办法去Hesai驱动源码里找到发布点云的函数把消息类型改成sensor_msgs::PointCloud2。但我建议不要这么干官方驱动后续升级会覆盖本地改动而且自己改驱动源码容易引入隐藏问题。转换节点虽然多一层拷贝但逻辑清晰调试方便。6.2 坑二IG1四元数顺序错误导致预积分扭曲这个问题我排查了整整一个下午。IG1手册里四元数顺序是w,x,y,zROS标准是x,y,z,w。我当时想当然地按照手册顺序填进Imu消息结果LIO-SAM的预积分出来的轨迹呈现一种对称性的扭曲转弯时尤其明显看起来像地图被拧了个麻花。判断方法很简单静止状态下把IMU的orientation转成欧拉角Roll/Pitch/Yaw应该接近(0,0,0)或(0,0,±π)。如果转出来是一组奇怪的角度优先检查四元数顺序。6.3 坑三协方差全零导致GTSAM异常LIO-SAM在因子图优化时会用IMU消息里的协方差矩阵构造噪声模型。初次写IMU驱动节点时我把三个协方差矩阵都设成了零结果程序一启动就报GTSAM的异常定位不到原因。后来查了LIO-SAM的源码才发现GTSAM里构造noiseModel::Gaussian时协方差必须正定全零矩阵直接触发断言失败。解决办法是在驱动节点里给三个协方差矩阵的对角线填一个较小的正值orientation_covariance: [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001] angular_velocity_covariance: [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001] linear_acceleration_covariance: [0.0001, 0, 0, 0, 0.0001, 0, 0, 0, 0.0001]6.4 坑四雷达安装方向与坐标轴未对齐PandarXT-16默认安装方向是线缆朝后、雷达自身x轴朝前。如果你为了走线方便把雷达转了90°安装但没有在params.yaml里改外参或者加静态坐标变换LIO-SAM拿到的点云和IMU外参完全对不上建图结果像被拧过一样。我习惯在run.launch里增加一个静态坐标变换节点确保base_link和雷达坐标系在启动时就对齐node pkgtf2_ros typestatic_transform_publisher namelidar_to_base args0 0 0.3 0 0 0 base_link hesai_link/这里0 0 0.3表示雷达安装在base_link上方0.3m处三个旋转角为0。如果雷达有偏转角度在args里填对应的roll/pitch/yaw即可。6.5 坑五启动顺序导致IMU时间戳晚于点云有一次我改变了启动顺序IMU节点比雷达节点晚启动了几秒结果LIO-SAM的IMU回调里频繁出现dt 0的警告。原因是IMU消息的时间戳晚于雷达点云帧的时间戳imageProjection.cpp在时间对齐时把点云缓存和IMU数据比较后得到负的时间差。解决方法是把雷达和IMU的时间戳统一到ROS系统时间并保证IMU节点先于LIO-SAM启动。如果雷达开了PTP授时要确保PTP主时钟和IMU节点所在工控机的系统时间一致否则会出现周期性的时间跳变。7. 跑通之后的几点体会整套系统稳定运行之后我最深的感受是硬件组合本身的潜力往往被忽略而工程对接细节决定成败。PandarXT-16和LpmsIG1在账面参数上完全够用真正拉开差距的是消息格式、时间戳、外参标定这些看不见的地方。如果你也在做类似的组合我建议优先把IMU外参标定做扎实。LIO-SAM对雷达到IMU外参的误差容忍度很低旋转偏差哪怕只有2~3°建图质量就会明显下降。标定这一步偷懒后面所有调试都是在给前面的偷懒交学费。另外一个小建议在params.yaml里把savePCD设为true这样每次跑完都能留下一份/laser_cloud_map的PCD文件方便离线复盘。我调试过程中遇到诡异问题时都是靠这些离线点云逐帧定位问题点的。这套方案目前在我自己的底盘上已经稳定运行了两个多月室内外过渡场景的定位精度相比纯激光里程计提升非常明显。希望这篇记录能帮你少走一些弯路。