☰
GAC-Mapping 激光建图实战:Ubuntu 20.04 环境配置与调试避坑指南
2026/10/26 11:18:37 网站建设 项目流程

GAC-Mapping 这套东西第一次跑起来的时候,我在一台装了 Ubuntu 20.04 的旧工作站上折腾了整整两天。标题里写的是"跑通",但真正卡住人的从来不是那几行编译命令,而是环境里那些看不见的版本咬合关系——ROS 的版本、PCL 的版本、Eigen 的对齐选项、Ceres 的编译开关,任何一个不对,编译能过,跑起来直接段错误或者点云全空。这篇就把我从裸系统到能出一张像样的建图结果的全过程拆开讲,重点放在那些官方 README 里不会写、但实际一定会撞上的地方。

GAC-Mapping 属于那种"论文开源代码"类型的项目,特点是算法思路清晰、代码质量参差、依赖声明含糊。它做的是基于几何一致性的点云配准与建图,核心链路大致是:读入激光点云序列,做帧间配准,维护局部地图,最后输出全局一致的建图结果。适合谁看?如果你手上有一台装了 Ubuntu 的机器、有一点 ROS 和 C++ 基础、想复现一个激光建图的开源方案,那这篇基本能让你少走大半的弯路。下面按我实际的操作顺序来,但章节会按"为什么这么做"来组织,方便你按需跳读。

1. 先把 Ubuntu 20.04 的底座摸清楚再动手

很多人一上来就 clone 仓库、catkin_make,结果报一堆找不到头文件的错,然后开始满世界搜。我的建议是反过来的:先花二十分钟把系统底座的状态确认清楚,后面能省掉几个小时的瞎猜。

1.1 系统版本与 ROS 版本的对应关系不是随便选的

Ubuntu 20.04 对应的是 ROS Noetic,这是 ROS 1 的最后一个长期支持版本,Python 3 是默认解释器。这一点很关键,因为 GAC-Mapping 这类项目如果混用了 Python 2 时代的脚本,在 Noetic 下会直接语法报错。先确认:

lsb_release -a

输出里Release应该是20.04。然后确认 ROS 是否已装:

echo $ROS_DISTRO

正常应该输出noetic。如果为空,说明环境变量没 source,检查~/.bashrc里有没有source /opt/ros/noetic/setup.bash这一行。我见过有人装是装了,但每次开新终端都忘了 source,然后怀疑是项目的问题——这种低级错误排查起来最浪费时间。

提示:不要试图在 Ubuntu 20.04 上装 Melodic 或更老的 ROS,glibc 和 Python 版本对不上,装到一半就会崩。Noetic 是唯一正确选择。

1.2 编译器与 C++ 标准的隐性约束

GAC-Mapping 的代码里大量用了 C++14 的特性(比如泛型 lambda、std::make_unique),而 Ubuntu 20.04 自带的 GCC 9.4 默认标准是 C++14,理论上够用。但有些第三方依赖(尤其是 Ceres 的新版本)要求 C++17。我的做法是统一把项目的CMakeLists.txt里CMAKE_CXX_STANDARD显式设成 17,避免不同子模块标准不一致导致的链接期符号问题。

set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)

这里解释一下为什么"显式"很重要:catkin 的构建系统会把多个 package 的编译单元混在一起链接,如果 A 包用 C++14 编译、B 包用 C++17 编译,某些模板实例化的 ABI 可能对不上,表现就是链接时报undefined reference,而且报的符号看起来完全莫名其妙。统一标准能规避掉一整类玄学问题。

1.3 磁盘和内存的现实检查

建图类项目对内存的胃口不小。GAC-Mapping 在维护局部地图时,点云累积会吃掉大量 RAM。我实测跑一段几百米的数据,峰值内存能到 4-6 GB。如果你的机器只有 4 GB 内存又开了图形界面,跑到一半被 OOM killer 干掉是很常见的。先看一眼:

free -h df -h

df -h是看根分区剩余空间,编译过程加上 PCL 和 Ceres 的中间产物,轻松吃掉 10 GB 以上。我建议至少留 20 GB 空闲。这些数字不是吓唬人,是我第一次编译到 87% 时磁盘满了、整个 build 目录作废重来的教训。

2. 依赖安装:顺序错了就是给自己挖坑

依赖这块是重灾区。GAC-Mapping 依赖 ROS 的 PCL、Eigen、Ceres,还有可能用到 OpenCV 和 Boost。问题在于,Ubuntu 的 apt 源里这些库的版本和项目期望的版本经常对不上,而"先装哪个后装哪个"会直接影响后面能不能编过。

2.1 用 apt 装的和源码编的必须分清

