FAST-LIVO2 源码精读(二):环境搭建与编译避坑
本文是「FAST-LIVO2 激光-惯性-视觉里程计源码精读」专栏第二篇。上一篇确立了系统全局认知与 19 维状态向量的精确构成;从这一篇起进入实战。编译通过是阅读源码、调参、改代码的前提——在此之前任何"理解"都悬在空中。本篇逐项拆解 FAST-LIVO2 的依赖树,重点讲清三处极易踩错的版本地雷,并给出
catkin_make常见报错的速查处置表。
一、引子:第一道门槛——依赖版本不对,编译必然失败
许多研究者在初次接触 FAST-LIVO2 时遭遇的第一个困境不是算法理解,而是编译报错。这并非偶然——FAST-LIVO2 对外部依赖的版本约束比一般 ROS 包严格得多,且 README 中的说明较为精简,关键的"踩坑点"往往只在代码注释或社区 issue 中出现。
其中最突出的一条:Sophus 必须切换到a621ff这一特定提交,而非直接克隆主分支或安装系统包。Sophus 在后续版本引入了模板化接口(Sophus::SE3<double>等),与 FAST-LIVO2 源码中的用法不兼容。主流 Ubuntu 20.04 的 apt 源没有提供对应版本,仅凭sudo apt install ros-noetic-sophus也会安装到错误版本,导致编译阶段即报模板实例化错误。
第二条同样高频:rpg_vikit 必须克隆xuankuzcr的分支,而非uzh-rpg的原版仓库。FAST-LIVO1 所用的 vikit 来自另一个分支,两者接口有差异;若复用旧工作空间中的 vikit 而未更新来源,vikit_common头文件缺口会造成视觉子系统整块无法编译。
理解这两条约束的成因,需要先了解整套依赖树的结构与 CMakeLists 的编译逻辑。
二、依赖全景:九棵树、两块版本地雷
FAST-LIVO2 的依赖可按来源分为三层:ROS/catkin 生态、独立安装的 C++ 库、需手动克隆入工作空间的 catkin 包。
图 1 FAST-LIVO2 完整依赖树:橙色节点为版本有特定约束的依赖,绿色为可 apt 安装的通用依赖
2.1 第一层:ROS Noetic + catkin 生态
FAST-LIVO2 官方支持 Ubuntu 18.04–20.04,推荐 Ubuntu 20.04 + ROS Noetic。
ROS Noetic 一步安装:
sudoaptinstallros-noetic-desktop-fulldesktop-full包含rviz、rosbag、tf、image_transport、cv_bridge、pcl_ros等,覆盖绝大部分 catkin 层依赖。安装后执行source /opt/ros/noetic/setup.bash,并建议将此行加入~/.bashrc以持久生效。
echo"source /opt/ros/noetic/setup.bash">>~/.bashrcsource~/.bashrc2.2 第二层:独立 C++ 库(含版本地雷)
Eigen ≥ 3.3.4
ROS Noetic 附带的 Eigen 版本通常已满足需求,可通过以下命令核查:
pkg-config--modversioneigen3若低于 3.3.4,需从源码编译并安装较新版本。FAST-LIVO2 大量使用Eigen::Map、块操作及模板表达式,3.3 以下的 API 有细微差异,会触发编译警告乃至错误。
PCL ≥ 1.8
PCL 1.8 是 Ubuntu 20.04 apt 源的默认版本,直接安装即可:
sudoaptinstalllibpcl-devFAST-LIVO2 主要依赖 PCL 的VoxelGrid滤波器与PointXYZI/PointXYZRGB类型,无高版本特性调用,1.8 完全满足。
OpenCV ≥ 4.2
OpenCV 4.2 是 Ubuntu 20.04 默认版本(sudo apt install libopencv-dev)。ROS Noetic 的cv_bridge也已与 OpenCV 4 对齐。
需特别注意:不可使用 OpenCV 3.x。vio.cpp中对cv::NORM_HAMMING2、部分仿射变换 API 及金字塔图像结构的调用依赖 OpenCV 4 接口,使用 3.x 会在链接阶段出现undefined reference错误,且报错指向位置较为隐蔽。
若系统同时安装了多版本 OpenCV,需在 CMakeLists 中显式指定路径:
set(OpenCV_DIR "/usr/lib/x86_64-linux-gnu/cmake/opencv4")Sophus(⚠ 版本地雷 1)
这是整套依赖中版本约束最严格的一项。Sophus 仓库在a621ff提交之后逐步向模板化 API 迁移(即Sophus::SE3d改为Sophus::SE3<double>),而 FAST-LIVO2 使用的是非模板版写法:
// src/LIVMapper.cpp 中典型用法(非模板版)Sophus::SO3d rot;若安装了新版 Sophus,编译时会遭遇如下报错:
error: 'class Sophus::SO3d' has no member named '...' fatal error: sophus/so3.h: No such file or directory正确安装步骤如 README 所示,必须精确 checkout 到a621ff:
gitclone https://github.com/strasdat/Sophus.gitcdSophusgitcheckout a621ff# ← 关键:切到非模板版本mkdirbuild&&cdbuild cmake..&&makesudomakeinstall该提交对应 Sophus 1.x 的最后一批稳定非模板版提交,在 cmake 安装后会在/usr/local/include/sophus/下提供so3.h、se3.h等纯头文件接口。
Boost::thread
sudo apt install libboost-dev libboost-thread-dev即可,Ubuntu 20.04 自带 Boost 1.71,满足要求。
2.3 第三层:需克隆入工作空间的 catkin 包
rpg_vikit(⚠ 版本地雷 2)
vikit_common与vikit_ros是 FAST-LIVO2 视觉子系统的基础库,提供相机模型、双线性插值、图像导数等工具函数。关键细节:
- FAST-LIVO1使用的是
uzh-rpg/rpg_vikit主仓库; - FAST-LIVO2使用的是
xuankuzcr/rpg_vikit(维护者郑纯然的 fork),两者头文件结构与部分函数签名已有差异。
README 中的注释明确标注:
# Different from the one used in fast-livo1gitclone https://github.com/xuankuzcr/rpg_vikit.git若沿用旧工作空间中的uzh-rpg/rpg_vikit,vio.cpp的#include <vikit/...>会在编译时找不到对应头文件,报错类似:
fatal error: vikit/abstract_camera.h: No such file or directorylivox_ros_driver
Livox 官方驱动包提供livox_ros_driver::CustomMsg消息类型,preprocess.cpp在 Livox Avia 路径下直接使用该自定义消息:
// src/preprocess.cpp(Livox 分支)voidLivoxFeatureExtract(constlivox_ros_driver::CustomMsgConstPtr&msg,...)获取方式有两种:其一,按官方文档安装livox_ros_driver(较老版本为livox_ros_driver,较新版本对应livox_ros_driver2,两者消息格式不同);其二,若仅使用标准机械式雷达(Ouster、Velodyne),可将CMakeLists.txt及相关源文件中的 Livox 分支条件性地移除,但此操作超出本篇范围。
三、CMakeLists 关键行解读
了解依赖之后,再看CMakeLists.txt的核心设计,有助于理解编译过程中出现某类错误时该从哪里入手。
图 2 CMakeLists 编译结构:5 个内部静态库并行编译,最终链接为单一可执行文件
3.1 C++ 标准与编译优化
# CMakeLists.txt:6 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF)FAST-LIVO2 要求C++17,这是强制项(REQUIRED ON)。Ubuntu 20.04 默认的 GCC 9 已完整支持 C++17。若在较老系统上以 GCC 7 编译,std::optional、结构化绑定等 C++17 特性会导致编译失败;需手动升级 GCC 或指定编译器路径。
针对不同 CPU 架构,编译器标志有明确分支:
# CMakeLists.txt:21 if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|aarch64|ARM|AARCH64)") if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") # RK3588 / Jetson Orin NX 等 64 位 ARM set(CMAKE_CXX_FLAGS_RELEASE "... -O3 -mcpu=native -mtune=native -ffast-math") else() # 32 位 ARM,额外加 NEON 支持 set(CMAKE_CXX_FLAGS_RELEASE "... -O3 -mcpu=native -mtune=native -mfpu=neon -ffast-math") endif() add_definitions(-DARM_ARCH) else() # x86-64 set(CMAKE_CXX_FLAGS_RELEASE "... -O3 -march=native -mtune=native -funroll-loops") add_definitions(-DX86_ARCH) endif()-march=native/-mcpu=native意味着编译产物与编译机器的 CPU 微架构绑定,在该机器上有最佳性能,但不可跨机器直接复制二进制文件(在旧 CPU 上运行新 CPU 编译产物会触发非法指令 SIGILL)。嵌入式部署场景下需注意此点。
3.2 OpenMP 多线程并行
# CMakeLists.txt:62 find_package(OpenMP QUIET) if(OpenMP_CXX_FOUND) add_compile_options(${OpenMP_CXX_FLAGS}) endif()OpenMP 以QUIET模式查找,未找到时不报错、不中断编译,仅静默跳过。FAST-LIVO2 在残差构建环节使用#pragma omp parallel for并行遍历点云,若 OpenMP 不可用,该段回退为串行执行,性能下降但不影响正确性。Ubuntu 20.04 的libgomp随 GCC 一并安装,通常无需额外操作。
3.3 多核数自适应
# CMakeLists.txt:44 ProcessorCount(N) if(N GREATER 4) math(EXPR PROC_NUM "4") add_definitions(-DMP_EN -DMP_PROC_NUM=${PROC_NUM}) elseif(N GREATER 1) math(EXPR PROC_NUM "${N}") add_definitions(-DMP_EN -DMP_PROC_NUM=${PROC_NUM}) else() add_definitions(-DMP_PROC_NUM=1) endif()ProcessorCount(N)检测 CPU 核数,并将MP_PROC_NUM宏注入编译选项。超过 4 核时限制为 4,防止残差并行线程过多反而因调度开销降低性能。MP_EN宏控制voxel_map.cpp中#ifdef MP_EN ... #pragma omp parallel for ...代码段的开关,这一设计使单核嵌入式平台无需修改代码即可正常编译。
3.4 五个内部库的分工
# CMakeLists.txt:121 add_library(vio src/vio.cpp src/frame.cpp src/visual_point.cpp) add_library(lio src/voxel_map.cpp) add_library(pre src/preprocess.cpp) add_library(imu_proc src/IMU_Processing.cpp) add_library(laser_mapping src/LIVMapper.cpp) add_executable(fastlivo_mapping src/main.cpp)五个内部库与后续源码精读的对应关系:
内部库 源文件 精读篇章 ────────────────────────────────────────────────────── laser_mapping LIVMapper.cpp 第 8、12、14 篇(主循环与融合调度) vio vio / frame / visual_point 第 13、14 篇(视觉直接法) lio voxel_map.cpp 第 9、12 篇(体素地图与 LIO 更新) pre preprocess.cpp 第 10 篇(点云预处理) imu_proc IMU_Processing.cpp 第 11 篇(IMU 传播与去畸变)这一分拆将编译时间均摊到各模块,修改单一模块后仅重新链接受影响的库,无需全量重编,对频繁迭代调参的场景有明显效率提升。
3.5 Sophus 的特殊链接方式
# CMakeLists.txt:102 set(Sophus_LIBRARIES libSophus.so)find_package(Sophus REQUIRED)找到的是a621ff版本在/usr/local下的安装文件。注意这里手动设置了Sophus_LIBRARIES为libSophus.so——非模板版 Sophus 会编译出一个独立的共享库(新版模板版则为纯头文件,无对应.so)。若系统误装了新版 Sophus,find_package虽能找到,但链接时因缺少libSophus.so而报错:
cannot find -lSophus这是另一条 Sophus 版本错误的诊断线索。
四、完整搭建流程与报错速查
4.1 逐步安装命令
以下步骤针对全新的 Ubuntu 20.04 + ROS Noetic 环境,按顺序执行可一次通过。
步骤一:ROS Noetic 安装
sudosh-c'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > \ /etc/apt/sources.list.d/ros-latest.list'sudoapt-key adv--keyserver'hkp://keyserver.ubuntu.com:80'--recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654sudoaptupdatesudoaptinstall-yros-noetic-desktop-fullsudoaptinstall-ypython3-catkin-tools python3-rosdep ros-noetic-pcl-ros\ros-noetic-cv-bridge ros-noetic-image-transport ros-noetic-tfsource/opt/ros/noetic/setup.bashsudorosdep init&&rosdep update步骤二:系统级 C++ 依赖
sudoaptinstall-ylibboost-dev libboost-thread-dev libeigen3-dev libpcl-dev\libopencv-dev步骤三:Sophus(非模板版,精确 checkout)
gitclone https://github.com/strasdat/Sophus.gitcdSophusgitcheckout a621ffmkdirbuild&&cdbuild cmake..-DCMAKE_BUILD_TYPE=Releasemake-j$(nproc)sudomakeinstallcd../..步骤四:创建 catkin 工作空间并克隆 catkin 包
mkdir-p~/catkin_ws/srccd~/catkin_ws/src# vikit(注意仓库来源,不是 uzh-rpg)gitclone https://github.com/xuankuzcr/rpg_vikit.git# livox_ros_driver(按实际使用的 Livox SDK 版本选择)gitclone https://github.com/Livox-SDK/livox_ros_driver.git# FAST-LIVO2 本体gitclone https://github.com/hku-mars/FAST-LIVO2.git步骤五:编译
cd~/catkin_ws catkin_make-DCMAKE_BUILD_TYPE=Release -j$(nproc)sourcedevel/setup.bash编译过程中,CMake 会输出架构检测结果:
-- Current CPU architecture: x86_64 -- Using general x86 optimizations: -O3 -march=native -mtune=native -funroll-loops -- Multithreading enabled. Cores: 4 -- OpenMP found若五行均无红色错误,最终在devel/lib/fast_livo/fastlivo_mapping处生成可执行文件即为成功。
图 3 完整搭建流程:两处橙色节点为版本约束关键步骤,任一偏离均可能导致后续编译失败
4.2 常见报错速查表
以下汇总实际编译中高频出现的报错信息、根本原因与处置方式。
报错关键词 根本原因 处置方式 ───────────────────────────────────────────────────────────────────────────────── 'class Sophus::SO3d' has no member Sophus 版本错误 重新编译,务必 git checkout a621ff libSophus.so: cannot find -lSophus Sophus 为新版纯头文件版 同上 fatal: vikit/abstract_camera.h 克隆了 uzh-rpg/rpg_vikit 改用 xuankuzcr/rpg_vikit 并重新 clone vikit_common not found vikit 未在工作空间内 确认 catkin_ws/src/rpg_vikit 存在 livox_ros_driver was not found 未克隆 livox_ros_driver 克隆入 src/ 后重新 catkin_make error: 'std::optional' ...C++17 编译器或 std 标准不对 GCC≥7,cmake 传 -DCMAKE_CXX_STANDARD=17 OpenCV 3.x: undefined reference OpenCV 版本低于 4.2 升级或通过 OpenCV_DIR 指定 4.x 路径 SIGILL(运行时崩溃) -march=native 跨机器运行 在目标机重新编译,勿直接复制二进制 catkin_make: Nothing to install 首次未 source setup.bash 执行 source devel/setup.bash 后重试其中前三条为最高频,覆盖了绝大多数"首次编译失败"的场景。
五、launch 与 yaml 文件速览
编译通过后,运行 FAST-LIVO2 需要正确配置 launch 文件与 yaml 参数文件,二者是驱动系统运行的直接入口。
5.1 launch 文件结构
以 Livox Avia 对应的launch/mapping_avia.launch为例:
<!-- launch/mapping_avia.launch --><launch><argname="rviz"default="true"/><!-- 加载主配置参数 --><rosparamcommand="load"file="$(find fast_livo)/config/avia.yaml"/><!-- 启动主节点,同时加载相机内参 --><nodepkg="fast_livo"type="fastlivo_mapping"name="laserMapping"output="screen"><rosparamfile="$(find fast_livo)/config/camera_pinhole.yaml"/></node><!-- RViz 可视化(可通过 rviz:=false 关闭) --><groupif="$(arg rviz)"><nodelaunch-prefix="nice"pkg="rviz"type="rviz"name="rviz"args="-d $(find fast_livo)/rviz_cfg/fast_livo2.rviz"/></group><!-- 图像解压(将 compressed 话题转为 raw) --><nodepkg="image_transport"type="republish"name="republish"args="compressed in:=/left_camera/image raw out:=/left_camera/image"output="screen"respawn="true"/></launch>几处细节值得关注:
双层 rosparam 加载:主节点外的<rosparam command="load">将avia.yaml的所有键值对加载到/命名空间下;节点内的<rosparam file>将相机内参覆盖写入/laserMapping命名空间。后者在多相机或换配置时仅修改camera_*.yaml即可,无需改动主配置。
图像解压节点:image_transport republish将压缩图像话题实时解压为原始图像格式,原因是 FAST-LIVO2 的 ROS 图像订阅默认使用原始格式,而许多 rosbag 存储的相机数据为压缩格式以节省空间。respawn="true"确保该节点崩溃后自动重启,不影响主节点。
nice前缀:RViz 节点用launch-prefix="nice"以低调度优先级运行,避免 UI 渲染抢占算法节点的 CPU 时间,这在嵌入式或算力紧张的平台上尤为重要。
5.2 yaml 参数文件的层次结构
config/avia.yaml将所有运行参数分为六个命名空间(节选关键字段及物理含义):
# config/avia.yaml(关键字段节选)common:img_topic:"/left_camera/image"# 图像话题名,需与 bag 或驱动一致lid_topic:"/livox/lidar"# 雷达话题名imu_topic:"/livox/imu"# IMU 话题名extrin_calib:extrinsic_T:[0.04165,0.02326,-0.0284]# 雷达→IMU 平移(米)extrinsic_R:[1,0,0,0,1,0,0,0,1]# 雷达→IMU 旋转(行主序 3×3)Rcl:[...]# 相机→雷达旋转Pcl:[...]# 相机→雷达平移preprocess:lidar_type:1# 1=Livox Avia, 2=Velodyne, 3=Ousterblind:0.8# 近距盲区(米),过近点丢弃lio:voxel_size:0.5# 体素边长(米),影响地图分辨率与计算量min_eigen_value:0.0025# 平面判据阈值,越小接受越多平面(第 9 篇详述)vio:max_iterations:5# 视觉 ESIKF 迭代次数outlier_threshold:1000# 光度残差外点阈值exposure_estimate_en:true# 是否启用在线曝光估计(第 1 篇提及的第三反直觉设计)话题名与外参是接入自己传感器时首先要修改的两处。lidar_type对应preprocess.cpp中的多雷达分支,取值 1/2/3 分别触发 Livox/Velodyne/Ouster 解析路径,第 10 篇将详细展开。
5.3 编译验证:快速确认节点可正常启动
编译通过后,可不播放 rosbag,仅启动节点以验证参数文件加载无误:
source~/catkin_ws/devel/setup.bash roslaunch fast_livo mapping_avia.launch rviz:=false正常情况下节点输出:
[ INFO] [1234567890.xxx]: FAST-LIVO [ INFO] [1234567890.xxx]: Lidar type: 1 (Livox Avia) [ INFO] [1234567890.xxx]: Waiting for data...若出现Parameter not found: ...或Could not load ...类提示,通常意味着 yaml 中的话题名拼写错误或 yaml 文件路径不在find fast_livo可访问的包内,需检查catkin_ws/src/FAST-LIVO2/config/目录完整性。
六、可选性能依赖:mimalloc
CMakeLists.txt中有一项通常被忽略的可选依赖:
# CMakeLists.txt:71 find_package(mimalloc QUIET) if(mimalloc_FOUND) target_link_libraries(fastlivo_mapping mimalloc) endif()mimalloc是微软开源的高性能内存分配器,在多线程场景下比系统默认的glibc malloc有更低的分配延迟和更少的内存碎片。FAST-LIVO2 在点云处理和视觉 patch 处理环节存在大量小对象的频繁分配与释放,实测在 ARM 平台上启用 mimalloc 后单帧耗时有 5%–10% 的改善。安装方法:
sudoaptinstalllibmimalloc-dev安装后重新catkin_make,CMake 会自动检测并链接;无需修改任何源码。
七、小结与下篇预告
本篇完成了 FAST-LIVO2 编译环境的完整搭建,要点总结:
- Sophus 版本:必须
git checkout a621ff,安装非模板版;新版 Sophus 的模板化接口与源码不兼容,是第一高频报错源。 - rpg_vikit 来源:克隆
xuankuzcr/rpg_vikit,与 FAST-LIVO1 所用分支不同。 - OpenCV ≥ 4.2:3.x 版本会在链接阶段产生隐蔽的 undefined reference。
- CMakeLists 架构:5 个内部库解耦编译,架构感知的
-march=native/-mcpu=native标志在嵌入式部署时需注意不可跨机器复制二进制。 - launch / yaml 结构:话题名与外参是接入自己硬件的核心修改点;
rviz:=false参数在算力受限时可减少无关负载。
依赖装好、节点启动,标志着可以进入下一个阶段。但此时若播放官方 rosbag,屏幕上的彩色点云究竟从哪里来、各参数实际控制什么行为——这些问题将在下一篇回答。
下一篇:《FAST-LIVO2 源码精读(三):数据集跑通与 RViz 可视化》