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 一步安装:
bash
sudo apt install ros-noetic-desktop-full
desktop-full 包含 rviz、rosbag、tf、image_transport、cv_bridge、pcl_ros 等,覆盖绝大部分 catkin 层依赖。安装后执行 source /opt/ros/noetic/setup.bash,并建议将此行加入 ~/.bashrc 以持久生效。
bash
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
2.2 第二层:独立 C++ 库(含版本地雷)
Eigen ≥ 3.3.4
ROS Noetic 附带的 Eigen 版本通常已满足需求,可通过以下命令核查:
bash
pkg-config --modversion eigen3
若低于 3.3.4,需从源码编译并安装较新版本。FAST-LIVO2 大量使用 Eigen::Map、块操作及模板表达式,3.3 以下的 API 有细微差异,会触发编译警告乃至错误。
PCL ≥ 1.8
PCL 1.8 是 Ubuntu 20.04 apt 源的默认版本,直接安装即可:
bash
sudo apt install libpcl-dev
FAST-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 中显式指定路径:
cmake
set(OpenCV_DIR "/usr/lib/x86_64-linux-gnu/cmake/opencv4")
Sophus(⚠ 版本地雷 1)
这是整套依赖中版本约束最严格的一项。Sophus 仓库在 a621ff 提交之后逐步向模板化 API 迁移(即 Sophus::SE3d 改为 Sophus::SE3<double>),而 FAST-LIVO2 使用的是非模板版写法:
cpp
// 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:
bash
git clone https://github.com/strasdat/Sophus.git
cd Sophus
git checkout a621ff # ← 关键:切到非模板版本
mkdir build && cd build
cmake .. && make
sudo make install
该提交对应 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 中的注释明确标注:
bash
# Different from the one used in fast-livo1
git clone 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 directory
livox_ros_driver
Livox 官方驱动包提供 livox_ros_driver::CustomMsg 消息类型,preprocess.cpp 在 Livox Avia 路径下直接使用该自定义消息:
cpp
// src/preprocess.cpp(Livox 分支)
void LivoxFeatureExtract(const livox_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++ 标准与编译优化
cmake
# 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 架构,编译器标志有明确分支:
cmake
# 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 多线程并行
cmake
# 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 多核数自适应
cmake
# 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 五个内部库的分工
cmake
# 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)
五个内部库与后续源码精读的对应关系:
text
内部库 源文件 精读篇章
──────────────────────────────────────────────────────
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 的特殊链接方式
cmake
# 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 安装
bash
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > \
/etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
sudo apt update
sudo apt install -y ros-noetic-desktop-full
sudo apt install -y python3-catkin-tools python3-rosdep ros-noetic-pcl-ros \
ros-noetic-cv-bridge ros-noetic-image-transport ros-noetic-tf
source /opt/ros/noetic/setup.bash
sudo rosdep init && rosdep update
步骤二:系统级 C++ 依赖
bash
sudo apt install -y libboost-dev libboost-thread-dev libeigen3-dev libpcl-dev \
libopencv-dev
步骤三:Sophus(非模板版,精确 checkout)
bash
git clone https://github.com/strasdat/Sophus.git
cd Sophus
git checkout a621ff
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
sudo make install
cd ../..
步骤四:创建 catkin 工作空间并克隆 catkin 包
bash
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
# vikit(注意仓库来源,不是 uzh-rpg)
git clone https://github.com/xuankuzcr/rpg_vikit.git
# livox_ros_driver(按实际使用的 Livox SDK 版本选择)
git clone https://github.com/Livox-SDK/livox_ros_driver.git
# FAST-LIVO2 本体
git clone https://github.com/hku-mars/FAST-LIVO2.git
步骤五:编译
bash
cd ~/catkin_ws
catkin_make -DCMAKE_BUILD_TYPE=Release -j$(nproc)
source devel/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 常见报错速查表
以下汇总实际编译中高频出现的报错信息、根本原因与处置方式。
text
报错关键词 根本原因 处置方式
─────────────────────────────────────────────────────────────────────────────────
'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 为例:
xml
<!-- launch/mapping_avia.launch -->
<launch>
<arg name="rviz" default="true" />
<!-- 加载主配置参数 -->
<rosparam command="load" file="$(find fast_livo)/config/avia.yaml" />
<!-- 启动主节点,同时加载相机内参 -->
<node pkg="fast_livo" type="fastlivo_mapping" name="laserMapping" output="screen">
<rosparam file="$(find fast_livo)/config/camera_pinhole.yaml" />
</node>
<!-- RViz 可视化(可通过 rviz:=false 关闭) -->
<group if="$(arg rviz)">
<node launch-prefix="nice" pkg="rviz" type="rviz" name="rviz"
args="-d $(find fast_livo)/rviz_cfg/fast_livo2.rviz" />
</group>
<!-- 图像解压(将 compressed 话题转为 raw) -->
<node pkg="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 将所有运行参数分为六个命名空间(节选关键字段及物理含义):
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=Ouster
blind: 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,仅启动节点以验证参数文件加载无误:
bash
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 中有一项通常被忽略的可选依赖:
cmake
# 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% 的改善。安装方法:
bash
sudo apt install libmimalloc-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 可视化》