FAST-LIVO2 源码精读(二):环境搭建与编译避坑

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 包含 rvizrosbagtfimage_transportcv_bridgepcl_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.xvio.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.hse3.h 等纯头文件接口。

Boost::thread

sudo apt install libboost-dev libboost-thread-dev 即可,Ubuntu 20.04 自带 Boost 1.71,满足要求。

2.3 第三层:需克隆入工作空间的 catkin 包

rpg_vikit(⚠ 版本地雷 2)

vikit_commonvikit_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_vikitvio.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_LIBRARIESlibSophus.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 编译环境的完整搭建,要点总结:

  1. Sophus 版本 :必须 git checkout a621ff,安装非模板版;新版 Sophus 的模板化接口与源码不兼容,是第一高频报错源。
  2. rpg_vikit 来源 :克隆 xuankuzcr/rpg_vikit,与 FAST-LIVO1 所用分支不同。
  3. OpenCV ≥ 4.2:3.x 版本会在链接阶段产生隐蔽的 undefined reference。
  4. CMakeLists 架构 :5 个内部库解耦编译,架构感知的 -march=native / -mcpu=native 标志在嵌入式部署时需注意不可跨机器复制二进制。
  5. launch / yaml 结构 :话题名与外参是接入自己硬件的核心修改点;rviz:=false 参数在算力受限时可减少无关负载。

依赖装好、节点启动,标志着可以进入下一个阶段。但此时若播放官方 rosbag,屏幕上的彩色点云究竟从哪里来、各参数实际控制什么行为------这些问题将在下一篇回答。

下一篇:《FAST-LIVO2 源码精读(三):数据集跑通与 RViz 可视化》

相关推荐
插件开发1 小时前
vs2015 cuda c++ cdpSimplePrint范例,递归功能实现演示
linux·c++·算法
Tisfy1 小时前
LeetCode 2130.链表最大孪生和:转数组 / 快慢指针+链表翻转(O(1))
算法·leetcode·链表·题解
来自于狂人2 小时前
第5章 记忆管理——让Agent记住事情
人工智能·算法·语言模型·自然语言处理
CHHH_HHH2 小时前
【C++】哈希表原理与实战:从冲突解决到性能优化
开发语言·数据结构·c++·学习·算法·哈希算法·散列表
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章84-包胶有无检测
图像处理·人工智能·opencv·算法·计算机视觉
Irissgwe2 小时前
数据结构-排序
数据结构·算法·排序算法
小O的算法实验室2 小时前
2025年IEEE TITS,基于动态聚类粒子群算法的无人机任务分配与路径规划
算法
Tairitsu_H2 小时前
[LC优选算法#5] 分治:快排 | 颜色分类 | 排序数组 | 第K大元素
c++·算法·leetcode·排序算法·快速排序
青山木2 小时前
Hot 100 --- 滑动窗口最大值
java·数据结构·算法·leetcode·动态规划