1. 安装前先弄清楚:Ceres依赖什么、为什么源码编译最稳
Ceres Solver在SLAM、三维重建、相机标定这些方向几乎是绕不开的依赖库,ORB-SLAM3、VINS-Fusion、LIO-SAM这些项目里都能看到它的身影。它本质上是一个专门求解非线性最小二乘问题的C++库,对于搞过优化求解的同学来说,Ceres就像是一个老朋友,帮你把复杂的数值优化过程封装得明明白白。
我在Ubuntu20.04上部署这些项目时踩过不少坑,这篇文章就把整个安装过程从头到尾捋一遍,包括每个依赖包的作用、版本怎么选、编译的时候有哪些坑,希望能让读者少走弯路。
1.1 Ceres到底解决什么问题
先花半分钟说清楚Ceres是干什么的。在视觉SLAM里,我们要根据图像特征点的观测来估计相机位姿和地图点位置,这个问题最终会转化成一个最小化重投影误差的优化问题。手写高斯牛顿法或者LM算法不是不行,但工程上要考虑稀疏性、鲁棒核函数、数值求导这些问题,工作量很大。
Ceres把这些都封装好了,你只需要定义代价函数、残差块和损失核函数,剩下的交给求解器。它支持自动求导、数值求导和解析求导,内置了LM和Dogleg等求解策略,还能用OpenMP和TBB做多线程加速。这也是为什么ORB-SLAM系列、VINS系类都选择它作为后端优化的核心库。
1.2 版本选型:1.14还是2.x
这是安装前必须定下来的事。Ceres目前主流有两个大版本分支:1.14.0和2.x系列。1.14.0是2019年前后的稳定版,很多SLAM项目(比如ORB-SLAM2、ORB-SLAM3早期commit)都是基于这个版本测试的,API比较老,网上资料也最多。
2.x系列从2022年开始逐步成为主线,API上有一些破坏性变更,比如一些头文件路径调整、函数命名规范化。新项目建议直接用2.1.0或2.2.0,老项目建议跟着原仓库锁定的版本走。我自己遇到过ORB-SLAM3在编译时用Ceres 2.x报一堆类型不匹配的错误,切回1.14.0就顺利通过了。所以安装前先去项目CMakeLists.txt里看一眼[find_package(Ceres)]那句后面写的版本号,按需安装,别盲目追求新版。
2. 环境确认与基础依赖安装
Ceres本身不是一个"零依赖"的库,它需要Eigen做矩阵运算、glog做日志、gflags做参数解析,可选的还有SuiteSparse、TBB等加速库。这里的顺序很关键,依赖没装好就编译Ceres,后面会冒出各种奇奇怪怪的undefined reference错误。
2.1 先确认系统与工具链
我用的环境是Ubuntu20.04,内核版本5.15左右,gcc/g++版本9.4.0,CMake版本3.16.3。这几个基础工具版本直接决定了后面能否顺利编译。如果你的系统比较干净,建议先执行一遍系统更新,把软件源索引刷新一下。
sudo apt update sudo apt upgrade -y然后确认编译工具链是否齐全:
gcc --version g++ --version cmake --version make --version如果没有安装,执行这条命令统一装好:
sudo apt install -y build-essential cmake git其中build-essential会装好gcc、g++、make,这是编译C/C++项目的基本盘。cmake版本3.16以上足够编译Ceres,但如果系统自带的cmake过旧,建议用pip装一个新版:
pip3 install cmake --upgrade注意pip装的cmake可能在/usr/local/bin,确保它优先于/usr/bin下的旧版本,可以用which cmake确认一下。
2.2 安装Eigen、glog、gflags、SuiteSparse
这部分是Ceres安装的关键依赖,任何一个缺失或版本不兼容都会导致编译失败。我建议直接分两组安装,第一组是必须依赖,第二组是推荐依赖。
必须依赖包括:
sudo apt install -y libeigen3-dev sudo apt install -y libgoogle-glog-dev sudo apt install -y libgflags-dev推荐依赖,用于支持稀疏矩阵加速求解:
sudo apt install -y libsuitesparse-dev sudo apt install -y libatlas-base-devEigen是一个头文件库,不需要编译安装,libeigen3-dev会默认装到/usr/include/eigen3。但注意,Ceres搜索Eigen时默认找的是/usr/include/eigen3路径,如果你是从源码安装Eigen到/usr/local/include/eigen3,后续CMake配置时要手动指定-DEIGEN_INCLUDE_DIR,这个坑我在后文详细说。
glog是Google的日志库,Ceres用它输出优化过程中的调试信息。gflags是命令行参数解析库,Ceres的可选工具会用到它,建议一起装。
SuiteSparse是一套稀疏矩阵求解工具集合,包含UMFPACK、CHOLMOD、SPQR等求解器。Ceres在做大规模BA优化时,使用SuiteSparse能大幅提升求解速度,建议务必安装。libatlas-base-dev是BLAS和LAPACK的实现,同样用于加速线性代数运算。
2.3 额外提一下TBB和OpenMP
Ceres支持通过TBB(Threading Building Blocks)做多线程加速。在Ubuntu20.04上可以用一条命令安装:
sudo apt install -y libtbb-dev但这个依赖比较"看心情",如果你发现TBB版本和Ceres不兼容,可以在CMake配置时通过-DTBB=OFF关掉。默认情况下Ceres也会尝试用OpenMP,gcc自带支持,这一般不会出问题。
我个人的建议是:第一次安装时先把TBB装上,如果编译Ceres时出现tbb相关的错误,再回退不迟。多线程加速在BA优化场景下确实能感受到明显性能提升。
3. 核心依赖编译注意点与版本冲突
直接用apt装依赖看起来很简单,但实际部署中我遇到最多的就是Eigen版本和glog版本导致的编译问题。这一节把常见版本坑单独拿出来说,因为这些问题在SLAM项目联调阶段几乎必现。
3.1 Eigen的版本选择与路径坑
Ubuntu20.04的软件源里libeigen3-dev默认是3.3.7,这个版本对Ceres 1.14和2.x都兼容,所以如果你只是希望"能编译通过",用apt装的就够了。
但有个细节:如果你同时电脑上有其他版本Eigen,比如ROS自带了一套Eigen、或者Anaconda环境里也带了一套Eigen,编译时CMake可能会找到不同的版本,导致编译出来的程序在运行时崩溃。这种问题非常隐蔽,报错形式通常是报错信息:eigen_assert失败或者断言错误。
我的排查经验是,编译前先看Ceres的CMake输出,确认它找到的是哪个路径下的Eigen:
grep -i "eigen" CMakeCache.txt正常的输出应该指向/usr/include/eigen3。如果指向了别的位置,建议把非标准路径的Eigen暂时移走,或者在CMake配置时显式指定:
cmake -DEIGEN_INCLUDE_DIR=/usr/include/eigen3 ..另外,有些项目会要求Eigen 3.4以上版本,这时候apt装的3.3.7就不够了。要从源码装Eigen的话,记得安装完后做符号链接:
sudo ln -s /usr/local/include/eigen3/Eigen /usr/local/include/Eigen sudo ln -s /usr/local/include/eigen3/unsupported /usr/local/include/unsupported否则很多依赖Eigen的库会找不到头文件。
3.2 glog版本升级引起的链接地狱
如果你从源码编译安装新版glog(比如手动编译了glog 0.6.0),那么Ceres在链接glog时可能会出现undefined reference to google::这类错误。原因是新版glog把命名空间从google改成了glog。
Ubuntu20.04仓库自带的libgoogle-glog-dev版本是0.4.0,默认命名空间是google,和Ceres 1.14兼容得很好。如果你自作主张升级了glog,再回头编译老版本Ceres,就会在链接阶段炸掉。
解决办法有三条,按推荐顺序排列:
- 用apt自带的glog,不要手动升级;
- 如果非要升级glog,就用Ceres 2.x版本,它已经适配了新的命名空间;
- 手动修改老版本Ceres的源码,把glog相关的命名空间全部替换成glog::,工作量很大,不推荐。
这条经验来自VINS-Fusion部署,我在一段时间内被这个问题折磨了很久,换回apt自带版本后马上就好了。
3.3 SuiteSparse加速与BLAS的关系
SuiteSparse和BLAS/LAPACK是一套组合拳。Ceres在CMake配置阶段会检测LAPACK和BLAS库是否存在,如果检测不到,就自动关闭SuiteSparse相关支持。这时即使装了libsuitesparse-dev,也无法启用CHOLMOD等加速求解器。
检查是否启用成功,看CMake输出里这几行:
-- Found LAPACK library: /usr/lib/x86_64-linux-gnu/liblapack.so -- Found BLAS library: /usr/lib/x86_64-linux-gnu/libblas.so -- SuiteSparse found (with LAPACK enabled)如果显示-- Suitesparse not found,大概率是LAPACK没有被正确识别。在Ubuntu20.04上,需要额外安装liblapack-dev:
sudo apt install -y liblapack-dev libblas-dev安装后清理Ceres的build目录重新编译,就能识别到了。这一步之所以重要,是因为SLAM后端优化中,Ceres默认用的密集求解器在几千个路标点的规模下性能下降明显,而使用稀疏求解器CHOLMOD能快上好几倍。
4. Ceres源码编译安装全流程
依赖准备好之后,编译Ceres本身反而是最不容易出错的部分。官方GitHub仓库地址是github.com/ceres-solver/ceres-solver,我们可以用git clone的方式来获取源码,也可以直接下载release的tar包。
4.1 下载源码并切换到指定版本
我一般推荐直接clone最新仓库,然后根据项目需要切换到对应tag:
git clone https://github.com/ceres-solver/ceres-solver.git cd ceres-solver git tag -l比如你要装1.14.0版本,就执行:
git checkout 1.14.0要装2.1.0版本,就执行:
git checkout 2.1.0这里想提醒一下,如果是第一次下载,clone到的默认分支通常是最新的2.2.0。如果你不确定项目要求什么版本,先问自己一个问题:这个项目是近几年更新的吗?如果项目更新频繁,直接使用master分支一般没问题;如果是经典老项目,优先选择1.14.0。
4.2 编译选项与完整构建命令
确认版本后,开始正式编译。我的习惯是在源码根目录下新建一个build目录,所有构建产物都放在里面,这样要清理时直接删掉build目录就行,不会污染源码。
cd ceres-solver mkdir build cd build cmake .. make -j$(nproc) sudo make install这里的-j$(nproc)表示用CPU所有核心并行编译,大幅缩短编译时间。在8核16线程的机器上,全量编译Ceres大概需要一到两分钟,速度很快。
如果希望优化编译过程,可以在cmake阶段指定Release模式:
cmake -DCMAKE_BUILD_TYPE=Release ..Release模式会开启-O3优化,Ceres在算法求解时数值性能更好。Debug模式主要用于调试,一般不需要。
还有一个参数是-DMINIGLOG=ON,这个选项告诉Ceres使用自带的迷你glog替代系统的glog。如果不想装glog、或者glog版本冲突严重,可以用这个选项避开问题。但我个人不推荐,因为迷你glog功能不全,调试信息输出也不完整。
编译完成后,Ceres的库文件会被安装到/usr/local/lib下,头文件在/usr/local/include/ceres。确认是否安装成功:
ls /usr/local/lib/libceres* ls /usr/local/include/ceres正常会看到libceres.a或者libceres.so,以及一堆头文件。到这里,Ceres库本身就算安装完成了。
4.3 通过官方示例验证安装
这一步很多人会跳过,但我建议无论如何都跑一下官方自带的示例程序,它能在正式进入项目之前快速暴露环境问题。
Ceres源码的examples目录下有一个helloworld示例,它求解一个最简的非线性最小二乘问题。在build目录下执行:
make -j$(nproc) helloworld ./bin/helloworld正常输出是一串初始值到最优值的迭代过程,最后x和y分别收敛到0.5和0.1左右。如果这个示例能跑通,说明Ceres的核心功能函数、自动求导、线性求解器都工作正常。
我还建议编译一下bundle_adjuster这个示例,它是Ceres对SLAM问题最直接的应用示例,能验证SuiteSparse是否真正链接成功:
make -j$(nproc) bundle_adjuster ./bin/bundle_adjuster ../data/problem-16-22106-pre.txt这里会看到一个包含16个相机和22106个观测点的BA问题被顺利优化,同时在输出中能看到Ceres Solver Report一栏,里面记录着迭代次数、终止条件、耗时等信息。如果你的编译配置里SuiteSparse没有生效,这个程序依然能跑,但每次迭代的时间会明显变长,这是一个可感知的性能差异。
5. 在CMake项目中正确引用Ceres
装好Ceres后,下一步就是在自己的项目里使用它。这一节讲CMake的配置方法,这部分虽然不难,但每次都有朋友踩坑,重点讲两个细节。
5.1 引用Ceres的标准CMake写法
Ceres官方提供了FindCeres.cmake,安装后会被放到/usr/local/lib/cmake/Ceres目录下。在项目CMakeLists.txt里这样写:
find_package(Ceres REQUIRED) target_link_libraries(your_target ${CERES_LIBRARIES}) target_include_directories(your_target PRIVATE ${CERES_INCLUDE_DIRS})这里的CERES_LIBRARIES会自动解析Ceres及其所有依赖库的链接路径,CERES_INCLUDE_DIRS则指向Ceres和Eigen的头文件路径。
如果你是老项目,可能会遇到find_package(Ceres)找不到的情况。大概率是因为Ceres安装目录不在CMake默认搜索范围内,可以手动指定路径:
cmake -DCeres_DIR=/usr/local/lib/cmake/Ceres ..5.2 和Eigen一起引用的常见坑
Ceres的头文件会间接引用Eigen,所以如果你在target里同时使用Eigen和Ceres,必须先包含Eigen的头文件路径,再包含Ceres的头文件路径。这个顺序在CMake里一般不体现(因为include_directories是累加关系,不关心顺序),但在源码中include的顺序会影响编译。
更常见的坑是Eigen版本不一致导致的模板编译错误。比如你的项目里自己引用了/usr/include/eigen3的Eigen 3.3.7,而Ceres在安装时用了源码编译的Eigen 3.4,两套Eigen头文件混用,编译时就会出现internal::eigen_assert之类非常晦涩的报错。
解决办法是统一Eigen来源。要么全部用apt安装的Eigen,要么全部用源码安装的Eigen。在CMakeCache里检查EIGEN_INCLUDE_DIR变量的值,确认家里只有一个Eigen版本。
6. 实战中的高频问题与排查记录
这部分是踩坑合集。我在多台电脑、多个Ubuntu20.04环境、多个SLAM项目里折腾Ceres的安装和集成,每次遇到的问题都不太一样,总结出下面这张速查表,希望能帮读者快速定位问题。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 编译时找不到ceres/ceres.h | Ceres未安装或路径不在搜索范围 | 检查/usr/local/include/ceres是否存在;CMake中指定Ceres_DIR |
| 编译时出现undefined reference to google::... | glog版本过新,命名空间变更 | 卸载手动编译的glog,改用apt的libgoogle-glog-dev |
| 编译时出现Eigen相关模板错误 | 多个Eigen版本冲突 | 统一Eigen来源,建议用apt的libeigen3-dev |
| 编译时提示找不到SuiteSparse | 缺少LAPACK/BLAS依赖 | 安装liblapack-dev、libblas-dev,删除build目录重新配置 |
| CMake找不到CeresConfig.cmake | Ceres安装路径非标准 | 手动指定Ceres_DIR或设置CMAKE_PREFIX_PATH |
| 编译成功后运行时崩溃 | Eigen对齐问题或版本混用 | 检查所有依赖库是否使用同一Eigen安装路径 |
| 编译速度极慢 | 未开启OpenMP/TBB加速 | 安装libtbb-dev,或者在CMake中开启-DOPENMP=ON |
6.1 CMake找不到Ceres
这个问题是初学者最常遇到的。明明Ceres装了,但项目里find_package(Ceres REQUIRED)就是报错。多数情况下是Ceres编译后安装路径不在CMake的默认查找路径里。
推荐用CMAKE_PREFIX_PATH来指定:
cmake -DCMAKE_PREFIX_PATH=/usr/local/lib/cmake/Ceres ..或者在项目CMakeLists.txt的find_package之前手动设置:
set(Ceres_DIR "/usr/local/lib/cmake/Ceres") find_package(Ceres REQUIRED)另外检查一下,如果是从源码安装时用了-DCMAKE_INSTALL_PREFIX指定了其他前缀,那CMake配置时也必须带上同样的参数。
6.2 与ROS环境共存时的问题
很多SLAM项目会在ROS环境下编译,这时候Ceres安装位置可能不止一个。比如ROS的包管理器可能自动装了一份Ceres到/opt/ros/noetic/lib,而你自己又源码装了一份在/usr/local/lib。两个版本同时存在时,CMake会优先找到哪个,完全取决于CMAKE_PREFIX_PATH和环境变量CMAKE_INCLUDE_PATH的搜索顺序。
我在ORB-SLAM3编译时遇到过这个问题,最后通过在CMakeLists里显式指定Ceres路径来解决:
set(Ceres_DIR "/usr/local/lib/cmake/Ceres")如果你希望ROS环境自带的Ceres生效,就把它对应路径放在最前面。
6.3 多版本Ceres编译冲突的处理
如果电脑上已经有旧版本Ceres,现在要装新版,直接编译安装新版本一般不会覆盖旧版本的头文件,但动态库会。旧的libceres.so会被新的替换,这导致一个编译好的老程序依赖新版本Ceres运行,反而可能崩溃。
处理方法有两种:一是直接把旧版本完全卸载,包括头文件和库文件;二是用-DCMAKE_INSTALL_PREFIX把不同版本安装到不同前缀下,然后在不同项目中指定不同路径。第二种方法更干净,但需要额外注意rpath的设置。
一般而言,对于技术学习或日常工具开发,我推荐直接卸载旧版本、安装新版本即可,版本隔离只在需要同时维护多个老项目时才值得考虑。
7. 在SLAM项目部署中的实际经验
这篇博文之所以专门写Ubuntu20.04下的Ceres安装,很大程度上是因为它和SLAM项目部署高度相关。如果你点进来是为了装ORB-SLAM3、VINS-Fusion或者LIO-SAM,那这一节的经验也许比前面的教程更有用。
7.1 ORB-SLAM3对Ceres版本的选择
ORB-SLAM3在自己的CMakeLists里写的find_package(Ceres REQUIRED)并没有指定版本,但实际测试下来,它和Ceres 1.14.0的兼容性明显优于2.x。原因是ORB-SLAM3内部使用了部分Ceres旧API,在Ceres 2.0之后有了改动,导致编译报错。
如果你非要使用Ceres 2.x来编译ORB-SLAM3,一种可行的改法是修改ORB-SLAM3源码中涉及Ceres的部分,把旧API替换成新API。但这样做非常痛苦,需要逐个检查残差块的构建方式。相比之下,直接安装Ceres 1.14.0时间成本低得多。
7.2 VINS-Fusion的依赖顺序
VINS-Fusion是一个视觉惯性导航系统,它依赖Ceres做后端优化。我在Ubuntu20.04上部署时遇到过一个问题:同时安装Ceres和Eigen之后,VINS-Fusion编译时提示Ceres版本太旧。查了一下,VINS-Fusion对Ceres 1.14支持良好,但如果系统里存在多个版本Ceres,CMake可能找到的是旧版本。
解决方法和前面一样,使用Ceres_DIR显式指定Ceres安装路径。同时确认Eigen版本不低于3.3。VINS-Fusion官方文档里没有特别指明这两点,但实际操作中这两步必不可少。
7.3 视觉惯性导航系统里Ceres加速的实际体感
我做过一个小的对比实验:在同一个VINS-Fusion数据集上,分别使用Ceres默认配置(不启用SuiteSparse)和启用SuiteSparse的配置,后端优化单帧处理的耗时差距在20%到40%之间。数据集规模越大、约束越多,差距越明显。
因此,如果你的项目包含视觉SLAM或大规模BA优化,强烈建议在安装Ceres时确保SuiteSparse被正确识别。判断方法很简单,编译Ceres时看CMake输出是否包含以下内容:
-- Found SuiteSparse: ... (with LAPACK enabled)如果只有SuiteSparse found而没有with LAPACK字样,说明LAPACK可能没有链接上,需要补装liblapack-dev,然后重新编译Ceres。这一步值得花时间。
8. 卸载与重装Ceres的正确方式
最后聊一下卸载和重装。Ceres安装教程很多,但卸载教程很少。将来如果升级Ceres或者装坏了要重来,知道怎么彻底卸载能避免很多烦恼。
8.1 从系统中移除Ceres
如果你是通过make install安装在默认路径下的,可以用以下方式卸载:
cd ceres-solver/build sudo make uninstall注意,make uninstall只在CMake生成了uninstall目标时才有效。CMake默认不生成这个目标,需要提前用cmake -DUNINSTALL_TARGET=ON配置。如果你当初没有开启,就只能手动删除文件:
sudo rm -rf /usr/local/lib/libceres* sudo rm -rf /usr/local/include/ceres sudo rm -f /usr/local/lib/cmake/Ceres/CeresConfig.cmake sudo rm -f /usr/local/lib/cmake/Ceres/CeresTargets.cmake sudo rm -f /usr/local/lib/pkgconfig/ceres.pc如果当初指定了CMAKE_INSTALL_PREFIX,则把上面路径换成对应前缀下的路径。
8.2 重新安装时的注意事项
重新安装前,记得清理旧的build目录,否则CMake可能会有缓存残留:
cd ceres-solver rm -rf build如果之前使用过-D参数指定依赖库路径,重新配置时务必带上同样的参数,或者直接清空build目录让它重新检测。
我自己在Ubuntu20.04上多次重装Ceres之后,养成了一个小习惯:把所有自定义安装记录在一个笔记里,包括使用了哪些cmake参数、依赖装到了哪个路径下。这样下次换机器或者重装系统,照着笔记执行一遍就能还原环境,不会丢三落四。
在多个版本的切换实践中,我后来比较喜欢用Docker来隔离开发环境,在容器里编译SLAM项目就不需要反复折腾宿主机上的库版本了。当然这是后话,如果只是单机开发,上面这套手动安装流程已经足够可靠。