我的原则是:能用 apt 装的绝不源码编,除非版本明确不满足。原因很简单,apt 装的库有统一的安装路径、有 pkg-config 文件、和 ROS 的构建系统配合良好;源码编的库如果make install到/usr/local,很容易和 apt 版本冲突,出现"头文件用的是新版、链接的是旧版"这种最难查的问题。

先装基础依赖:

sudo apt update sudo apt install -y build-essential cmake git sudo apt install -y libeigen3-dev libboost-all-dev sudo apt install -y libpcl-dev pcl-tools sudo apt install -y libopencv-dev

这里libeigen3-dev在 20.04 上是 3.3.7 版本,libpcl-dev是 1.10。这两个版本对 GAC-Mapping 来说基本够用。装完可以用下面的命令确认版本:

dpkg -l | grep -E "eigen|pcl|opencv"

2.2 Ceres 的版本选择是个分水岭

Ceres 是最容易出问题的一环。apt 源里的libceres-dev在 20.04 上是 1.14 版本,而不少开源建图项目用的是 2.x 的 API。1.14 和 2.x 之间有一个破坏性变更:LocalParameterization在 2.x 里被Manifold取代。如果你的项目代码里用的是旧接口,装 2.x 会编译失败;反之用新接口装 1.14 也会失败。

我的处理方式是先看项目CMakeLists.txt里find_package(Ceres)后面有没有版本要求,再决定。如果没有明确要求,先试 apt 的 1.14:

sudo apt install -y libceres-dev

如果编译时报Manifold相关错误,说明代码要 2.x,那就得源码编。源码编的时候有个关键开关:

git clone https://ceres-solver.googlesource.com/ceres-solver cd ceres-solver mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING=OFF \ -DBUILD_EXAMPLES=OFF \ -DUSE_CUDA=OFF .. make -j4 sudo make install

-DBUILD_TESTING=OFF和-DBUILD_EXAMPLES=OFF能省掉大量编译时间,-DUSE_CUDA=OFF是因为大多数人的机器没配 CUDA 环境,开着反而会报错。装到/usr/local之后,记得让 CMake 优先找到它,可以在项目 CMakeLists 里加:

set(Ceres_DIR "/usr/local/lib/cmake/Ceres")

注意:源码装完 Ceres 后,如果之前 apt 装过 1.14,两个版本会共存。链接时到底用哪个取决于LD_LIBRARY_PATH和 CMake 的查找顺序,这种不确定性是 bug 的温床。我的做法是源码编之前先sudo apt remove libceres-dev,保证系统里只有一个版本。

2.3 PCL 的隐藏依赖和 VTK 的坑

libpcl-dev装的时候会连带装 VTK,而 PCL 的可视化模块依赖 VTK 的渲染后端。在无图形界面的服务器上,VTK 初始化会失败,导致程序在pcl::visualization相关调用处崩溃。如果你只是跑建图、不需要实时可视化,最稳妥的办法是在代码里把可视化部分注释掉,或者编译时加宏关掉。

另外 PCL 1.10 对 Eigen 的对齐有要求,涉及Eigen::Vector4f这类固定大小向量作为类成员时,必须加EIGEN_MAKE_ALIGNED_OPERATOR_NEW宏,否则在 32 位对齐的指令下会段错误。这个坑在 PCL 官方文档里有提,但很多人不看,跑起来随机崩溃还以为是算法问题。

3. 编译 GAC-Mapping:从 catkin 工作空间到第一个可执行文件

依赖齐了之后进入正题。GAC-Mapping 是 ROS 包结构,标准做法是放进 catkin 工作空间的src下编译。

3.1 工作空间初始化的标准动作

mkdir -p ~/gac_ws/src cd ~/gac_ws/src # 把 GAC-Mapping 源码放进来,假设目录名是 GAC-Mapping cd ~/gac_ws catkin_make -DCMAKE_BUILD_TYPE=Release

-DCMAKE_BUILD_TYPE=Release这个参数千万别省。建图算法里有大量 Eigen 矩阵运算,Debug 模式下慢一个数量级,跑一段数据能等到你怀疑人生。我第一次忘了加,跑 200 帧花了快四十分钟,加上之后三分钟出结果。

编译过程中最常见的三类错误,我按出现频率排一下:

错误类型典型报错根因处理
找不到头文件fatal error: xxx.h: No such file依赖没装或路径没导出确认 apt 包装了,检查 CMakeLists 的 include 路径
符号未定义undefined reference to ...C++ 标准不一致或库版本冲突统一 CXX_STANDARD,清理 build 重编
模板实例化失败一长串模板报错Eigen/Ceres 版本 API 不匹配按 2.2 节调整 Ceres 版本

3.2 编译通过不等于能跑:先做冒烟测试

