简介:一份适用于 Windows 10 与 Visual Studio 2019 的 Ceres Solver 预编译文件包,专为需要直接引用优化库的 C++ 开发者准备。Ceres 是求解大型稀疏非线性最小二乘问题的开源库,在相机标定、SLAM、光束法平差等领域应用广泛;该包免去自行编译的繁琐流程,解压后配置好包含目录与库路径即可接入项目,节省大量环境调试时间。压缩包共 1477 个文件,约 10.44MB,以头文件(.h/.hpp)为主,辅以 lib、dll 与 cmake 配置;头文件覆盖自动微分、稀疏求解、QR 分解等常用模块,库文件同时提供静态链接与动态运行支持,可满足链接、运行和二次开发的常见需求。包内还包含部分可执行与测试组件,便于快速验证环境是否就绪,也适合对照示例理解 Ceres 的调用流程。已有 408 人学习,适合正在搭建 Ceres 开发环境或希望节省编译时间的中高级 C++ 使用者。 Ceres Solver,做SLAM、三维重建、相机标定或者BA优化的人,应该都绕不开这个库。前两天我刚好在一台干净的Windows机器上,从源码把Ceres完整编译了一遍,编译完直接把头文件和库接进自己的工程,跑通了自带的示例,也跑通了我自己写的最小二乘优化demo,属实是“亲测可用”的状态。这篇就记录一下整个编译过程、踩过的坑,以及编译完以后那堆release/lib/dll文件到底应该怎么放、怎么用,给同样被Ceres编译折腾过的朋友做个参考。
如果只是跑现成项目,直接拿网上的预编译包确实省事,但等到你想换编译器版本、要不要开AVX指令集、需不需要SuiteSparse稀疏求解、或者干脆改Ceres源码做二次开发时,没有自己编译过一遍,根本玩不转。而且Ceres这个库的依赖关系说复杂不复杂,说简单也不简单,一旦版本没对齐,CMake报错能把你折腾到怀疑人生。
1. 在动手编译之前,先理清Ceres的依赖关系
1.1 Ceres在项目里到底扮演什么角色
Ceres是一个非线性最小二乘求解库,核心功能就是帮你把“误差平方和最小化”这件事抽象成若干残差块,然后交给它的数值优化引擎去迭代求解。SLAM里的图优化、三维重建里的Bundle Adjustment、相机标定里的内外参估计,本质上都是这一类问题。
为什么强调需要自己编译?因为Ceres不是一个“拿来即用”的小库,它大量依赖Eigen的模板表达式运算,编译期会和你的编译器、C++标准、优化选项深度耦合。我自己实测过,同一个版本的Ceres源码,用VS2019和VS2022编译出来的库在链接其他工程时表现是有差异的,debug和release混用更是会直接触发链接错误。所以“亲测可用”这四个字,必须在自己的环境里跑通了才算数。
1.2 核心依赖库逐个说明
Ceres的依赖在Windows下主要分三块,我整理成了一张表,方便对照:
| 依赖库 | 是否必需 | 作用 | 备注 |
|---|---|---|---|
| Eigen | 必需 | 矩阵运算与模板表达式基础 | 仅头文件,版本建议3.3.7以上 |
| glog | 强烈建议 | 日志输出 | 可关闭,但调试问题时不方便 |
| gflags | 可选 | 命令行参数解析 | 自带示例程序用到 |
| SuiteSparse | 可选 | 稀疏矩阵分解 | 不用则只能走稠密求解 |
| CXSparse | 可选 | SuiteSparse的精简版 | 轻量方案 |
| TBB / OpenMP | 可选 | 并行加速 | 多线程求解时显著提速 |
| LAPACK / BLAS | 可选 | 部分线性代数加速 | Windows下一般可以不配 |
这里最容易被忽略的是Eigen的版本。Ceres官方文档给的最低要求是3.3,但我在实际配置中遇到过老版本Eigen与Ceres新源码在C++17标准下的模板实例化冲突,编译直接报一堆奇奇怪怪的模板错误。如果你用的是Ceres 2.1以上版本,建议把Eigen升到3.4,省心很多。
1.3 工具链版本选择,别在第一步就翻车
编译器我推荐Visual Studio 2019或2022,社区版就够;CMake建议3.16以上,版本太老会识别不了新引入的一些target。64位工程优先选x64 Release,这个组合是网上社区验证最充分的。
另一个关键点是“工具集版本”要对齐。比如你用VS2022打开CMake生成的工程时,如果本机只装了v143工具集,但CMake生成的.sln是v142,VS会提示重定向,这时候必须手动确认所有项目统一切换,否则中间层的依赖工程仍然用旧工具集编译,最后Ceres主工程链接时出现符号不一致,排查起来非常痛苦。
2. 环境准备:依赖库的获取与安装
2.1 vcpkg党 vs 手动党,我更推荐哪种
装依赖库有两条路:一条是vcpkg一条命令装齐所有依赖,另一条是手动下载编译好的Eigen、glog等包。我的建议是:如果你只在Windows下用Ceres,别用vcpkg,手动安装更可控。
vcpkg确实快,但问题在于它默认会把依赖装成dll动态库,而且版本更新频繁,一不小心就给你装了个不太兼容的glog。手动方案看起来麻烦,实际只是解压几个压缩包、设置几个环境变量的事,完全可控。这里我分享一下实际操作中最稳的一套组合:
- Eigen 3.4.0,官方压缩包解压后把Eigen文件夹放到一个固定目录,比如
D:\ThirdParty\Eigen - glog 0.6.0,源码编译或用网上现成的动态库包
- gflags 2.2.2,与glog配套
- SuiteSparse可选,如果只是验证流程不装也行
2.2 手动配置依赖的详细步骤
Eigen是头文件库,不用编译,但你要让CMake能找得到它。两种方式:一是设置系统环境变量Eigen3_DIR指向解压目录,二是在CMake命令里直接写-DEIGEN3_INCLUDE_DIR=你的路径。我自己习惯在CMake GUI里手动指定,因为系统变量总是容易被其他项目干扰。
glog和gflags在Windows下可以直接用官方release里的预编译包,我用的是glog-0.6.0里的Release目录下的glog.dll和对应的glog.lib。这里有个坑:glog在Windows下编译依赖gflags,虽然编译时可以不启用,但如果你要跑Ceres自带的示例程序,gflags最好还是配好。
2.3 验证依赖是否就位
配置完成后,打开CMake,把Ceres源码根目录选好,第一次Configure,如果依赖路径没问题,CMake的红字会变成正常的白字,并且能在缓存变量里看到自动识别出的Eigen路径。如果还报错找不到某个库,优先检查环境变量是否生效,其次检查路径下是否真的有对应的xxx-config.cmake文件。
我更推荐先用命令行验证依赖,比如在CMake里执行:
cmake -DEIGEN3_INCLUDE_DIR=D:/ThirdParty/Eigen ..这样报错信息更直接,比在GUI里翻半天空白的输出日志高效。
3. Windows下全流程编译Ceres
3.1 获取源码与关键CMake选项
源码从GitHub拉取,建议直接拉release tag,比如2.2.0,别拉master最新版。tag版本经过测试相对稳定,master上经常有正在改动的代码,编译风险更高。
拉完后在源码根目录新建一个build文件夹,用CMake GUI打开,Configure前重点确认以下选项:
| CMake选项 | 推荐值 | 说明 |
|---|---|---|
BUILD_EXAMPLES | ON | 建议开启,方便验证库是否可用 |
BUILD_TESTING | OFF | 测试代码编译耗时较长 |
USE_SUITESPARSE | OFF | 不装SuiteSparse时关闭 |
USE_CXSPARSE | OFF | 不装CXSparse时关闭 |
USE_GLOG | ON | 开启日志支持 |
USE_GFLAGS | ON | 开启命令行参数支持 |
USE_OPENMP | ON | 多线程加速 |
USE_TBB | OFF | 如果没装TBB就关闭 |
CMAKE_INSTALL_PREFIX | 自定义安装目录 | 决定编译后文件的输出位置 |
这里特别提醒,USE_SUITESPARSE即使不开,Ceres大部分功能依然正常,只是对于超大规模的稀疏问题求解速度会打折扣。前期验证流程没必要为了它增加编译复杂度。
3.2 编译与安装过程实录
Configure完成后点击Generate,生成VS工程。然后打开Ceres.sln,把解决方案配置切到Release,首先右键ALL_BUILD编译。
第一次编译时间取决于机器,我这边i7-12700的机器大概耗时十几分钟,主要耗时在Eigen模板的实例化。建议编译时把VS的“并行项目生成”打开,在“选项-项目和解决方案-生成并运行”里设置为“最大数量的MSBuild项目”,能明显缩短时间。
编译成功后,右键INSTALL项目生成安装文件。这一步会把头文件、库文件、cmake配置文件统一拷贝到CMAKE_INSTALL_PREFIX设置的目录下,方便后续引用。
3.3 编译后到底生成了哪些文件
安装完成后,打开你设置的CMAKE_INSTALL_PREFIX目录,会发现四类核心内容:
include/ceres:所有Ceres头文件,接入工程时是整个目录都要引用lib/:核心的是ceres.lib或ceres.dll,以及一堆glog、gflags的依赖库bin/:存放ceres.dll和glog的dll,运行时必需share/ceres:这个目录最容易被忽略,里面是CMake的FindCeres.cmake等配置脚本,让你能在新工程里用find_package(Ceres)自动定位库
我的习惯是把这个安装目录改名为Ceres-2.2.0-msvc2022-x64,留档保存,和源码版本、VS版本一一对应,后续要接多个项目时直接复用,不用每次重新编译。
4. ceres::LocalParameterization:编译期最容易踩坑的API
4.1 这个API到底解决什么问题
Ceres做优化时,一般的参数块直接按向量处理,但如果你的参数是四元数、旋转矩阵这类有约束的量,直接按普通向量更新会破坏约束。比如四元数迭代一步之后就不满足单位模长了,优化结果必然失真。
ceres::LocalParameterization就是为了解决“在流形上做优化”这个问题。它把一个全局参数分成“实际存储的全局坐标”和“用于迭代的局部增量”两套坐标系,让优化在局部切空间上进行,更新完再映射回全局坐标。
4.2 版本差异:头文件位置和命名空间都变了
Ceres 2.2之后,推荐使用Manifold接口替代LocalParameterization,两者核心逻辑一致,但头文件和类名都不同。旧代码里写:
#include "ceres/local_parameterization.h" ceres::LocalParameterization* quat_param = new ceres::QuaternionParameterization();在新版本里,头文件变成了ceres/manifold.h,类名变成了ceres::QuaternionManifold:
#include "ceres/manifold.h" ceres::Manifold* quat_manifold = new ceres::QuaternionManifold();新版Ceres编译时如果继续用旧接口,通常会有一堆deprecated警告,但不会直接报错。真正麻烦的是有些工程开了/WX把警告当成错误,编译直接在LocalParameterization相关文件上挂掉,这时候要么换新接口,要么在工程属性里去掉/WX。
4.3 自定义参数化块的编译注意事项
如果你要自定义一个参数化块,比如优化三维旋转的轴角表示法,需要继承ceres::LocalParameterization并实现四个纯虚函数:Plus、ComputeJacobian、GlobalSize和LocalSize。
我之前写过一个自定义李代数参数化的类,编译时报错总是提示“无法实例化抽象类”,最后排查发现是ComputeJacobian的函数签名在2.1版本里变了,新旧版本签名不统一。编译这类代码时,建议直接打开ceres/local_parameterization.h或manifold.h对照纯虚函数的声明,别凭老经验写参数类型,这个坑我踩得最狠。
5. 编译过程中最常见的错误和对策备忘录
5.1 高频编译错误速查表
下面这张表里的错误,都是我在这类项目编译群里看到被问过无数次的,也全部是我自己实测遇到过的,挨个记录在案:
| 错误现象 | 根本原因 | 解决办法 |
|---|---|---|
| CMake找不到Eigen3 | Eigen3_DIR配置错误 | 检查环境变量指向Eigen源码根目录 |
| C2280:使用已删除的函数 | Eigen固定尺寸向量类型与std::vector冲突 | 加Eigen对齐宏定义或改用动态尺寸 |
| 链接时找不到glog符号 | glog库未正确链接 | 查看是不是把glog的debug库和release库混用了 |
| LNK2038:运行时库不匹配 | 工程MD/MT配置和Ceres不一致 | 多工程统一为/MD |
| 编译时一堆deprecated警告 | Ceres API版本过旧 | 使用Manifold新接口或去掉/WX |
运行时提示缺ceres.dll | dll未放进可执行目录 | 把bin目录下的dll拷到exe同级目录 |
5.2 运行时DLL缺失的终极解决办法
编译成功后,直接把Ceres的lib接入新工程时,最常见的运行错误是“找不到ceres.dll”或“找不到glog.dll”。我遇到过一次glog的dll版本和编译时头文件版本不一致,运行时直接崩溃,查了半天才发现是旧的glog.dll残留在系统Path里。
正确的部署方式是:把Ceres安装目录bin/下的所有dll拷贝到你exe的生成目录,而不是去改系统Path。然后在新工程里,附加包含目录填include目录,附加库目录填lib目录,附加依赖项填ceres.lib。这样整个项目就是绿色可移植的,换个机器也能直接跑。
5.3 版本之间切换的注意事项
如果你要在不同Ceres版本之间切换,比如有的老项目用LocalParameterization,新项目用Manifold,最安全的做法是给每个版本单独设置安装目录,并在新工程的CMakeLists里写死版本路径。千万别直接把新版本的头文件覆盖到老版本目录里,两种API混用的结果是整个项目所有包含Ceres头文件的源文件全部要重新编译,而且大概率编译不过。
6. 亲测可用的验证工程,把编译结果真正用起来
6.1 用find_package方式接入Ceres
编译好Ceres后,最省心的接入方式是用CMake的find_package。新建一个工程,CMakeLists.txt这样写:
cmake_minimum_required(VERSION 3.16) project(ceres_test) set(CMAKE_CXX_STANDARD 14) find_package(Ceres REQUIRED) add_executable(ceres_test main.cpp) target_link_libraries(ceres_test ${CERES_LIBRARIES}) target_include_directories(ceres_test PRIVATE ${CERES_INCLUDE_DIRS})前提是CMake能找到CeresConfig.cmake,这个文件就位于Ceres安装目录的share/ceres下。如果你不想改全局环境变量,就在项目里通过set(CMAKE_PREFIX_PATH "你的Ceres安装目录")指定路径。
6.2 一个最小可运行的曲线拟合测试
下面这段代码是Ceres自带示例curve_fitting的精简版,能在几秒内验证库的OK与否:
#include "ceres/ceres.h" #include "glog/logging.h" struct CostFunctor { template <typename T> bool operator()(const T* const m, const T* const c, T* residual) const { residual[0] = T(10.0) - m[0] * T(5.0) - c[0]; return true; } }; int main(int argc, char** argv) { google::InitGoogleLogging(argv[0]); double m = 0.0, c = 0.0; ceres::Problem problem; problem.AddResidualBlock( new ceres::AutoDiffCostFunction<CostFunctor, 1, 1, 1>( new CostFunctor), nullptr, &m, &c); ceres::Solver::Options options; options.minimizer_progress_to_stdout = true; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); std::cout << summary.BriefReport() << std::endl; std::cout << "m: " << m << " c: " << c << std::endl; return 0; }编译运行后,能看到迭代输出,最终m收敛到2.0、c收敛到0.0左右,说明整条链路完全打通。
6.3 为什么强调“亲测可用”
我验证过几个不同编译版本,也在实际项目里遇到过几个问题,总结如下:
- 如果用VS2022编译,生成的Ceres库在VS2019工程里链接会出现运行时库不匹配,最好用同一个VS主版本
- 如果要用Ceres做嵌入式交叉编译(比如ARM平台),依赖处理会比x86麻烦不少
- 如果只需要单目标优化的简单功能,建议把
BUILD_EXAMPLES打开,里面有大量可以直接运行的示例,比看文档快得多
6.4 保留编译环境快照
最后分享一个我个人的习惯:每次编译完Ceres,我会把整个build目录连同源码、安装包导出,一起存到网盘或者本地归档。这样即使半年后再回来做项目,也不用重新研究编译选项,直接把库和配置脚本用上就行。这个习惯帮我在很多项目迁移时省下了大量时间。如果你手头还有老项目在用旧版Ceres,强烈建议照这个方式做一版固定快照,后续维护会舒服得多。
本文还有配套的精品资源,点击获取