如果你正在 Ubuntu 20.04 上折腾 Floam 的编译与运行,那么这篇笔记大概率能帮你省下两三天的时间。Floam 是 LOAM 系列激光里程计里一个非常经典的开源实现,工程结构简洁、实时性好、在 CPU 上就能跑,特别适合用来学习激光 SLAM 的特征提取、帧间匹配和局部地图维护。但麻烦就在于,原版 Floam 的依赖是针对较早的 ROS 版本设计的,拿到 Ubuntu 20.04 + ROS Noetic 上编译,Ceres、g2o、PCL、Eigen 这些库稍不注意就会把整个构建过程卡死。我前后在干净环境里编译过好几遍,也帮朋友排查过各种报错,这篇文章就把我验证过的完整流程、报错原因和最终能稳定跑通的方案一次性写清楚。
我默认你是准备在 x86 架构的普通电脑上操作,目标是把 Floam 编译出可执行节点,并用手头的点云数据包或公开的激光 bag 把建图、里程计完整跑起来。下面内容既有环境准备,也有具体编译命令,还有我亲测踩过的坑,每一步都按“为什么这么做”来讲,不光是能跑通,还要让你知道跑不动的时候去哪查。
1. Floam 的算法定位:先搞清楚它解决什么问题
1.1 三个核心节点的分工逻辑
Floam 的代码结构继承了 LOAM 的思想,把整个激光里程计拆成了三个并行节点,理解这个分工再去看编译过程和运行输出会非常顺手。
第一个节点是特征提取,对应源码里的 scanRegistration。它把每一帧原始激光点云根据局部曲率分成两类特征点:曲率大的点作为角点,曲率小的点作为平面点。这里有个关键细节——它并不是全点云找特征,而是把一帧点云分成六个子区域,在每个子区域内独立排序提取特征。这样做的目的是保证特征点在空间上分布均匀,避免角点全部集中在某个方向导致后续匹配退化成欠约束问题。
第二个节点是帧间里程计,对应 laserOdometry。它做的事情是拿当前帧的角点和平面点,去匹配上一帧的对应特征:角点匹配到直线,平面点匹配到平面,然后通过非线性最小二乘求解两帧之间的位姿变换。这一步跑得很快,但长时间运行会有累积漂移,所以需要第三个节点来兜底。
第三个节点是局部地图优化,对应 laserMapping。它不是拿当前帧去匹配上一帧,而是与一个由历史关键帧组成的局部子地图匹配,相当于用更大的范围约束来压低漂移。整个过程还维护了一个体素栅格地图,控制地图点密度,避免计算量随着运行时长无限膨胀。我建议第一次跑通之前先把这三个节点的输入输出话题搞清楚,后面看 rviz 里彩色点云对应哪个话题时就不会一脸懵。
1.2 为什么选 Ubuntu 20.04 编译 Floam 最容易出问题
如果你去查 Floam 官方仓库的 README,会发现它默认的环境是 Ubuntu 16.04 + ROS Kinetic。而 Ubuntu 20.04 标配的是 ROS Noetic,这套系统自带 PCL 1.10、Eigen 3.3.7,而系统仓库里的 Ceres Solver 版本是 1.14.0。看起来版本都不算离谱,但 Floam 的源码里用了不少老版 Ceres 的 API,比如ceres::LocalParameterization这类接口。
问题就出在这些版本差上。如果你图省事直接源码编译安装了最新的 Ceres 2.x,那编译 Floam 时会报出大量LocalParameterization not declared之类的错误,得改源码或者降级 Ceres。如果你用系统 apt 安装 1.14.0,情况就好很多,基本不用改代码。所以这篇文章的核心思路就是:优先用系统自带的库版本,别去追求新版,能少踩很多坑。
2. 编译前把依赖版本选明白:Ceres、g2o、PCL 的取舍
2.1 一条命令装完基础依赖
我建议先装好 ROS Noetic 桌面版,然后一次性把 Floam 需要的第三方库补上。在我实际测试中,下面这套命令是最省心的:
sudo apt update sudo apt install ros-noetic-desktop-full sudo apt install libpcl-dev libeigen3-dev libyaml-cpp-dev \ libgoogle-glog-dev libgflags-dev libsuitesparse-dev \ python3-catkin-tools这里多说一句,Noetic 默认仓库里的libpcl-dev会把 PCL 1.10 装进来,而 Floam 的代码在 PCL 1.10 下可以正常编译,不需要动。Eigen 用系统自带的 3.3.7 也完全没问题。真正需要谨慎处理的只有 Ceres 和 g2o,所以我单独放一小节讲清楚版本选择。
提示:不要在 Ubuntu 20.04 上直接
sudo apt install libceres-dev之后又手动源码安装 Ceres 2.x,二选一即可。混装容易把/usr/local/include和/usr/include里的头文件搞乱,编译报错时你会分不清到底是哪个版本在被引用。
2.2 Ceres 版本选择:apt 的 1.14.0 刚好能用
Floam 源码里多处使用了老版 Ceres 的LocalParameterization,Ceres 2.0 之后它被替换成了Manifold接口。直接上 2.x 版本编译 Floam,会遇到类似下面这种报错:
error: ‘ceres::LocalParameterization’ has not been declared这时候大部分人第一反应是去改源码,把类型名换成新版接口。但我实际试过,改动点比想象中多,而且改完之后还要重新调整 ceres 求解器的配置结构,属于费力不讨好。正确做法是直接让系统帮我们装:
sudo apt install libceres-dev这条命令在 Ubuntu 20.04 上装的是 1.14.0 版本,对 Floam 而言足够兼容。如果你有强迫症想用 1.13.0,从源码编译也可以,但没必要,1.14.0 实测稳定。
2.3 g2o 的安装:编译大坑集中在可视化依赖上
Floam 源码中回环优化相关模块依赖的是 g2o 库。g2o 在 Ubuntu 20.04 上没有现成的 apt 包,得从源码编译。记住两个核心参数:关掉 QGLViewer 可视化应用,关掉示例程序。原因很简单,Floam 并不需要 g2o 的 GUI 部分,而 QGLViewer 这个依赖在无桌面环境下经常搜不到,白白增加安装难度。
我验证过的编译命令:
git clone https://github.com/RainerKuemmerle/g2o.git cd g2o mkdir build && cd build cmake -DG2O_BUILD_APPS=OFF -DG2O_BUILD_EXAMPLES=OFF -DG2O_BUILD_LINKED_APPS=OFF .. make -j4 sudo make install这样安装出来的 g2o 只包含核心优化库,编译速度快,而且不会在 Floam 的 CMake 阶段报出QGLViewer找不到的问题。要注意安装完成后 g2o 的头文件默认放到/usr/local/include/g2o,库文件放到/usr/local/lib,后续 Floam 的 CMake 要是找不到,多半是/usr/local/lib没加入LD_LIBRARY_PATH。
2.4 工程目录与源码组织
依赖装齐后,创建一个 catkin 工作空间,把 Floam 源码克隆进去:
mkdir -p ~/floam_ws/src cd ~/floam_ws/src git clone https://github.com/your-local-copy/floam.git cd ~/floam_ws catkin_make如果你是第一次 clone 下来,我建议先花两分钟看一眼src目录下的文件名,确认里面至少有 scanRegistration、laserOdometry、laserMapping 这三个核心源文件。这三个文件对应前面说的三个节点,它们的编译单位独立,任何一个报错都好定位。
3. 头一次编译:我遇到的四个报错和对应解法
3.1 编译前先把内存和编译线程规划好
Floam 整体代码量不算大,但 PCL 头文件非常吃编译资源。我第一次编译的时候直接catkin_make -j8,结果编译器因为内存不足被系统杀掉了,日志最后一行是Killed,没有任何报错细节,排查起来很痛苦。后面我改用-j4,顺带把交换分区扩到了 4 GB,再也没出现过这个问题。
cd ~/floam_ws catkin_make -j4这里顺便提一句,如果编译过程中roslaunch相关模块找不到,多半是 ROS 环境没有 source:
source /opt/ros/noetic/setup.bash source ~/floam_ws/devel/setup.bash3.2 报错一:Ceres 版本引发接口缺失
这个报错是出现频率最高的。如果你在编译 laserMapping 这类源文件时看到类似‘ceres::LocalParameterization’ has not been declared,几乎可以肯定系统里被 CMake 定位到的 Ceres 是 2.x。排查方法很简单:
dpkg -l | grep libceres如果确实装的是 2.x,要么卸载后重新apt install libceres-dev,要么手动指定 Floam 的 CMake 去/usr/lib/x86_64-linux-gnu/cmake/Ceres找 1.14 的配置。后者操作繁琐,直接降级最省事。
3.3 报错二:Eigen 对齐断言导致编译失败
Eigen 库在某些代码路径下会要求开启 C++14 或更高标准,否则会跑进static assertion错误,日志里通常带着EIGEN_STATIC_ASSERT字样。Floam 源码里大量使用 Eigen 的固定尺寸向量和四元数,这些类型需要对齐,而如果 CMake 里使用的标准偏老,就会编译不过。
解决办法是把编译标准强制提上来。我一般会在 Floam 的CMakeLists.txt中加一行:
set(CMAKE_CXX_STANDARD 14)加完这行后重新catkin_make,报错基本消失。如果你不想改工程文件,也可以在catkin_make时传入参数,但改 CMakeLists 更直观,后面自己看代码时也容易反应过来。
3.4 报错三:g2o 的 QGLViewer 依赖
如果你没有按前面 2.3 节的参数编译 g2o,而是直接默认全量编译,那么在 Floam 的 CMake 阶段经常会出现Could NOT find QGLViewer之类的提示。本质原因是 g2o 默认把可视化部分也编译了,而可视化部分需要 Qt 相关的 OpenGL 封装。
我的建议是不去补装 QGLViewer,直接重编 g2o,把图形界面相关选项关掉。原因有两个:Floam 根本不需要 g2o 的画图功能;Qt5 和 OpenGL 的组合在纯命令行服务器上还会牵扯出libGL.so缺失的问题,一个坑牵出更多坑,没必要。
3.5 编译完成后的产物检查
编译成功后,devel/lib目录下应该能看到类似这样的可执行文件:
devel/lib/floam_scanRegistration devel/lib/floam_laserOdometry devel/lib/floam_laserMapping看不到任何一个是失败的。我习惯顺手用rosnode list在后续运行时确认节点有没有真正拉起,而不是只在终端里看到 roslaunch 输出就以为万事大吉。
4. 让数据包能直接跑:launch 文件与话题配置
4.1 launch 文件里最容易被忽略的两个参数
Floam 默认的启动文件会把三个节点打包成一个floam.launch,你直接用roslaunch floam floam.launch就行。但这个 launch 文件是根据作者当时使用的雷达数据写的,默认话题是/velodyne_points,如果你手里的 bag 用的不是这个名字,就必须做话题映射。
我通常会这样改 launch 文件,在 launch 内部加入:
<remap from="/velodyne_points" to="/your_pointcloud_topic"/>这里要强调另一个高频坑:播放 bag 时如果没有把仿真时间打开,节点之间时间戳会错乱,TF 树里容易出现四元数异常,报表里全是TF_DENORMALIZED_QUATERNION。解决办法是在 launch 文件里加入:
<param name="/use_sim_time" value="true"/>或者启动时手动rosparam set /use_sim_time true。不少新手困惑为什么 odom 一直在跳,其实是忘了开仿真时间。
4.2 播放 bag 的正确姿势
数据方面,手头没有激光 bag 的话,可以去下载一些公开的园区道路点云数据包,或者用自己实验室录制的室内点云包。播放时我习惯这样操作:
rosbag play --clock --pause your_data.bag--clock会把 bag 里的时间作为仿真时间发布出来,配合 launch 里的use_sim_time正好。先暂停,等三个节点都起来后,再按空格继续播放。
播放速度不建议直接-r 2快速跳过,因为激光里程计算法对帧率敏感,如果加速播放导致同一帧被重复匹配,地图容易出现异常偏移。实测下来用-r 1.0最稳妥。
4.3 如何判断算法真的跑起来了
跑起来之后,我建议打开三个终端窗口分别看话题状态:
rostopic hz /laser_odom_to_init rostopic hz /laser_cloud_map rostopic hz /feature/point_sharp正常运行时,/laser_odom_to_init的频率应该和雷达帧率接近,比如 10 Hz 的雷达包就接近 10 Hz。/laser_cloud_map更新频率不需要那么高,有输出即可。同时打开 rviz,加载 Floam 仓库里自带的 rviz 配置或者手动添加 PointCloud2 显示话题为/laser_cloud_map,再叠加一个 Odom 话题的轨迹路径显示。
如果刚启动时地图显示为空,先别急着调参,等上一两秒让第一个关键帧进入局部地图。激光 SLAM 这类算法在初始几帧的收敛需要一点点时间,很多新手以为崩了,其实只是还没反应过来。
5. 常见问题与排查速查表
我在多次实操和帮人排查中整理了下面这张速查表,几乎覆盖了 Floam 在 Ubuntu 20.04 下的典型故障。建议收藏起来,按症状对号入座。
| 症状 | 原因 | 解决办法 |
|---|---|---|
编译报LocalParameterization未声明 | 安装了 Ceres 2.x | 卸载后apt install libceres-dev降级到 1.14 |
| CMake 找不到 g2o | g2o 安装路径不在默认搜索范围内 | 在 Floam 的 CMakeLists 中指定g2o_DIR=/usr/local/lib/cmake/g2o |
| QGLViewer 找不到 | g2o 编译时没关可视化相关选项 | 用-DG2O_BUILD_APPS=OFF重编 g2o |
| 编译中途进程被杀 | 内存不足,并行编译线程太多 | 改用catkin_make -j4,扩大 swap |
| 播放 bag 时 TF 四元数异常 | 未开启仿真时间 | launch 中加入use_sim_time参数 |
话题/velodyne_points没有数据 | bag 话题名不一致 | 使用 remap 映射到实际点云话题 |
| rviz 里地图为空白 | 局部地图还没初始化,或帧率不匹配 | 等待数秒再观察,检查点云话题是否正常输出 |
| 轨迹漂移越来越严重 | 局部地图窗口过小或特征点过少 | 适当增大 mapping 的体素滤波分辨率,或者检查 bag 数据质量 |
这里额外说一个我踩过的坑:当你修改了 Floam 的源码之后,务必删除build和devel目录再重新编译。因为 CMake 有时不会自动识别源码变更,还会链接旧的编译产物,导致你改了阈值却发现运行效果没变。我每次都会执行一次干净重建:
cd ~/floam_ws rm -rf build devel catkin_make -j4虽然编译时间会长一点,但能保证你改的代码确实生效。
还有一个很多人没意识到的问题:Floam 的节点进程是单线程实时处理的,如果 CPU 频率被省电模式限制,激光里程计节点会跟不上雷达帧率,表现就是地图卡顿、轨迹断断续续。运行前可以用cpupower frequency-set -g performance把 CPU 调到性能模式,实测对帧率不稳定有明显改善。
6. 跑通之后,我建议你再改这几个参数
编译和运行折腾完,FlOAM 已经能在你的机器上正常出图出轨迹了,但这才算是真正开始接触这个算法。我记得一开始只顾着看彩色点云好不好看,后来为了理解算法才去逐行改参数对比效果,这个过程学到的东西比跑通本身多得多。
第一个值得改的是 scanRegistration 里的曲率阈值。源码里判断角点和平面点的阈值写得很随意,你把它调大,特征点数量减少,运行变快但精度下降;调小则特征点变密集,匹配更稳但计算量上升。对室内环境和小范围走廊,我建议把阈值调小一点试效果。
第二个值得改的是 laserMapping 里的体素滤波尺寸。默认值往往对室外大场景比较友好,但你在室内小场景跑时,地图点会显得很稀松。把这个尺寸从 0.5 米下调到 0.2 米,输出的地图密度会明显变高,细节特征更清楚,代价是内存占用变大。
第三个值得改的是角点子区域的数量。六个子区域是对室外 360 度雷达设计的,如果你的雷达视场角有限,比如只有前方 90 度范围,那么某些子区域里根本没有点云,特征提取会比较吃亏。把子区域数量改成与视场角匹配的数值,在实测中能减少很多初始化阶段的不稳定。
我个人的体会是,Floam 这类工程型代码,最好的学习方式就是先把它跑通,然后带着“这个参数调大或调小会怎样”的好奇心去改、去对比地图效果。当你发现只是改了一个阈值,地图质量就完全不同时,你对特征提取、匹配约束、地图维护的理解会比读十篇论文都扎实。Ubuntu 20.04 下的编译只是开始,真正的乐趣在于让这套系统在你自己的数据上稳定工作,并且知道它为什么会这样工作。