资讯详情

资讯详情

Windows下VS2019配置Sophus库:从编译到SLAM工程集成实战

1. 为什么要在Windows上折腾Sophus库如果你正在做视觉SLAM、视觉里程计或者任何跟李群李代数相关的项目Sophus这个名字你一定不陌生。它几乎是C环境下处理SO(3)、SE(3)这些旋转和平移变换的标配库高博的《视觉SLAM十四讲》里也是拿它当主力工具在用。但问题来了这本书和大多数教程默认你是在Linux环境下开发apt装一下、CMake编一下就完事了。一旦你切换到Windows平台尤其是用Visual Studio做开发事情就变得没那么顺滑了。我这次接的一个项目整个工程框架都是基于VS2019搭建的依赖的第三方库包括Eigen、OpenCV、Ceres等等全都是Windows下编译好的版本。Sophus作为一个header-only为主的库按理说应该很好集成但实际操作下来从CMake配置到VS工程属性设置中间踩了不少坑。这篇文章就是把我整个配置过程完整记录下来包括为什么选某个方案、每一步背后的逻辑是什么、遇到问题怎么排查争取让你少走弯路。Sophus本质上是一个C模板库主要实现了李群和李代数之间的转换、指数映射、对数映射、伴随矩阵等操作。它在SLAM里的核心价值在于优化过程中我们通常对李代数求导但状态更新又需要在李群上进行Sophus帮你把这两者之间的桥梁搭好了。没有它你就得自己手写SO(3)的exp/log容易出错不说代码复用性也差。那为什么不用其他方案呢比如直接调Ceres里面的旋转参数化可以但Ceres的旋转参数化是跟它的优化框架绑定的你没法单独拿出来在非优化场景下用。Sophus的好处是它足够独立只依赖Eigen你可以在任何需要李群运算的地方引入它。这也是我最终选择Sophus的原因。这篇文章适合谁看如果你是在Windows上用VS2019做C开发项目里需要处理三维旋转、刚体变换或者你在跟着SLAM十四讲学习但不想装双系统那这篇内容应该能帮到你。我会从环境准备开始一步步讲到Sophus的编译、配置、以及在VS工程里的实际使用最后附上我遇到过的几个典型问题和解决方法。2. 环境准备与工具链选型2.1 软件版本确认与下载在动手之前先把需要的东西列清楚。我这次用的环境是Windows 10 64位专业版VS2019社区版版本号16.11.x。Sophus我选的是较新的稳定版本从代码托管平台拉取源码编译。Eigen用的是3.3.9这个版本跟Sophus的兼容性比较好太新的版本有时候反而会有API变动导致编译报错。VS2019自带的CMake工具是这次配置的关键。很多人不知道VS2019安装的时候可以勾选“使用C的CMake工具”这个组件勾上之后你不需要单独装CMakeVS里面直接就能打开CMake工程、生成缓存、编译。这个工具链的好处是它跟VS的调试器、IntelliSense是打通的你可以在VS里面直接对CMake工程下断点调试体验比命令行CMakeVS生成器要顺畅得多。具体需要下载的东西Eigen 3.3.9从官网下载压缩包解压到某个目录比如D:\Libs\eigen-3.3.9。Eigen是header-only的不需要编译解压即用。Sophus源码从代码托管平台克隆或者下载zip包放到D:\Libs\Sophus。VS2019确保安装了“使用C的桌面开发”和“使用C的CMake工具”这两个工作负载。注意Eigen的路径里不要有中文和空格Sophus的路径也一样。Windows下很多编译问题都是路径引起的这个坑我踩过不止一次。2.2 为什么选择VS2019自带的CMake而不是独立安装这里多说一句工具选型的问题。你完全可以在Windows上单独装一个CMake然后用命令行生成VS的sln文件再用VS打开编译。这个方式也没问题但有几个不方便的地方一是版本管理麻烦你装的CMake版本可能跟VS内置的不一致二是每次改CMakeLists.txt之后要手动重新生成三是调试的时候断点信息有时候对不上。VS2019自带的CMake工具解决了这些问题。它会在你打开包含CMakeLists.txt的文件夹时自动配置你修改CMakeLists.txt后它会自动重新生成缓存。而且它支持CMakePresets.json你可以把不同的配置Debug/Release、不同的编译器选项写成preset切换起来很方便。当然这个工具也不是没有缺点。它的配置界面相对简陋有些高级的CMake选项在GUI里找不到还是得手动编辑CMakeSettings.json。另外它的错误提示有时候不够详细遇到配置失败的时候需要看输出窗口的完整日志才能定位问题。但总体来说对于Sophus这种规模的库用VS自带的CMake工具是够用的而且体验比命令行好很多。2.3 目录结构规划在开始之前建议把目录结构规划好。我习惯把第三方库统一放在一个目录下比如D:\Libs然后每个库一个子目录。这样后续在CMake里引用的时候路径清晰不容易搞混。我的目录结构是这样的D:\Libs\ ├── eigen-3.3.9\ │ ├── Eigen\ │ ├── unsupported\ │ └── ... ├── Sophus\ │ ├── CMakeLists.txt │ ├── sophus\ │ └── ... └── build\ └── Sophus\Sophus的源码目录和编译输出目录分开这样源码目录保持干净编译产物都在build目录下删起来也方便。这个习惯是从Linux下沿用过来的在Windows上同样适用。3. Sophus库的编译与配置3.1 用VS2019打开Sophus工程Sophus的源码根目录下有一个CMakeLists.txtVS2019可以直接识别。操作步骤是打开VS2019选择“文件”-“打开”-“CMake”然后定位到Sophus的源码目录选中CMakeLists.txt打开。VS会自动开始配置。第一次配置的时候它会让你选择工具链默认就是Visual Studio 2019的MSVC编译器直接确认就行。配置过程会在输出窗口显示进度如果一切顺利你会看到“CMake 生成完毕”的提示。但实际情况往往不会这么顺利。最常见的问题是Sophus在配置阶段会去找Eigen如果找不到就会报错。Sophus的CMakeLists.txt里通常有类似find_package(Eigen3 REQUIRED)的语句你需要告诉CMake去哪里找Eigen。3.2 配置Eigen路径的几种方式告诉CMake Eigen在哪有几种做法我逐个说一下。第一种是设置环境变量。你可以在系统环境变量里加一个Eigen3_DIR指向Eigen的share/eigen3/cmake目录。但Eigen 3.3.9的压缩包解压后不一定有这个目录因为它是源码包不是安装包。所以这种方式有时候行不通。第二种是修改Sophus的CMakeLists.txt在find_package之前手动设置Eigen3_DIR或者直接用include_directories把Eigen的路径加进去。这种方式最直接但缺点是修改了第三方库的源码后续更新Sophus的时候可能会冲突。第三种是用CMake的命令行参数或者CMakeSettings.json来传变量。VS2019的CMake工具支持在CMakeSettings.json里配置cacheVariables你可以把Eigen的路径作为变量传进去。这种方式不修改源码配置也集中管理是我比较推荐的做法。具体操作在VS里右键CMakeLists.txt选择“CMake设置”这会打开CMakeSettings.json。在configurations数组里找到你的配置添加cacheVariables字段{ name: x64-Debug, generator: Ninja, configurationType: Debug, buildRoot: ${projectDir}\\out\\build\\${name}, installRoot: ${projectDir}\\out\\install\\${name}, cmakeCommandArgs: , buildCommandArgs: , ctestCommandArgs: , variables: [ { name: Eigen3_DIR, value: D:/Libs/eigen-3.3.9/share/eigen3/cmake, type: PATH } ] }但如果你的Eigen包里没有cmake配置文件那就得换一种方式。可以直接在CMakeLists.txt里加set(EIGEN3_INCLUDE_DIR D:/Libs/eigen-3.3.9) include_directories(${EIGEN3_INCLUDE_DIR})然后把find_package(Eigen3 REQUIRED)注释掉。这种方式虽然粗暴但确实有效。我这次就是用这个方式过的因为下载的Eigen压缩包里确实没有cmake配置目录。提示路径里的斜杠用正斜杠/或者双反斜杠\\不要用单反斜杠\否则CMake会把它当转义字符处理。3.3 编译Sophus的静态库Sophus默认是编译成静态库的。在VS的CMake工具里配置完成后你可以直接在“生成”菜单里选择“全部生成”或者在解决方案资源管理器里右键CMakeLists.txt选择“生成”。编译过程中可能会遇到几个警告比如C4819文件包含不能在当前代码页中表示的字符这个是因为Sophus源码里有一些非ASCII字符不影响功能可以忽略。如果遇到C4996不安全函数的警告可以在CMakeLists.txt里加add_compile_definitions(_CRT_SECURE_NO_WARNINGS)来屏蔽。编译完成后你会在build目录下得到sophus.lib文件。这个文件就是你后续在VS工程里需要链接的库。注意Debug和Release配置下生成的lib文件是不同的不要混用。3.4 安装Sophus到指定目录如果你不想每次都在源码目录里找头文件和lib可以用CMake的install功能把Sophus安装到一个统一的目录。在VS的CMake工具里右键CMakeLists.txt选择“安装”-“Sophus”它会执行cmake --install命令把头文件、lib文件、cmake配置文件都复制到你指定的install目录。安装完成后你的install目录结构大概是这样的D:\Libs\Sophus-install\ ├── include\ │ └── sophus\ │ ├── se3.h │ ├── so3.h │ └── ... ├── lib\ │ └── sophus.lib └── share\ └── sophus\ └── cmake\有了这个安装目录后续在其他工程里引用Sophus就方便多了直接用find_package(Sophus REQUIRED)就能找到。4. 在VS2019工程中使用Sophus4.1 创建测试工程为了验证Sophus配置是否成功我建议先建一个简单的测试工程。在VS2019里新建一个C控制台应用然后配置项目属性。需要配置的地方有C/C - 常规 - 附加包含目录添加Sophus的include目录和Eigen的根目录。比如D:\Libs\Sophus-install\include和D:\Libs\eigen-3.3.9。链接器 - 常规 - 附加库目录添加Sophus的lib目录比如D:\Libs\Sophus-install\lib。链接器 - 输入 - 附加依赖项添加sophus.lib。C/C - 语言 - C语言标准设置为ISO C17或更高。Sophus的新版本用了一些C17的特性标准太低会编译报错。配置完成后写一段测试代码#include iostream #include sophus/so3.h #include sophus/se3.h int main() { // 绕Z轴旋转90度 Sophus::SO3d rot_z Sophus::SO3d::rotZ(M_PI / 2.0); std::cout Rotation matrix:\n rot_z.matrix() std::endl; // 李代数 Eigen::Vector3d so3 rot_z.log(); std::cout so3: so3.transpose() std::endl; // SE3 Sophus::SE3d pose(rot_z, Eigen::Vector3d(1, 2, 3)); std::cout SE3 matrix:\n pose.matrix() std::endl; return 0; }如果编译通过并且运行结果正确说明Sophus配置成功了。4.2 用CMake管理自己的工程如果你的工程本身也是用CMake管理的那配置起来更简单。在CMakeLists.txt里加find_package(Sophus REQUIRED) find_package(Eigen3 REQUIRED) add_executable(my_app main.cpp) target_link_libraries(my_app Sophus::Sophus Eigen3::Eigen)前提是Sophus和Eigen都正确安装了并且CMake能找到它们的配置文件。如果找不到可以通过CMAKE_PREFIX_PATH变量指定搜索路径。4.3 常见编译错误与解决即使配置看起来没问题编译的时候还是可能报错。我遇到过的几个典型错误错误1Cannot open include file: sophus/so3.h这个通常是附加包含目录没设对。检查一下你的包含目录是D:\Libs\Sophus-install\include还是D:\Libs\Sophus-install\include\sophus。应该是前者因为代码里写的是#include sophus/so3.h编译器会在包含目录下找sophus子目录。错误2unresolved external symbol链接错误说明lib文件没链接上。检查附加依赖项里有没有sophus.lib附加库目录对不对以及Debug/Release配置是否匹配。Debug工程链接Release的lib是会报错的。错误3Eigen相关的模板错误这种错误信息通常很长看起来吓人但核心问题往往是Eigen版本不兼容或者C标准太低。先检查C标准是不是C17然后确认Eigen版本是不是3.3以上。错误4C4996或C4819警告这些是警告不是错误但如果你开了“将警告视为错误”就会导致编译失败。可以在项目属性里关掉这个选项或者在代码里加#pragma warning(disable:4996)。5. 实操心得与避坑指南5.1 路径问题的排查思路Windows下配置第三方库十有八九是路径问题。我的排查思路是这样的先看错误信息里提到的文件路径是什么然后去文件资源管理器里确认这个路径下到底有没有这个文件。很多时候你以为路径是对的但实际上多了一层目录或者少了一层。比如Eigen解压后可能是eigen-3.3.9\eigen-3.3.9\Eigen这种嵌套结构你如果直接指向外层目录编译器就找不到Eigen/Core。所以解压后先看一眼目录结构确认Eigen文件夹在哪一层。5.2 Debug和Release的区分VS里Debug和Release配置是分开的第三方库的lib文件也是分开的。Sophus编译的时候如果你只编译了Debug版本那Release工程就链接不上。解决办法是要么两个版本都编译要么统一用一个配置。我个人的习惯是开发阶段统一用Debug发布的时候再切Release重新编译所有依赖。这样虽然麻烦一点但不容易出错。5.3 关于Sophus的模板实例化Sophus大量使用了模板SO3d、SE3d这些类型都是模板实例化出来的。模板的一个特点是如果你只用了SO3d那SO3f相关的代码不会被实例化也就不会产生链接需求。但如果你在头文件里声明了某个模板函数在cpp文件里定义了它然后又在另一个cpp文件里调用就可能出现链接错误。解决办法是尽量把Sophus相关的代码都放在头文件里或者显式实例化你需要的模板类型。Sophus本身已经做了显式实例化所以正常使用不会遇到这个问题但如果你自己封装了一些基于Sophus的模板类就要注意这一点。5.4 性能相关的注意事项Sophus的运算本身是轻量级的但如果你在性能敏感的代码里频繁调用log()和exp()还是要注意一下。这两个操作涉及三角函数和矩阵运算比普通的加减乘除要慢。在优化迭代的内循环里能复用中间结果就复用不要每次都重新算。另外Sophus的SO3内部存储的是四元数SE3内部存储的是四元数加平移向量。如果你需要频繁访问旋转矩阵每次调用matrix()都会从四元数转换一次这个转换是有开销的。如果同一个旋转矩阵要用多次先存到一个局部变量里。5.5 与其他库的兼容性Sophus只依赖Eigen所以跟其他库的兼容性主要看Eigen的版本。我遇到过Ceres和Sophus同时用的时候因为Ceres内部也依赖Eigen如果两个库用的Eigen版本不一致可能会出现ODR单一定义规则问题。解决办法是确保整个工程用同一个Eigen版本不要混用。OpenCV的cv::Mat和Eigen的Matrix之间的转换也要注意Sophus的输入输出都是Eigen类型如果你从OpenCV拿到的数据需要先转成Eigen再传给Sophus。6. 常见问题速查与排查技巧6.1 配置阶段问题问题现象可能原因解决方法CMake配置时报找不到EigenEigen路径未设置或设置错误在CMakeSettings.json里设置Eigen3_DIR或直接在CMakeLists.txt里include_directoriesCMake配置时报找不到C编译器VS2019的C工作负载未安装打开VS Installer确认“使用C的桌面开发”已勾选配置过程卡住不动网络问题导致CMake下载依赖检查网络或者手动下载依赖放到指定目录6.2 编译阶段问题问题现象可能原因解决方法编译报大量模板错误C标准太低项目属性里设置C17或更高编译报C4819警告源码包含非ASCII字符忽略或在项目属性里关闭“警告视为错误”编译报C4996警告使用了不安全函数添加_CRT_SECURE_NO_WARNINGS宏定义编译成功但链接失败lib文件未链接或版本不匹配检查附加依赖项和附加库目录确认Debug/Release一致6.3 运行阶段问题问题现象可能原因解决方法程序启动时报缺少DLL运行时库配置不一致确保所有依赖库的运行时库设置一致MT/MD计算结果异常四元数未归一化Sophus的SO3构造时会自动归一化但如果手动修改内部数据需要自己归一化性能不达预期频繁的log/exp调用缓存中间结果减少不必要的转换6.4 几个容易被忽略的细节第一个细节是Sophus的命名空间。Sophus的类都在Sophus命名空间下但有些版本里SO3和SE3是在全局命名空间的。如果你编译报“找不到标识符”先确认一下命名空间对不对。第二个细节是M_PI这个宏。在MSVC里M_PI默认是不定义的需要加#define _USE_MATH_DEFINES而且要在#include cmath之前加。这个坑我在测试代码里就遇到了编译报M_PI未定义。第三个细节是Sophus的版本差异。不同版本的Sophus API有变化比如老版本用Sophus::SO3double新版本用Sophus::SO3d。你看的教程可能是老版本的实际用的时候要对一下版本。第四个细节是CMake的生成器选择。VS2019自带的CMake工具默认用Ninja生成器但如果你手动用命令行CMake默认可能是Visual Studio生成器。两种生成器的输出目录结构不一样不要混用。7. 从配置到使用的完整流程回顾整个流程走下来核心步骤其实就四步准备Eigen和Sophus源码、用VS2019的CMake工具配置Sophus、编译生成lib文件、在自己的工程里引用Sophus的头文件和lib。但每一步都有细节需要注意。Eigen的路径要确认到Eigen文件夹的上一层Sophus的CMake配置要正确指向Eigen编译时的C标准要够高工程引用时的包含目录和库目录要对应。我个人的建议是第一次配置的时候不要急着往大工程里集成先建一个最小的测试工程把Sophus的基本功能跑通。确认没问题了再往正式工程里加。这样出问题的时候排查范围小容易定位。另外如果你后续要升级Sophus版本记得把旧的lib文件删掉重新编译不要直接覆盖。MSVC有时候会缓存旧的符号信息导致链接到错误的版本。最后分享一个我常用的调试技巧如果链接错误的信息里提到了某个Sophus的符号你可以用dumpbin /symbols sophus.lib命令查看lib文件里到底导出了哪些符号对比一下你的代码需要的符号在不在里面。这个命令在VS的开发人员命令提示符里可以直接用排查链接问题很有效。这个配置方案我在多个项目里用过稳定性没问题。如果你在操作过程中遇到其他问题欢迎交流讨论。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →