激光SLAM系统在移动场景下的定位精度,很大程度上取决于点云数据的质量。想象一下用手机拍摄行驶中的汽车——如果快门速度不够快,照片就会模糊。激光雷达在运动过程中扫描环境时也会遇到类似问题:第一个点和最后一个点的采集时刻存在时间差,而这段时间内雷达本身可能已经发生了位移。这就是所谓的"运动畸变"。
我在实际项目中使用速腾16线雷达时,曾遇到过这样的场景:当小车以2m/s速度行驶并转弯时,未去畸变的点云会导致建图出现明显的"鬼影"效果。具体表现为同一面墙在点云中会出现多个重影,定位轨迹也会产生10cm以上的漂移。而开启正确的去畸变后,墙面点云重合度提升80%,轨迹误差控制在3cm以内。
畸变产生的核心原因在于:
调试Fast-LIO时,我习惯在laserMapping.cpp中添加以下诊断代码:
cpp复制std::cout << "雷达时间差(ms): "
<< (Measures.lidar_end_time - Measures.lidar_beg_time)*1000
<< std::endl;
正常情况下,10Hz雷达应显示90-100ms的时间差。如果输出接近0,说明系统未能正确获取点云时间信息。我曾遇到过两种典型异常:
rostopic echo查看sensor_msgs/PointCloud2消息的fields字段,确认是否存在time或timestamp字段preprocess.cpp中添加调试代码,打印最后一个点的时间偏移量/cloud_registered话题,快速运动时如果点云出现"拖尾"现象,很可能去畸变未生效速腾雷达(如RS16)需要在驱动中明确启用XYZIRT格式。检查CMakeLists.txt中的配置:
cmake复制set(POINT_TYPE XYZIRT) # 必须与雷达输出格式一致
我在调试某工业AGV项目时,发现即使配置正确,时间戳仍存在问题。根本原因是雷达驱动返回的时间单位不统一——有的版本用毫秒,有的用秒。
需要修改三处关键代码:
cpp复制// 原代码(错误:多除以1000)
float point_time = pl_orig.points[i].time / 1000.0;
// 修改为
float point_time = pl_orig.points[i].time;
cpp复制// 查找所有涉及time_scale的代码
// 确保没有不必要的单位转换
cpp复制// 检查所有时间相关运算
// 确保时间单位统一为秒
验证方法:运行后观察终端输出的时间差是否稳定在0.1秒左右,同时用rosbag play测试高速运动场景下的点云质量。
某些Velodyne雷达(如VLP-16)的ROS驱动默认不提供点级时间戳。通过rostopic echo检查时,会发现点云消息只有xyz坐标字段。这时需要:
bash复制roslaunch velodyne_pointcloud VLP16_points.launch
use_sim_time:=false
min_range:=0.5
max_range:=100.0
当硬件层面无法获取时间信息时,需要在预处理阶段添加时间补偿:
cpp复制void Preprocess::velodyne_handler(...) {
// 强制关闭原始时间标记
given_offset_time = false;
// 添加线性时间补偿
for (int i=0; i<plsize; i++) {
pl_orig.points[i].time = i * time_increment;
}
}
其中time_increment的计算公式为:
code复制总扫描时间 / (点数-1)
例如16线雷达每帧约30000个点,10Hz扫描时:
code复制time_increment = 0.1 / 29999 ≈ 3.33e-6秒
最直观的方式是设计"绕圈测试":
我在某服务机器人项目中的实测数据:
建议在laserMapping.cpp中添加以下统计代码:
cpp复制Eigen::Vector3d pos_diff = state_point.pos - last_pos;
double translation_error = pos_diff.norm();
ROS_INFO("帧间位移误差: %.3f m", translation_error);
正常值范围:
在params.yaml中调整这些关键参数:
yaml复制point_filter_num: 2 # 降采样率
max_iteration: 3 # 迭代次数
filter_size_surf: 0.5 # 面元滤波大小
调试心得:
filter_size_surf(0.3-0.8)max_iteration(但不要超过5次)point_filter_num(牺牲精度换速度)症状:终端突然出现"lidar loop back"警告。解决方法:
preprocess.cpp中添加时间校验:cpp复制if (msg->header.stamp.toSec() < last_timestamp_lidar) {
ROS_WARN("时间戳异常!当前:%.6f 前次:%.6f",
msg->header.stamp.toSec(),
last_timestamp_lidar);
}
在IMU_Processing.hpp中确认时间补偿逻辑:
cpp复制// 确保IMU数据能覆盖激光扫描时段
if (imu_buf.back()->header.stamp.toSec() < lidar_end_time) {
ROS_WARN("IMU数据不足!");
}
当使用多个雷达时,需要特别处理:
cpp复制void livox_cbk(const livox_ros_driver::CustomMsg::ConstPtr &msg) {
if (msg->lidar_id != config.lidar_id) return;
// ...处理逻辑...
}
特点:线速度>3m/s,角速度>1rad/s。应对策略:
point_filter_num到1(保留更多点)IMU_Processing.hpp中提高预测频率:cpp复制void process(...) {
imu_interval = 0.002; // 原值0.005
}
特点:频繁启停、窄走廊环境。建议配置:
yaml复制filter_size_surf: 0.3
max_iteration: 4
特殊处理:
IMU_Processing.hpp中的方差参数)cpp复制state_point.vel[2] *= 0.9; // 抑制垂直方向抖动
使用rqt_plot监控时间关系:
bash复制rqt_plot /laser_time /imu_time
推荐使用pcl_viewer进行离线分析:
bash复制pcl_viewer -bc 255,255,255 -fc 0,0,255 input.pcd
在关键函数添加耗时统计:
cpp复制double t1 = omp_get_wtime();
// ...处理代码...
ROS_INFO("预处理耗时: %.3fms", (omp_get_wtime()-t1)*1000);
典型耗时参考: