当你在大型停车场或多层建筑中使用SC-LIO-SAM构建地图时,最令人沮丧的莫过于在Ctrl+C中断程序后发现地图保存不完整。这个问题困扰着许多SLAM实践者——你花费数小时采集的数据,可能因为一个不经意的操作而前功尽弃。本文将深入解析如何在SC-LIO-SAM中实现安全可靠的地图保存机制,让你完全掌控地图保存的时机和过程。
SC-LIO-SAM作为LIO-SAM的改进版本,继承了其优秀的激光-惯性里程计框架,但在保存机制上仍存在一些局限性。默认情况下,系统会在程序终止时(通常是Ctrl+C触发)自动保存点云地图和位姿图。这种设计存在几个明显缺陷:
典型的失败场景包括:
bash复制# 当你在终端按下Ctrl+C时可能看到的输出片段
Saving the posegraph...
Processing feature cloud 120 of 356 ...
^C # 用户中断
[lio_sam-2] 已终止
# 结果只有部分点云被保存
我们需要设计一个独立于程序生命周期的保存机制,核心思路是:
主要修改集中在mapOptmization.cpp文件中:
cpp复制// 在MapOptimization类中添加以下成员
private:
ros::Subscriber saveTriggerSub;
void saveMapCallback(const std_msgs::String::ConstPtr& msg);
// 在构造函数中初始化订阅者
saveTriggerSub = nh.subscribe<std_msgs::String>("/lio_sam/save_map", 10, &MapOptimization::saveMapCallback, this);
将原有保存逻辑封装为独立函数:
cpp复制void MapOptimization::saveMapCallback(const std_msgs::String::ConstPtr& msg) {
ROS_INFO("Received map save request");
// 加锁确保线程安全
std::unique_lock<std::mutex> lock(mtx);
// 保存位姿图
savePoseGraph();
// 保存点云地图
savePointCloudMap();
ROS_INFO("Map saved successfully to: %s", savePCDDirectory.c_str());
}
将保存功能拆分为两个独立部分:
位姿图保存函数:
cpp复制void MapOptimization::savePoseGraph() {
std::ofstream pgSaveStream(savePCDDirectory + "pose_graph.txt");
for (auto& _line : vertices_str)
pgSaveStream << _line << std::endl;
for (auto& _line : edges_str)
pgSaveStream << _line << std::endl;
pgSaveStream.close();
const std::string kitti_format_pg_filename = savePCDDirectory + "optimized_poses.txt";
saveOptimizedVerticesKITTIformat(isamCurrentEstimate, kitti_format_pg_filename);
}
点云地图保存函数:
cpp复制void MapOptimization::savePointCloudMap() {
pcl::PointCloud<PointType>::Ptr globalMapCloud(new pcl::PointCloud<PointType>());
// 合并所有关键帧点云
for (int i = 0; i < (int)cloudKeyPoses3D->size(); i++) {
*globalMapCloud += *transformPointCloud(cornerCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
*globalMapCloud += *transformPointCloud(surfCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
ROS_INFO_THROTTLE(1.0, "Processing frame %d/%d", i+1, (int)cloudKeyPoses3D->size());
}
// 保存完整地图
pcl::io::savePCDFileBinary(savePCDDirectory + "cloudGlobal.pcd", *globalMapCloud);
// 可选:保存各组成部分
pcl::io::savePCDFileBinary(savePCDDirectory + "trajectory.pcd", *cloudKeyPoses3D);
pcl::io::savePCDFileBinary(savePCDDirectory + "transformations.pcd", *cloudKeyPoses6D);
}
修改run.launch文件,添加保存路径参数:
xml复制<launch>
<param name="lio_sam/savePCDDirectory" value="$(find lio_sam)/map/"/>
<!-- 其他原有参数 -->
</launch>
处理大型地图时,可以考虑以下优化策略:
| 优化方法 | 实现方式 | 适用场景 |
|---|---|---|
| 增量保存 | 只保存新增关键帧 | 长时间运行系统 |
| 多线程保存 | 使用独立线程处理IO | 大型地图(>1GB) |
| 压缩存储 | 使用二进制PC格式 | 节省磁盘空间 |
增量保存示例:
cpp复制void saveIncrementalMap(int start_frame) {
pcl::PointCloud<PointType>::Ptr incrementalCloud(new pcl::PointCloud<PointType>());
for (int i = start_frame; i < cloudKeyPoses3D->size(); i++) {
*incrementalCloud += *transformPointCloud(cornerCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
*incrementalCloud += *transformPointCloud(surfCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
}
pcl::io::savePCDFileBinary(savePCDDirectory + "incremental_"+std::to_string(start_frame)+".pcd", *incrementalCloud);
}
定时保存:结合ROS的timer实现定期自动保存
bash复制rostopic pub -r 0.1 /lio_sam/save_map std_msgs/String "data: 'auto_save'"
保存前验证:添加检查点确保数据完整
cpp复制bool validateDataBeforeSave() {
return !cloudKeyPoses3D->empty() &&
(cloudKeyPoses3D->size() == cornerCloudKeyFrames.size());
}
多地图管理:支持按场景分段保存
bash复制# 保存特定区域地图
rostopic pub /lio_sam/save_map std_msgs/String "data: 'area_1'"
即使实现了自定义保存功能,仍可能遇到一些典型问题:
权限问题:确保保存目录可写
bash复制chmod -R a+w /path/to/save_directory
磁盘空间不足:添加检查逻辑
cpp复制bool checkDiskSpace(uint64_t required_bytes) {
struct statvfs stat;
if (statvfs(savePCDDirectory.c_str(), &stat) == 0) {
return stat.f_bavail * stat.f_frsize >= required_bytes;
}
return false;
}
点云错位:保存前验证位姿图一致性
cpp复制void verifyPoseGraphConsistency() {
if (cloudKeyPoses3D->size() != cloudKeyPoses6D->size()) {
ROS_ERROR("Pose graph inconsistency detected!");
// 处理策略...
}
}
在大型商场建图项目中,这套自定义保存机制成功将地图丢失率从原来的15%降到了0。关键是在每次区域扫描完成后手动触发保存,而不是依赖程序退出时的自动保存。