1. 项目概述与方案设计
1.1 为什么要专门写一篇Ceres安装教程
在Ubuntu 20.04上部署ORB-SLAM3、VINS-Fusion、COLMAP这类视觉SLAM或三维重建项目时,Ceres Solver基本是一个绕不开的依赖。很多同学第一次遇到它,是因为编译ORB-SLAM3时终端突然报出一堆找不到ceres/ceres.h的错误,然后开始漫无目的地搜教程。但实际上Ceres并不是一个“装上就能跑”的库,它牵涉到Eigen版本、SuiteSparse、glog、gflags等一系列底层依赖,而Ubuntu 20.04自带的软件源版本又比较老,直接apt install libceres-dev装出来的版本经常和SLAM框架的接口对不上,最后不得不手动卸载重来。
我最初接触Ceres时也走过不少弯路,最头疼的是项目明明编译过了,运行时却报ceres::Problem相关符号找不到,或者版本不匹配导致的模板编译错误。这类问题追根究底,大多是安装方式不对、编译选项和项目不一致导致的。这篇内容就是把我自己在Ubuntu 20.04上反复实验、确认可行的完整流程整理出来,覆盖依赖安装、源码编译、CMake集成、常见报错排查这几个环节,目标是让你照着做一遍就能把Ceres稳定用起来。
1.2 源码编译与apt安装怎么选
关于Ceres的安装方式,网上教程里无非是两条路:一条是sudo apt install libceres-dev,另一条是源码编译。我强烈建议你用源码编译,理由很现实:
第一,Ubuntu 20.04软件源里的Ceres版本停留在1.14.0,这个版本太老了。ORB-SLAM3、VINS-Fusion等现代项目通常要求Ceres 2.0以上,老版本接口差异明显,编译时会出现各种奇怪的模板报错。第二,apt装出来的Ceres依赖的是系统自带的Eigen 3.3.7,这个Eigen版本也存在不少历史问题,尤其在做BA优化时表现不佳,很多SLAM项目会要求Eigen 3.4.0。
源码编译看似步骤多,但每一步都可控,版本、编译选项、安装路径全在自己手里。而且Ceres的源码编译流程本身很成熟,官方CMake脚本写得清楚,真正需要手动处理的坑并不多。依赖装好之后,整个编译过程通常五到十分钟就能完成,性价比很高。
1.3 版本选择与整体思路
Ceres Solver目前主流的稳定版本是2.1.0和2.2.0。2.1.0发布时间较早,被广泛测试,和大多数SLAM项目兼容性很好。2.2.0版本更新,修复了一些偶发问题,但部分老项目还没有验证过兼容性。
我个人推荐安装2.1.0,这是一个“稳”字当头的选择。如果你后续要编译的项目明确要求2.2.0,再切换也不迟。版本选择确定后,整体思路按三步走:
- 安装Ceres编译需要的系统依赖库;
- 下载Ceres源码,配置CMake编译选项并编译安装;
- 编写测试程序验证Ceres是否可用,并确认项目CMakeLists.txt的正确写法。
下面从依赖准备开始逐步展开。
2. 编译前依赖准备
2.1 核心依赖库逐个说明
Ceres不是一座孤岛,它需要一系列底层数学和线性代数库来支撑。在Ubuntu 20.04上,我们需要先安装以下几类库。下面这个命令是我验证过可用的:
sudo apt update sudo apt install -y cmake libgoogle-glog-dev libgflags-dev libatlas-base-dev libeigen3-dev libsuitesparse-dev这里每个库都有自己的角色,我来逐个说一下,方便你理解为什么少了它们会出问题。
Eigen3是Ceres最核心的依赖之一。Ceres内部大量的矩阵运算、求导计算都基于Eigen完成。Ubuntu 20.04默认的Eigen版本是3.3.7,这个版本可以满足Ceres 2.1.0的编译要求,不过如果你要跑ORB-SLAM3,我建议顺手升级到3.4.0,因为很多SLAM项目默认按3.4.0来写的,低版本Eigen会带来编译期的模板报错。
libgoogle-glog-dev和libgflags-dev是一对好搭档。glog是Google开源的日志库,Ceres用它输出优化过程中的调试信息、错误提示;gflags则是命令行参数解析库,Ceres的部分测试程序会用到。如果你不装这两个库,CMake配置时会提示找不到,虽然可以通过-DMINIGLOG=ON选项让Ceres启用内置的迷你日志库,但这样做会丢失很多调试信息,排查问题时会很痛苦,所以还是建议完整安装。
libatlas-base-dev和libsuitesparse-dev用于提供BLAS/LAPACK和稀疏矩阵求解能力。Ceres在求解大规模非线性最小二乘问题时,需要高效的线性代数求解器。SuiteSparse是其中最常用的后端,包括了SPQR、CHOLMOD等求解器模块,缺失会导致Ceres无法使用稀疏矩阵加速,大场景BA优化性能明显下降。
另外还有一个容易忽略的依赖:liblapack-dev。虽然atlas-base包会顺带处理一部分,但部分版本的Ceres在CMake检测LAPACK时会有遗漏,建议一起装上:
sudo apt install -y liblapack-dev2.2 升级Eigen版本的正确姿势
既然前面提到Eigen 3.4.0的好处,这里就顺便说一下升级方法。但请注意,Eigen的升级不推荐用apt源直接替换,因为Ubuntu 20.04的软件源里最高只有3.3.7,而且系统很多组件都依赖这个版本的Eigen,盲目替换可能导致ROS等环境出问题。
安全的做法是单独下载源码,安装到独立的路径,然后在你的项目CMakeLists.txt里,通过指定EIGEN3_INCLUDE_DIR来优先使用新版本。具体操作如下:
wget https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz tar -xzf eigen-3.4.0.tar.gz cd eigen-3.4.0 mkdir build && cd build cmake .. sudo make install默认安装路径是/usr/local/include/eigen3,不会覆盖系统的/usr/include/eigen3。之后在项目里使用新版本Eigen时,在CMakeLists.txt中这样写:
set(EIGEN3_INCLUDE_DIR "/usr/local/include/eigen3") include_directories(${EIGEN3_INCLUDE_DIR})这样就能在不影响系统环境的情况下,让SLAM项目用到新版本Eigen。有一个细节需要提醒:Ceres源码编译时也会检测Eigen版本,如果你先升级了Eigen,Ceres会自动用最新版本;如果先编译Ceres后升级Eigen,理论上Ceres是静态链接Eigen的,影响不大,但还是建议先把Eigen处理好,再编译Ceres。
3. 源码编译与安装实操
3.1 下载源码与准备工作
依赖库就绪后,开始下载Ceres源码。建议直接克隆官方仓库,并切换到2.1.0这个稳定tag:
git clone https://github.com/ceres-solver/ceres-solver.git cd ceres-solver git checkout 2.1.0这里有一个经验之谈:不要直接使用master分支。master分支是开发版本,代码更新频繁,虽然功能更全,但偶尔会有实验性改动,可能在编译时遇到未知问题。切换到正式release tag,至少保证这个版本是被社区广泛验证过的,出现问题也更容易搜到解决方案。
下载完成后,建一个build目录进行编译。Ceres官方不推荐在源码根目录下直接编译,因为会产生大量中间文件污染源码,后续想切换版本或者clean都会麻烦。
mkdir build cd build3.2 CMake配置参数详解
编译前需要配置CMake选项。以下这组参数是我在多个项目里验证过,覆盖了大多数使用场景:
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF这里每个参数都有讲究,我展开聊聊。
-DCMAKE_BUILD_TYPE=Release设置编译模式为发布版。Release模式会开启编译优化,Ceres在求解非线性优化时性能提升明显。很多教程不写这一项,默认是空值(即无优化),SLAM项目跑起来明显卡顿。实测中,Release模式比Debug模式下BA求解速度快两到三倍,这个差距在大型场景中会被放大,所以这一项务必加上。
-DBUILD_EXAMPLES=OFF表示不编译官方示例程序。Ceres自带的示例代码质量很高,但对日常部署来说不是必需,取消可以节省不少编译时间。-DBUILD_TESTING=OFF同理,跳过测试用例的编译。
如果想自定义安装路径,可以加上-DCMAKE_INSTALL_PREFIX=/your/desired/path,默认是/usr/local。对绝大多数场景,用默认路径就行,后续项目链接时不用额外指定路径,省心。
如果遇到glog相关报错,还可以考虑加上-DCMAKE_PREFIX_PATH=/usr/local来帮助CMake定位库文件。这一项不是必需的,但如果你之前手动编译过glog到/usr/local下,加上它会更稳妥。
配置完成后,开始编译:
make -j$(nproc)$(nproc)会自动获取CPU核心数,用满并行编译。如果机器内存较小(比如8GB以下),建议用make -j4,避免编译过程中内存溢出。实际测试中,Ceres全量编译在8核机器上大约需要三到六分钟,视CPU性能而定,耐心等就好。
编译完成后安装:
sudo make install sudo ldconfigldconfig这一步很容易被忽略,它的作用是刷新动态链接库缓存,确保系统能找到新安装的.so文件。不执行的话,后续编译项目时也许没问题,但运行时可能报libceres.so.2: cannot open shared object file的错误。
3.3 验证安装是否成功
安装完成后,用一个小例子验证Ceres是否能正常使用。先检查库文件是否安装到位:
ls /usr/local/lib/libceres* ls /usr/local/include/ceres/ceres.h正常情况下能看到libceres.a、libceres.so(或libceres.so.2)等文件和头文件目录。
自定义安装路径后,验证命令需要改为ls /your/path/lib/和ls /your/path/include/ceres/。
然后写一个最简单的测试程序,验证Ceres的核心功能——求解一个最小二乘问题。这里我们构建一个简单的一元二次函数拟合问题,检验Ceres能否正确求解:
#include <ceres/ceres.h> #include <iostream> struct CostFunctor { template <typename T> bool operator()(const T* const x, T* residual) const { residual[0] = T(10.0) - *x; return true; } }; int main(int argc, char** argv) { double x = 0.5; ceres::Problem problem; ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor); problem.AddResidualBlock(cost_function, nullptr, &x); ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.minimizer_progress_to_stdout = true; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); std::cout << summary.BriefReport() << std::endl; std::cout << "x : " << x << std::endl; return 0; }这段代码的含义很直白:我们希望找到一个x,使得10 - x的残差最小。显然最优解是x=10。将代码保存为test_ceres.cpp,接着编译:
g++ test_ceres.cpp -o test_ceres -I/usr/local/include -L/usr/local/lib -lceres -lglog如果Ceres正确安装,运行时输出类似:
Ceres Solver Report: Iterations: 8, Initial cost: 9.025000e+01, Final cost: 0.000000e+00, Termination: CONVERGENCE x : 10能够看到cost降为0,x收敛到10,说明Ceres从编译到链接再到动态库加载都完全正常。
3.4 在SLAM项目中的CMake配置写法
Ceres安装好之后,需要把它集成到自己的项目中。最规范的写法是在CMakeLists.txt中使用find_package:
find_package(Ceres REQUIRED) include_directories(${CERES_INCLUDE_DIRS}) target_link_libraries(your_project ${CERES_LIBRARIES})这样CMake会自动找到Ceres的头文件路径和库文件,并将其链接到你的目标程序。需要注意,如果你的Ceres是自定义安装路径,还需要设置环境变量或CMake变量:
export Ceres_DIR=/your/path/lib/cmake/Ceres或者在CMakeLists.txt中:
set(Ceres_DIR "/your/path/lib/cmake/Ceres")非要自定义安装路径的话,一定要记得先设置这个变量,否则find_package(Ceres)会报找不到的错。这也是很多同学自定义路径后遇到的第一个坑。
4. 常见问题与排查技巧实录
4.1 编译报错速查表
从网上各路反馈和我自己的踩坑经验来看,Ceres安装过程中常见的报错集中在这几类。我把典型问题和解决方式整理成一个速查表,供你对照排查。
| 报错信息 | 原因分析 | 解决方案 |
|---|---|---|
Could not find a package configuration file provided by "Eigen3" | Eigen未安装或CMake找不到Eigen路径 | 安装libeigen3-dev,或指定-DEIGEN3_INCLUDE_DIR |
Unable to find Google Log ... | glog/gflags未安装 | 安装libgoogle-glog-dev libgflags-dev |
Cannot find SuiteSparse | 稀疏矩阵库缺失 | 安装libsuitesparse-dev |
ceres/ceres.h: No such file or directory | 项目头文件搜索路径未包含Ceres | 检查include_directories(${CERES_INCLUDE_DIRS}) |
cannot find -lceres | 链接器找不到Ceres库文件 | 检查-L路径是否正确,或是否执行ldconfig |
libceres.so.2: cannot open shared object file | 动态链接库缓存未更新 | 执行sudo ldconfig,或设置LD_LIBRARY_PATH |
Unable to find LAPACK libraries | 缺少BLAS/LAPACK | 安装libatlas-base-dev liblapack-dev |
这里面的坑,80%集中在“依赖没装全”和“链接路径不对”两类。尤其第一个和最后一个看起来只是小缺失,实际上直接导致CMake配置失败,极其影响心情。
4.2 Eigen版本冲突的典型场景
Eigen版本冲突问题主要出现在编译ORB-SLAM3这类大型项目时,终端会刷出大段模板报错,最典型的是:
error: no match for 'operator-' in '...'或者:
error: static assertion failed: YOU_MIXED_DIFFERENT_NUMERIC_TYPES_这类报错如果出现在Eigen相关代码中,十有八九是Eigen版本和项目要求不匹配。ORB-SLAM3的官方文档明确要求Eigen 3.3以上,但实际开发测试用的是3.4.0。Ubuntu 20.04系统自带3.3.7,理论上可以编译,但在某些优化场景下会触发编译器无法处理的边缘情况。
解决办法就是我前面提到的:源码编译Eigen 3.4.0,然后在项目CMakeLists.txt中将EIGEN3_INCLUDE_DIR指到新版本路径。如果你不想修改CMakeLists.txt,还有一个快速办法:直接替换系统的Eigen头文件。操作前做好备份:
sudo mv /usr/include/eigen3 /usr/include/eigen3_backup sudo ln -s /usr/local/include/eigen3 /usr/include/eigen3这个方式风险较高,如果后面其他软件依赖旧Eigen可能会有问题。但很多SLAM项目是独立环境,这样处理效率最高,实测可用。如果你之后要跑ROS相关的功能包,这个方法不建议采用,还是走局部指定更稳妥。
4.3 卸载重装Ceres的正确姿势
如果之前装的是apt版本,或者编译安装过出问题的版本,需要干净卸载后再重装。apt版本卸载:
sudo apt remove libceres-dev sudo apt autoremove源码编译安装的卸载比较复杂,因为make install不会记录安装文件清单。一个实用的思路是重新进入之前的build目录执行make uninstall,前提是当时CMake生成了uninstall目标。如果没有,只能手动删除相关文件:
sudo rm -rf /usr/local/include/ceres sudo rm -f /usr/local/lib/libceres* sudo rm -f /usr/local/lib/cmake/Ceres sudo rm -f /usr/local/share/Ceres这里我提醒一句:如果在多个路径下都装过Ceres,用find / -name "libceres*"搜索所有残留文件,逐一确认删除,避免系统里存在多个版本的Ceres文件,编译时被CMake随机命中,造成线上问题和本地开发结果不一致的诡异情况。
5. 一些进阶配置与编译选项解析
5.1 开启OpenMP加速
Ceres在求解大规模问题时,可以通过OpenMP实现多线程加速。默认编译模式下,OpenMP的支持取决于CMake是否能检测到。如果你希望显式开启,在CMake配置时加一行:
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -DOPENMP=ON开启OpenMP后,Ceres的线性求解器会在多核CPU上并行计算,大型BA问题的求解速度提升明显。实测中,在8核机器上开启OpenMP后,稠密增量求解(DENSE_SCHUR)大概快1.5到2倍。代价是编译产物体积稍大、内存占用提高,但对桌面级SLAM项目来说完全值得。
5.2 使用CUDA后端加速
如果你有NVIDIA显卡,并安装了CUDA工具链(比如热搜词里出现的nvidia-driver-535,说明很多人已经配好了显卡驱动),可以在Ceres里启用CUDA后端:
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -DCUDA=ON启用CUDA后,Ceres可以使用GPU加速某些稠密矩阵运算。但需要说明的是,Ceres的CUDA支持主要面向大规模稠密求解场景,对SLAM中常见的稀疏BA问题帮助有限,很多情况下甚至感觉不出明显加速。所以除非你明确知道自己需要GPU求解,否则不用刻意开启这个选项。
5.3 使用自定义安装路径时的CMake配置细节
自定义安装路径在一些服务器环境比较常见,因为没有sudo权限,只能装到用户目录。这时候CMake配置和系统默认路径有些差异。
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/ceres-install make -j$(nproc) make install之后在项目CMakeLists.txt里,设置为:
set(Ceres_DIR "$ENV{HOME}/ceres-install/lib/cmake/Ceres") find_package(Ceres REQUIRED) include_directories($ENV{HOME}/ceres-install/include) target_link_libraries(your_project ${CERES_LIBRARIES})同时记得设置动态库搜索路径:
export LD_LIBRARY_PATH=$HOME/ceres-install/lib:$LD_LIBRARY_PATH这是无sudo权限环境下最完整的方案。很多服务器用户卡在这一步,其实核心就两个:CMake配置时指定Ceres_DIR,运行时指定LD_LIBRARY_PATH。
6. 我在实操中的一些体会
Ceres的安装本身并不复杂,真正消耗时间的往往是Eigen版本、依赖缺失、链接路径这些“周边问题”。我见过不少人卡在Could not find a package configuration file provided by "Eigen3"这一行报错上,其实也就是一条apt install libeigen3-dev的事,但如果不理解CMake查找规则,就很容易到处乱试。理解每个库在Ceres里的角色,比死记硬背安装命令有用得多。
还有一点想特别说明:不管你后续是编译ORB-SLAM3、VINS-Fusion还是自己的优化代码,尽量保持“把Ceres当作项目的一部分来管理”这个意识,而不是“装完就万事大吉”。Ceres版本、Eigen版本和项目代码三者之间存在隐形的兼容性链条,任何一环断裂都会产生极难排查的模板报错。最简单的做法就是记录自己在用的版本组合,换项目时先检查版本是否一致。
按照上面的流程装好后,其实你还能顺手做一件小事,就是保留好Ceres源码目录和build目录,不要删。后续如果想切换版本、查看官方示例或者用make uninstall清理,这些目录都能派上用场。我把这视为源码编译安装“最后的保险”,虽然平时用不上,但真遇到问题时会给你省下大量的排查时间。