VS2013环境下Ceres库x64静态编译与依赖配置详解
简介这是面向 Visual Studio 2013 x64 平台的 Ceres Solver 预编译资源包适合需要在不升级开发环境的前提下集成 Ceres 的 C 开发者用于解决大型非线性最小二乘与数值优化问题。压缩包共约 2000 个文件近 349MB包含大量 sln/vcxproj 工程文件、cpp/c/h 源码与 hpp 头文件、cmake 配置脚本以及编译生成的 obj/lib/dll/exp 等库文件还带有 png 文档图、txt 说明与示例数据可满足构建、链接和二次开发需求。已有 393 人学习下载。借助这套资源可免去自行梳理 Eigen、glog/gflags 等依赖与 CMake 配置的繁琐过程直接获得可在 VS2013 x64 下使用的 Ceres 库和配套工程骨架便于快速开展基于自动微分、Levenberg-Marquardt 等算法的优化应用并参考其源码结构理解实现细节。1. VS2013与x64一个看着简单却让人卡壳的Ceres库编译问题在 Windows 上用 VS2013 编 Ceres 库难点从来不在 Ceres 本身而在它背后那一整条依赖链的版本匹配上。Ceres 是 Google 维护的非线性最小二乘求解库SLAM、三维重建、相机标定这类场景里凡是需要优化位姿或参数的工程几乎都会撞到它。但官方对 Windows 的支持长期处于「能编但没人替你踩坑」的状态尤其是 VS2013 这种老工具链C11 支持不完整新版本事一个都吃不上。这篇文章写给两类人一是还在维护老视觉工程、被锁在 VS2013 上的开发者二是想在 Windows x64 上拿到一个能跑的 Ceres 静态库、不想被 CMake 折腾到半夜的新手。下文所有路径、参数、坑都按我实际编译时的做法来写跟着做就能出库。至于为什么每个选项要这么选下面拆开讲。2. 编译前的依赖选型VS2013 到底能用哪个版本的 Ceres2.1 Ceres 不是单文件依赖链都是谁Ceres 本身是源码与头文件组成的库但它的 CMake 构建会检查一系列第三方库缺了任何一个都过不了配置。跑在 VS2013 上这些依赖的版本选择直接决定你是半小时编译完还是三天后还在改报错。常见依赖有四块我按需要程度排个序Eigen矩阵与线性代数模板库Ceres 的数值计算底座header-only不需要编译出单独的 .lib但它的版本会影响 Ceres 的 API 和编译器要求。glogGoogle 的日志库Ceres 在运行时打印优化信息和报错都靠它必须预先编译成 Windows 库。gflags命令行参数解析库Ceres 的 examples 和部分测试用但核心库不强制依赖完全可以关掉。SuiteSparse / LAPACK / BLAS稀疏矩阵求解器Windows 上编译成本极高能用 Eigen 自带的稀疏求解替代直接关掉。这里有个常见的误解有人以为只要把 Ceres 源码拉下来、用 CMake 一键生成工程就行。实际上一键生成的后果是 CMake 疯狂报「Could NOT find GLOG」这类错误。Ceres 的 Windows 编译本质上是先解决 Eigen、glog、gflags 三个前置库的版本与路径再去编 Ceres 本身。2.2 版本红线C11 与 Eigen 的兼容边界VS2013 对 C11 的支持是「大部分支持小部分拒绝」而 Ceres 主分支和近年发版对 C14 有硬性要求。第一条红线就是版本Ceres 1.14.0 是最后一个默认 C11 的正式发版之后的版本在 VS2013 下编译会在源码里直接撞上 constexpr 等 C14 特性报错位置毫无规律。所以版本就锁 1.14.0不要用 master也不要图新。Eigen 的版本选择同样有讲究。Ceres 1.14 的 CMake 检查要求 Eigen 最低 3.1但 3.3.x 这一代对 MSVC 的配置做了更新官方只在 VS2015 以上的工具链上做过验证在 VS2013 上偶尔会触发预处理断言。我一般用 Eigen 3.2.10这代与 VS2013 配合最稳API 也完全满足 Ceres 1.14 的需求。另一个关键点是Eigen 是 header-only所谓「安装」就是把解压目录放到一个固定位置CMake 里指定路径即可不涉及编译。glog 与 gflags 的选择是一条容易翻车的暗线glog 0.3.5 配 gflags 2.2.2 是那个时期 Windows 下最常用的组合glog 在 Windows 上编译需要先有 gflags静态链接时如果你在 CMake 里关掉了 examples 和 testsCeres 构建本来不需要 gflags但 glog 的静态库仍然需要 gflags 的符号。所以两个都得准备一个都别省。2.3 依赖准备Eigen / glog / gflags 的获取与放置我一般会在 D 盘建一个third_party目录把三个依赖都放进去路径固定下来方便 CMake 反复引用。目录结构大致是D:\third_party ├─ eigen-3.2.10 │ └─ Eigen ├─ gflags-2.2.2 │ ├─ build │ └─ include └─ glog-0.3.5 ├─ build └─ includeEigen 只需要把压缩包解压然后把eigen-3.2.10这一层放到固定位置注意 CMake 里要指定到包含Eigen子目录的上一级否则#include Eigen/Core会找不到文件。gflags 和 glog 需要先用 CMake 各自生成 VS2013 工程再编译这一步有个先后的隐藏关系要先编 gflags再编 glog因为 glog 的静态库要链接 gflags 的符号。两个库都建议编成静态库Release 与 Debug 各编一次动态库在后续集成时多一个 DLL 分发问题静态库把事一次做完。编译参数上gflags 用默认配置即可glog 在 CMake 里要关掉BUILD_SHARED_LIBS并把GLOG_BUILD_TESTS设为 OFF。编译完静态库文件路径类似gflags\build\Release\gflags_static.lib与glog\build\Release\libglog_static.lib记住这两个路径后面要给 Ceres 的 CMake 用。提示glog 在 Windows 上编译时如果 CMake 提示找不到 gflags先确认 gflags 已经编译完成并在 glog 的 CMake 配置里通过CMAKE_PREFIX_PATH指到 gflags 的 build 目录。这个步骤跳过的后果是 glog 编出来一个半残的库Ceres 链接时再报一堆 LNK 错误。3. 在 VS2013 下编译 x64 CeresCMake 生成与 msbuild 构建3.1 用 CMake 生成 VS2013 x64 工程生成器与依赖参数依赖齐了以后Ceres 本身的编译反而是最顺的。源码直接取 1.14.0 的 tag解压到D:\third_party\ceres-solver-1.14.0。然后建一个build_vs2013_x64目录用命令行执行 CMake 配置。这里的关键是生成器必须指定到 VS2013 的 64 位版本CMake 里对应的生成器名称是Visual Studio 12 2013 Win64。很多人在这步习惯性选Visual Studio 12 2013结果生成出来的是 Win32 工程后面全乱。cd D:\third_party\ceres-solver-1.14.0 mkdir build_vs2013_x64 cd build_vs2013_x64 cmake .. -G Visual Studio 12 2013 Win64 ^ -DCMAKE_BUILD_TYPERelease ^ -DEIGEN_INCLUDE_DIRD:/third_party/eigen-3.2.10 ^ -DGLOG_INCLUDE_DIRD:/third_party/glog-0.3.5/build/include ^ -DGLOG_LIBRARYD:/third_party/glog-0.3.5/build/Release/libglog_static.lib ^ -Dgflags_DIRD:/third_party/gflags-2.2.2/build ^ -DBUILD_SHARED_LIBSOFF ^ -DBUILD_TESTINGOFF ^ -DBUILD_EXAMPLESOFF ^ -DSUITESPARSEOFF ^ -DLAPACKOFF这段命令的逻辑说明-G指定生成 VS2013 的 x64 工程-DCMAKE_BUILD_TYPE虽然多配置生成器下不是硬性必须但写上能让 IDE 默认落在 ReleaseEigen、glog、gflags 三个路径必须显式给出尤其是GLOG_LIBRARY不指定的话 CMake 的 FindGLOG 模块在 Windows 上基本找不到BUILD_SHARED_LIBSOFF强制 Ceres 编成静态库BUILD_TESTING与BUILD_EXAMPLES关掉可以省掉大量编译时间同时让 gflags 依赖不被引进来SUITESPARSE与LAPACK关掉是避免在 Windows 上触发稀疏求解器的额外依赖检查Ceres 会自动退回 Eigen 的稀疏求解路径。3.2 编译与产出确认 .lib 真的生成了CMake 配置成功后build_vs2013_x64下会生成ceres.sln。可以直接打开 Visual Studio 编译也可以用命令行 msbuild我习惯用 msbuild方便后续写构建脚本msbuild ceres.sln /p:ConfigurationRelease /p:Platformx64 /m/m参数允许多进程编译能明显缩短时间。Ceres 1.14 在 VS2013 下源码编译大约需要几分钟取决于机器核心数。编译完成后静态库的默认输出路径是build_vs2013_x64\lib\Release\ceres.lib。注意 Ceres 的库文件名不会带_static或_x64后缀这跟 glog 的命名习惯不同集成时容易搞混。Debug 配置同理再跑一次msbuild ceres.sln /p:ConfigurationDebug /p:Platformx64 /m产出在build_vs2013_x64\lib\Debug\ceres-debug.lib。两个配置都要编后面链接老工程时Debug 与 Release 必须各用各的库混着用会出现运行库级别不匹配的问题这个坑下一章细说。注意如果 CMake 配置时提示找不到编译器或生成器先确认 CMake 版本对 VS2013 生成器还有支持。较新的 CMake 已经移除了对 VS2013 的生成器定义我一般用 CMake 3.x 中期版本跑完这条命令不会报错。如果你手里的 CMake 太新建议退回 CMake 3.12 到 3.16 之间的版本。3.3 编译不过时的排查顺序编译过程中最常见的报错是fatal error C1083: Cannot open include file: glog/logging.h。这个错误几乎都是GLOG_INCLUDE_DIR指错层级导致的。glog 的头文件目录需要指向包含glog这个子目录的那一层而不是直接指到glog目录本身。另外一类报错集中在Eigen/src/Core/...里如果报的是 Eigen 内部的模板错误基本可以断定是 Eigen 版本太新换回 3.2.10 即可。还有一类 Link 错误出现在生成ceres.lib时提示glog_static.lib无法解析的外部符号这类通常是 gflags 没有先编译或者 glog 静态库编成了动态库回第 2 章把 gflags 与 glog 的构建顺序检查一遍。4. 集成与排查把库接进老工程时的五个经典坑4.1 把 Ceres 接进工程include 与 lib 的配置清单拿到ceres.lib与ceres-debug.lib之后集成这一步本身不复杂但老工程的配置项多任何一个不匹配都会在链接时爆炸。我在某个视觉定位工程里是这样配的包含目录ceres-solver-1.14.0\include、eigen-3.2.10、glog-0.3.5\build\include、gflags-2.2.2\build\include库目录ceres-solver-1.14.0\build_vs2013_x64\lib\ReleaseDebug 配置对应 Debug 目录附加依赖项ceres.lib、libglog_static.lib、gflags_static.lib链接顺序上Ceres 在前glog 与 gflags 在后。如果工程里还用了其他第三方库出现符号冲突时优先检查是不是链接了多个版本的 gflags。这里有一个要提前加上的预处理定义GLOG_NO_ABBREVIATED_SEVERITIES这个宏让 glog 不用ERROR、WARNING这种短名字而是用GLOG_ERROR全称。Windows 头文件里已经有ERROR宏不关掉的话Ceres 的头文件一展开就是满屏重定义报错。4.2 编译期报错宏冲突与头文件路径现象一编译工程时出现C1189: #error: ERROR macro already defined有时是警告有时直接报错。原因是 glog 的logging.h与 Windows 的windows.h都定义了ERROR、DEBUG等宏glog 在 0.3.5 里提供了GLOG_NO_ABBREVIATED_SEVERITIES开关但不会自动开启。解决办法是在工程预处理定义里加GLOG_NO_ABBREVIATED_SEVERITIES同时建议加NOMINMAX避免min/max宏与 Eigen 的模板代码冲突。这两项在 VS2013 环境下几乎是必加的不加的后果是编译到 Eigen 的DenseBase相关头文件时突然冒出几十行min: macro definition错误。现象二Cannot open include file: Eigen/Core。这个报错说明EIGEN_INCLUDE_DIR指错了层级。注意 CMake 配置时指向的是包含Eigen子目录的上级目录工程里的包含目录也要保持一致。Eigen 解压出来有两层路径eigen-3.2.10下面是Eigen目录Eigen目录下面才是Core、Geometry这些子目录。包含目录必须指到eigen-3.2.10这一层。4.3 链接期报错运行库与迭代器级别不匹配链接期最常见的两个 LNK 错误都和 VS2013 的工程配置习惯有关。现象一LNK2038: mismatch detected for RuntimeLibrary: value MD_DynamicRelease doesnt match value MT_StaticRelease。原因很直接Ceres 编译时用的是/MD动态 CRT而你的老工程可能开了/MT静态 CRT或者反过来。解决方法是让 Ceres 的编译配置和调用工程的运行库保持一致。我一般统一用/MD也就是 CMake 不额外设置CMAKE_MSVC_RUNTIME_LIBRARYVS2013 默认 Release 就是/MD调用工程也保持默认即可。如果工程必须用/MT那 Ceres 的 CMake 配置里也要通过CMAKE_CXX_FLAGS_RELEASE/MT重新编译一套库。现象二LNK2038: mismatch detected for _ITERATOR_DEBUG_LEVEL。这是 Debug 与 Release 混编的典型症状。VS2013 的 Debug 默认_ITERATOR_DEBUG_LEVEL2Release 默认 0两者不能混合链接。解决办法就是在集成时严格按配置选库Debug 工程链ceres-debug.libRelease 工程链ceres.lib。这里有个容易忽视的细节glog 与 gflags 的静态库在 Debug 和 Release 下文件名可能不同比如带_debug后缀需要一并区分不要图省事全链 Release 版否则 Debug 编译会报同样的_ITERATOR_DEBUG_LEVEL错误。4.4 运行期崩溃静态库依赖与初始化顺序编译链接都过了程序一跑就崩这一类问题最让人头疼。现象Debug 下启动就崩溃崩溃点在glog的初始化代码里或者是访问违例在Eigen::internal::aligned_malloc附近。前者的原因通常是 glog 被编成了动态库而 DLL 没有放到 exe 所在目录或系统 PATH 里。解决方法是把 glog 也编成静态库并在预处理定义里加上GLOG_STATIC_LIBRARY确保链接进去的是静态符号。后者一般是 Eigen 在 x64 下的内存对齐问题VS2013 的 x64 默认对齐策略与 Eigen 3.2 的EIGEN_MAX_ALIGN_BYTES有关如果工程里开了/Zp或自定义包对齐需要把 Ceres 相关源文件的默认对齐恢复一般保持工程默认 8 字节对齐即可。4.5 最容易忽略的坑64 位与 32 位库混用最后一个坑属于「看起来对实际错」的类型。现象链接时提示LNK1112: module machine type x64 conflicts with target machine type x86。原因是 Ceres 的库确实编出来了但编的是 x64而工程平台仍停留在 Win32。VS2013 的工程配置里平台下拉框默认可能是Win32哪怕你的工程跑在 64 位系统上。解决方法是把工程平台切换到x64并确认整个解决方案里所有项目都是 x64特别是 gflags 和 glog 这两个静态库它们的生成平台要与 Ceres 一致。x64 混 x86 的静态库在链接阶段会报错但如果两个库用不同的调用约定有时报错会被推迟到运行期更难排查。5. 编译之后的验证用最小非线性最小二乘问题确认库可用库编完、工程配置完先别急着把大段优化代码搬进来。写一个最小的 Ceres 求解程序跑通一次迭代确认「头文件正确、链接正确、运行正确」三件事。下面这个例子求解的是f(x) 10 - x^2的最小值点理论上最优解在远大于 0 的位置实际求解结果会收敛到一个使残差平方和最小的x用这个来验证库的基本功能。#include ceres/ceres.h #include glog/logging.h #include iostream // 残差块计算 f(x) 10 - x^2 struct SimpleCostFunctor { template typename T bool operator()(const T* const x, T* residual) const { residual[0] T(10.0) - x[0] * x[0]; return true; } }; int main(int argc, char** argv) { google::InitGoogleLogging(argv[0]); double x 2.0; ceres::Problem problem; ceres::CostFunction* cost_function new ceres::AutoDiffCostFunctionSimpleCostFunctor, 1, 1( new SimpleCostFunctor()); 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 初始 x 2.0\n; std::cout 求解后 x x \n; std::cout 最终残差 summary.final_cost \n; return 0; }这段代码的验证逻辑分三层第一层AutoDiffCostFunction确认自动求导的头文件和模板实例化正常如果库的 C11 支持有问题编译期就过不去第二层DENSE_QR确认稠密线性求解器可运行这依赖 Eigen 的矩阵运算路径第三层final_cost能输出一个接近 0 的残差说明求解迭代真的收敛了而不只是库能链接。运行这个 demo 时顺手确认两件事一是输出结果里的迭代信息能正常打印说明 glog 的日志系统在运行期没问题二是在任务管理器里看进程是 x64 还是 x86确认没有隐式地把 32 位跑起来。这个最小程序我建议留在工程里以后每次换机器、换编译器版本、换依赖库版本都先跑一遍它再谈集成业务代码。我自己的习惯是把编译命令、依赖路径、预处理定义全部写进一个构建脚本不依赖人工记住 CMake 参数。Ceres 在 VS2013 下的编译并不难难的是版本组合的确定性——一旦锁定了版本和参数这套流程在任何一台机器上都能稳定复现。希望帮到你。本文还有配套的精品资源点击获取