catkin_make显示 100% 之后,别急着上真实数据。先确认可执行文件生成了:

ls ~/gac_ws/devel/lib/

找到对应的节点名(比如gac_mapping_node之类)。然后 source 环境:

source ~/gac_ws/devel/setup.bash

这一步很多人漏掉,直接rosrun会提示找不到包。source 之后可以用rospack find确认包能被找到。

冒烟测试我一般用一个极小的数据集,或者干脆用项目自带的示例数据。目的是确认程序能启动、能读到配置、能初始化,而不是一上来就跑完整序列。如果启动就崩,问题一定在环境或配置,跟算法无关,这时候排查范围小得多。

3.3 launch 文件里的参数不是摆设

GAC-Mapping 通常带一个 launch 文件,里面有一堆参数:点云话题名、体素滤波的 leaf size、配准的迭代次数、地图分辨率等等。这些参数直接决定跑不跑得起来。最常见的两个坑:

第一个是话题名对不上。你的激光数据发布在/velodyne_points,launch 里订阅的是/points_raw,那程序就一直在等数据,看起来像"卡住了",其实是没收到输入。用rostopic list和rostopic hz确认数据在发、频率正常。

第二个是体素滤波的 leaf size 设得太大。比如设成 1.0 米,点云被抽稀得只剩骨架,配准直接失败。一般激光雷达数据用 0.1 到 0.3 米比较合理,具体看你的场景尺度。这个参数没有标准答案,得根据数据调,我后面会讲怎么调。

4. 跑真实数据:从"能启动"到"建图结果像样"之间隔着什么

编译通过、冒烟测试过了,接下来才是真正花时间的部分——让建图结果达到可用质量。这一段我按问题现象来组织,因为实际调试就是"看到什么现象、往哪个方向查"的过程。

4.1 点云全空或者只有一帧:先查坐标系

第一次跑真实数据,rviz 里啥都没有,或者只有零星几个点。这种情况九成是坐标系问题。激光雷达发布的点云带一个frame_id,比如velodyne,而建图节点期望的坐标系可能是map或odom。如果 TF 树里没有从frame_id到期望坐标系的变换,点云变换会失败,结果就是空的。

排查步骤:

rosrun tf view_frames

生成一个 pdf 看 TF 树结构。确认你的点云frame_id在树里,并且能连到map。如果缺变换,要么在 launch 里加一个静态 TF 发布,要么改建图节点的配置让它接受原始frame_id。

提示:静态 TF 用static_transform_publisher发布,格式是x y z yaw pitch roll parent child,注意单位是米和弧度,别把角度当弧度填。

4.2 建图漂移:配准参数和场景特性的匹配

点云有了,但建出来的图明显漂移,墙是歪的、回环对不上。这是建图项目的经典问题,原因可能有好几层,得逐层剥。

第一层看配准的初值。GAC-Mapping 这类方法通常需要帧间有一个不错的初始位姿估计,如果运动太快、帧间重叠太少,配准会失败。解决办法是降低数据播放速度(rosbag play -r 0.5),或者提高激光雷达的帧率。我实测把播放速率降到 0.5 倍,漂移明显改善,说明算法本身对帧间运动是敏感的。

第二层看体素滤波和法向量估计的参数。GAC-Mapping 的"几何一致性"依赖法向量或局部几何特征,如果点云太稀疏,法向量估计不准,一致性约束就失效。把 leaf size 调小(比如从 0.3 降到 0.15),点云密度上来,法向量更稳,但计算量也上去了。这是个权衡,得根据你的机器性能找平衡点。

第三层看回环检测的阈值。如果场景里有明显的回环但没被检测到,全局优化就不会触发,漂移累积。检查配置里回环相关的距离阈值和相似度阈值,适当放宽。但放宽太多会引入错误回环,把图拉得更歪,所以要一点点试。

4.3 内存持续增长直到崩溃:地图管理的边界

跑长序列时,程序内存一路涨,最后被系统杀掉。这是局部地图没有做裁剪导致的。GAC-Mapping 维护局部地图时,如果只加不删,点云越积越多。检查代码里有没有基于距离或时间的滑窗裁剪逻辑,配置里通常有map_size或local_map_radius之类的参数,把它设成一个合理值(比如 50 米),超出范围的老点云要剔除。

如果代码里压根没有裁剪逻辑,那就得自己加。最简单的做法是在每次更新地图后,遍历一遍点云,把距离当前位置超过阈值的点删掉。用 PCL 的PassThrough或者手写一个半径过滤都行。这段代码不难,但得有,否则长序列必崩。

5. 那些让我多花了一天的细节问题

前面讲的是主线,这一段专门收那些零碎但致命的坑。它们不一定每个人都会遇到,但遇到了就是几个小时起步。

5.1 Eigen 对齐导致的随机段错误

前面提过一嘴,这里展开说。Eigen 为了 SIMD 加速,对固定大小的向量化类型(Vector4f、Matrix4f等)要求 16 字节对齐。如果一个类里有这种成员,而这个类是用new分配的,编译器不一定保证对齐,运行时就会在访问该成员时崩。表现是"有时候崩有时候不崩",极其难查。

解决办法是在类定义里加:

class MyClass { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW Eigen::Vector4f vec; // ... };

或者在 CMake 里全局关掉向量化:-DEIGEN_DONT_VECTORIZE。后者性能有损失,但能一劳永逸。我一般先加宏,实在查不出来再关向量化。

5.2 rosbag 的时间戳和 TF 的时间同步

用 rosbag 回放数据时,如果 bag 里的 TF 和点云时间戳对不齐,tf查询会报Lookup would require extrapolation into the future。这是因为回放时系统时间和 bag 时间不一致。解决办法是加--clock参数,并设置use_sim_time:

rosparam set use_sim_time true rosbag play --clock your_data.bag

use_sim_time让 ROS 节点用 bag 里的时间而不是系统时间,TF 查询就能对上。这个坑我第一次遇到时,报错信息看得一头雾水,查了半天才发现是时间源的问题。

5.3 编译缓存导致的"改了没生效"

catkin 的增量编译有时候会抽风,改了代码但可执行文件没更新,或者改了 CMakeLists 但配置没重新生成。表现是"我明明改了参数,怎么结果一样"。遇到这种情况,别犹豫,直接清:

cd ~/gac_ws rm -rf build devel catkin_make -DCMAKE_BUILD_TYPE=Release

全量重编虽然慢,但能排除掉一整类"改了不生效"的假象。我现在的习惯是,只要改了 CMakeLists 或者动了依赖,就全量重编一次,省得后面怀疑人生。

5.4 可视化拖慢主循环

rviz 里订阅了点云话题做实时显示,数据量大时 rviz 本身会吃掉大量 CPU,导致建图主循环被拖慢,帧处理不过来,队列积压。表现是"跑着跑着越来越慢"。解决办法是关掉 rviz 的实时显示,或者降低显示的点云降采样率。调试阶段可以开,正式跑数据时关掉,用 rosbag record 录下来事后看。

6. 调参的实操思路:别瞎试,按影响面排序

参数多的时候最容易犯的错是"每个都调一点",结果变量太多根本不知道是哪个起了作用。我的做法是按影响面从大到小排,一次只动一个。

6.1 先定分辨率,再定配准,最后调回环

影响面最大的是体素滤波的 leaf size,它决定了整个管线的数据密度,后面所有参数都建立在它之上。先把 leaf size 定下来(根据场景尺度和机器性能,0.1 到 0.3 之间选),然后调配准的迭代次数和收敛阈值,最后才是回环检测的阈值。

配准迭代次数一般 30 到 50 次够用,太少不收敛,太多浪费时间且可能过拟合噪声。收敛阈值看你的点云精度,厘米级精度的话设 1e-4 到 1e-6 比较合理。

回环阈值是最玄学的,因为它依赖场景。我的经验是先关掉回环跑一遍,看纯里程计的漂移有多大,再打开回环看能修正多少。如果打开回环后图反而更歪,说明阈值太松,检测到了错误回环,收紧。

6.2 用定量指标而不是"看着差不多"

调参不能只靠肉眼。至少记录两个指标:一是相邻帧配准的 fitness score(配准残差),二是回环前后的轨迹闭合误差。前者反映帧间配准质量,后者反映全局一致性。把这两个数记下来,调参才有依据。我一般写个小脚本从日志里 grep 出来,画个曲线,比盯着 rviz 靠谱得多。

7. 我个人在实际操作中的几点体会

跑通 GAC-Mapping 这件事,技术门槛其实没有想象中高,真正消耗时间的是环境的不确定性和问题的定位效率。我最大的体会是:先把底座和环境确认死,再动代码和参数。很多人一上来就怀疑算法,其实九成的问题出在版本、路径、时间戳这些"非算法"的地方。

另外一个实用的习惯是,每解决一个问题就把现象和根因记下来,形成自己的排查清单。下次遇到类似现象,先对照清单过一遍,能省掉大量重复劳动。我现在的清单里,"点云全空先查 TF""随机崩溃先查 Eigen 对齐""改了不生效先全量重编"这几条,几乎每次都能命中。

最后说一个容易被忽略的点:跑通之后,把整个环境(依赖版本、编译参数、launch 配置)完整记录一份。开源项目的依赖是会变的,今天能编过的组合,半年后换个机器可能就编不过了。留一份可复现的记录,比什么都值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询