1. 为什么说OpenMVS源码编译最大的坑不是源码,而是依赖环境
OpenMVS这个名字,做三维重建的人应该都不陌生。它是目前开源社区里效果最完整的多视角稠密重建管线之一,输入一组照片,输出稠密点云、网格模型,还能帮你做纹理映射,一条流水线全包了。很多人在网上看到它的效果图,兴冲冲clone了仓库,然后卡在了编译这一步。
我最初编译OpenMVS的时候也犯过同样的错误——以为重点在源码本身。真正动手之后才发现,OpenMVS的代码结构其实很清爽,CMakeLists写得也算规矩,真正折磨人的是它那一大串外部依赖:Eigen、OpenCV、Boost、Ceres Solver、CGAL,还有可选的CUDA。这些库各自有版本要求,版本之间又互相牵制,稍有不慎就给你编出一堆莫名其妙的链接错误。
这篇文章我就把整个OpenMVS源码编译的完整过程拆开讲一遍。从依赖选型、CMake配置、分平台编译实操,到常见的报错排查链路,全部走一遍。无论你是Windows用户还是Linux/macOS用户,只要照着这一篇走,都能少踩几个坑。
先说一个总体的认识:OpenMVS本身的编译,说白了就是一个标准的CMake项目——cmake生成构建文件,然后make或者MSBuild编译。难点全在依赖库的版本匹配和查找路径上。理解了这个定位,后面遇到问题就知道往哪个方向排查了。
2. 动手之前先理清依赖库的版本选型策略
这里我先说结论:OpenMVS对依赖库的版本要求,从来都不是“越新越好”。很多人编译失败,就是因为用了太新的OpenCV或者太新的CGAL,结果跟OpenMVS源码里的接口对不上。
2.1 核心依赖清单与版本建议
OpenMVS源码里有一个README.md,里面会列出推荐的依赖版本。但这些版本信息更新得不算勤快,实际操作时你需要结合系统环境自己判断。我梳理过一套在不同平台上都比较稳的版本组合,列在下面这张表里:
| 依赖库 | 建议版本 | 作用 | 注意事项 |
|---|---|---|---|
| Eigen | 3.4.x | 线性代数基础库 | 头文件库,不需要编译,但路径必须指对 |
| OpenCV | 3.4.x或4.5.x | 图像读写、特征提取 | 版本差异可能带来API兼容问题,4.x需要关注cv::命名空间变化 |
| Boost | 1.65~1.83 | 文件系统、线程、program_options | 版本过老过新都可能触发编译错误 |
| Ceres Solver | 2.0.x或2.1.x | 非线性最小二乘优化(BA核心) | 依赖Eigen和glog,版本不能太老 |
| CGAL | 5.4.x或5.5.x | 计算几何算法 | 新版CGAL要求C++17,注意编译标准一致 |
| VCGLib | 源码内置 | 网格处理基础库 | OpenMVS源码自带的vcglib子模块,无需单独装 |
| CUDA(可选) | 11.x或12.x | GPU加速稠密重建 | 不装也能编,但速度会差很多 |
2.2 版本之间的隐藏兼容链条
这几条版本之间是有连锁关系的,我建议你按这个逻辑来选,而不是一个个独立选:
- Cerès和Eigen是强绑定关系。Ceres编译时会检查Eigen版本,太新的Eigen可能触发Ceres的
EIGEN_VERSION判断报错。稳妥做法是选了Ceres 2.1.x,就配Eigen 3.4.x,不要动辄去试Eigen 3.8的实验分支。 - CGAL决定C++标准。CGAL 5.5之后的版本基本默认要求C++17,OpenMVS本身的代码也要用
-DCMAKE_CXX_STANDARD=17来对齐,否则编译时到处报std::filesystem找不到之类的错。 - Boost版本跟OpenCV也有联动。如果你用vcpkg或Homebrew统一管理依赖,务必保证它们用的是同一套编译工具链和运行时。混用MSVC的Debug/Release库是Windows下最常见的一类问题。
2.3 我的选型建议
如果你不想纠结,直接照抄我这套经过验证的组合:
Eigen 3.4.0 OpenCV 4.5.5 Boost 1.76 Ceres Solver 2.0.0 CGAL 5.4.4这套组合在Ubuntu 20.04、CentOS 7,以及Windows 10/11的VS2019/VS2022下我都试过,编译链路是通的。如果你坚持用更高版本,大概率会踩到某个接口改动,到时候排查的成本远高于你省下的那点编译时间。
提示:OpenMVS的
vcglib子模块是编译必需的。clone的时候记得用--recursive参数,否则后面CMake配置的时候会报找不到vcglib目录。这个参数很多人第一次都会忘。
3. CMake配置的完整逻辑:每个开关背后是什么
依赖装齐了,接下来就是CMake配置。这一环节的常见误区是“照着网上的命令抄一遍”。抄能跑通自然好,但一旦报错就容易抓瞎。我建议你先把OpenMVS的CMakeLists打开扫一遍,看清楚里面到底有哪些开关,每个开关对应什么作用。
3.1 OpenMVS的CMake选项全解析
OpenMVS的CMakeLists里对外暴露的主要选项有这几个:
-DCMAKE_BUILD_TYPE=Release # 构建类型,不要用Debug,性能和链接行为差别很大 -DMVS_DIR=... # OpenMVS源码根目录,一般多指生成时项目文件所在路径 -DMVS_USE_CUDA=ON/OFF # 是否启用CUDA加速,有N卡建议ON -DMVS_USE_OPENMP=ON/OFF # 多核CPU并行,默认ON -DMVS_USE_CGAL=ON/OFF # 是否启用CGAL相关功能,建议ON -DMVS_USE_BOOST=ON/OFF # Boost,默认ON -DMVS_USE_OPENCV=ON/OFF # OpenCV,默认ON -DMVS_USE_CERES=ON/OFF # Ceres,默认ON -CUDA_TOOLKIT_ROOT_DIR=... # CUDA安装路径,Windows下经常要手动指定 -CUDA_GENERATION=... # 根据显卡架构指定,如Kepler/Pascal等 -OpenCV_DIR=... # OpenCV的CMake配置目录 -CGAL_DIR=... # CGAL的CMake配置目录3.2 最重要的两个路径变量:MVS_DIR 和 MVS_DEPS_DIR
在OpenMVS的CMake逻辑里,厂商实际上希望你把外部依赖统一放在一个目录下,然后通过MVS_DIR和MVS_DEPS_DIR联合指引。不过我在实际使用中发现,大多数人直接用系统级别的包管理安装依赖,这种情况下更省事的方式是让CMake自动通过find_package去寻找,而不必刻意创建依赖目录。
关键是你得确保CMake能找到这些包的配置文件。以Windows为例,CMake会去注册表、环境变量和常用路径里搜索*Config.cmake。如果找不到,一款非常典型的情况是:
CMake Error at CMakeLists.txt:65 (find_package): By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project asked CMake to find a package configuration file provided by "OpenCV", but CMake did not find one.这种报错的本质是OpenCV_DIR没指对。OpenCV安装后会生成一个OpenCVConfig.cmake,在Windows上通常位于opencv/build/x64/vc15/lib(取决于版本),把这个路径通过-DOpenCV_DIR传给CMake,报错就没了。同样的逻辑适用于CGAL_DIR、Ceres_DIR。
3.3 我推荐的CMake配置命令
以Linux为例,假设依赖全部装在系统路径里:
cd OpenMVS mkdir -p build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_STANDARD=17 \ -DMVS_USE_CUDA=ON \ -DCUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda \ -DCUDA_GENERATION=Pascal \ -DOpenCV_DIR=/usr/lib/x86_64-linux-gnu/cmake/opencv4 \ -DCGAL_DIR=/usr/lib/x86_64-linux-gnu/cmake/CGAL \ -DCeres_DIR=/usr/local/lib/cmake/Ceres注意-DCMAKE_CXX_STANDARD=17这一步。如果你的CGAL是5.5以上版本,这一步缺了就等着编译报错吧。
Windows下用CMake GUI更直观,填完路径选项后点Configure,VS版本选对,再点Generate,然后打开生成的.sln文件编译。整个过程里最容易出问题的是VS版本和库的编译工具链不一致。换句话说:依赖库是MSVC 2019编译的,你却用VS2022的生成器去编OpenMVS,链接时就容易出奇葩错误。统一工具链,是Windows编译的理想要素。
4. 分平台编译实操:Windows、Linux、macOS各自要注意什么
授人以鱼不如授人以渔。我把三个主流平台的完整编译路径分别走一遍,你在哪个平台就对照看哪一段。
4.1 Windows + MSVC:vcpkg是省力通道,但也有限制
Windows下编译OpenMVS,我强烈建议直接用vcpkg装依赖。理由很简单:手动从源码编译OpenCV+Boost+Ceres+CGAL,光Boost就能让你折腾一下午。vcpkg可以有一条命令搞定整套依赖。
先安装vcpkg并集成到Visual Studio:
git clone https://github.com/microsoft/vcpkg.git cd vcpkg .\bootstrap-vcpkg.bat .\vcpkg integrate install然后安装OpenMVS需要的库(这一条会去下载编译,时间比较长):
.\vcpkg install opencv4[core,contrib,nonfree] boost-system boost-filesystem boost-program-options eigen3 ceres-solver cgal --triplet x64-windows装完之后在CMake GUI配置时,vcpkg会自动被integrate install注册到系统里,CMake能找到vcpkg的包。但是有一个容易忽略的地方:vcpkg默认会装一个最新版OpenCV,而OpenMVS对OpenCV的某些旧接口还有依赖。如果遇到接口匹配问题,你需要指定安装历史版本:
.\vcpkg install opencv4[core]@4.5.5接着CMake配置时,再补充一条:
-DCMAKE_TOOLCHAIN_FILE=[vcpkg路径]/scripts/buildsystems/vcpkg.cmake用vcpkg编译的好处是依赖之间的关系vcpkg会帮你协调好,省心。坏处是第一次安装这些库要花很长时间,而且vcpkg编译过程中如果中途断了,重来一遍非常痛苦。我试过在内存只有8G的机器上编Boost,直接被编译器内存不足干崩好几次。建议把/MP参数加上(VS的多核编译),同时保证硬盘有10G以上的空余。
Windows下还有一个高频坑:OpenMVS的三方库中OpenCV的world模式(把所有模块编进一个lib)和默认模式生成的lib命名不一样。如果你在链接时报LNK2019 unresolved external symbol,八成是OpenCV模块没对上,去检查链接库里是不是缺少对应的opencv_xxx4.lib文件。
4.2 Linux:apt安装为主,Ceres建议源码编译
Linux是OpenMVS最友好的平台。Ubuntu/Debian下大部分依赖直接用apt装:
sudo apt install libeigen3-dev libopencv-dev libboost-all-dev libcgal-dev libceres-dev libgflags-dev libgoogle-glog-dev libsuitesparse-dev这里有一个需要注意的细节:apt里的libceres-dev往往版本偏老。比如Ubuntu 20.04自带的是Ceres 1.14,而OpenMVS的某些功能模块需要Ceres 2.0以上的接口。如果你不打算用OpenMVS里的全部功能,老版本Ceres也能凑合编过;但如果你要跑BA(Bundle Adjustment)相关的完整管线,我建议源码编译一份Ceres 2.1.x装到系统里,再回来编OpenMVS。
Ceres源码编译很简单:
git clone https://github.com/ceres-solver/ceres-solver.git cd ceres-solver mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) sudo make install装完Ceres后,记得确认一下CMake能找到它:
ls /usr/local/lib/cmake/Ceres如果能列出CeresConfig.cmake,说明CMake路径没问题。
Linux编译OpenMVS的另一个隐藏问题是OpenMP和CUDA的配合。如果你在CMake里同时开了-DMVS_USE_OPENMP=ON和-DMVS_USE_CUDA=ON,在部分老版显卡驱动上会出现运行时冲突,表现是程序一启动就直接段错误。遇到这种情况,可以先关掉OpenMP试试,大多数场景下GPU已经够快,CPU并行是锦上添花。
4.3 macOS:Homebrew是好帮手,但OpenMP要手动装
macOS下的OpenMVS编译,依赖基本靠Homebrew搞定:
brew install eigen opencv boost ceres-solver cgal但有一个macOS特有的坑:Homebrew默认的clang编译器在Apple Silicon机器上不一定自带OpenMP支持。如果你编译时开了-DMVS_USE_OPENMP=ON,很可能会看到一个奇怪的报错:
fatal error: 'omp.h' file not found这个报错的意思是系统找不到omp.h头文件。解决办法是手动安装libomp:
brew install libomp然后在CMake时指定一下:
cmake .. -DOpenMP_CXX_FLAGS="-Xpreprocessor -fopenmp" -DOpenMP_CXX_LIB_NAMES="omp" -DOpenMP_omp_LIBRARY=$(brew --prefix libomp)/lib/libomp.dylib这一条命令就是告诉编译器,“去找Homebrew目录下的libomp”。不写的话,编译器会用默认路径搜索,Apple Silicon上往往搜不到。
macOS下编译时还有一个很容易被忽视的问题:OpenCV版本。Homebrew当前默认的OpenCV是4.x,对OpenMVS来说4.x整体兼容性还可以,但个别函数(比如cv::imread对路径的编码处理)在macOS的某些版本上会有诡异行为。这个不影响编译,影响的是运行时。如果后面跑测试数据集时发现图像加载不出来,优先检查路径和图像编码格式。
4.4 三平台对比小结
| 平台 | 依赖安装方式 | 主要风险点 | 我的建议 |
|---|---|---|---|
| Windows | vcpkg或手动装 | 工具链不统一、OpenCV lib命名 | 统一MSVC版本,用vcpkg管理 |
| Linux | apt + 源码补装 | Ceres版本太老、OpenMP与CUDA冲突 | Ceres源码编译,CUDA与OpenMP先开一个 |
| macOS | Homebrew | OpenMP缺失、OpenCV运行时问题 | 手动加libomp路径,运行时再调试 |
5. 编译报错的完整排查链路:从报错信息反推根因
这一节是全文的重头戏。你费尽千辛万苦,终于把CMake配置跑通了,然后make,报错。怎么排查?我分几个典型场景逐一讲。
5.1 CMake阶段的报错:百分之八十是路径问题
第一阶段是CMake配置阶段,报错基本逃不开下面这几类:
找不到包:
Could not find a package configuration file provided by "Ceres"。- 根因:
Ceres_DIR没设置或设置错了。 - 排查:先在终端里执行
list命令查看Cereal的安装位置,然后把实际路径传给CMake。注意不要只传给父目录,CMake要的是包含CeresConfig.cmake的那个目录。
- 根因:
版本不匹配:
The Eigen version is too old或requires Eigen >= 3.3.7。- 根因:某些库(比如Ceres)在编译时强制检查Eigen版本。
- 排查:检查是否有多个版本的Eigen存在。Ubuntu下有时候
/usr/include/eigen3和/usr/local/include/eigen3里各装了一份,CMake用了老的那份。直接用-DEIGEN3_INCLUDE_DIR指到新版路径即可。
Python/其他无关依赖干扰:偶尔报找不到Python或者Qt的错。
- 根因:OpenMVS的构建系统里某些第三方模块可能被系统全局的配置污染。
- 排查:这种情况极少见,通常关闭对应的
MVS_USE_*开关就可以。
5.2 编译阶段的报错:一看到C++11/C++14/C++17字样就要警觉
第二步开始真正编译代码,最常见的一类报错是:
error: #error This file requires compiler and library support for the ISO C++ 2011 standard.这个报错的根因不是你的编译器太老,而是CMake没有把C++11/C++17标准传给编译器。OpenMVS在部分分支上要求C++17,但CMakeLists里可能没有硬性设置,而是依赖你手动传递。
解决办法是CMake时加:
-DCMAKE_CXX_STANDARD=17我在Ubuntu 20.04上第一次编的时候漏了这个参数,结果CGAL的头文件里疯狂报错,一度以为是自己CGAL装坏了。其实问题很简单,就是标准没对齐。
另一类典型编译报错是模板实例化相关:
error: no matching function for call to 'XXX::Compute'这类问题基本可以锁定为版本接口变化。比如Ceres 1.x到2.x,很多函数的参数从ceres::Problem变成了ceres::Problem::Options,代码里如果OpenMVS源码是按Ceres 2.0的接口写的,你用Ceres 1.x编译就会报这种错。反过来也一样。所以如果源码是从最新master分支拉的,建议依赖也尽量靠近最新稳定版;如果源码是某个release版本,建议依赖版本和release发布的时间接近。
5.3 链接阶段的报错:Windows最痛,Linux稍少
链接阶段报错在Windows上最常见,典型表现是大量LNK2019、LNK2001。这类报错我有一个百试百灵的排查顺序:
- 确认所有依赖库都是同一个编译器和同一个运行时编译的。混合使用MT(静态运行时)和MD(动态运行时)会在链接时爆炸。
- 确认Debug和Release的库没有混用。比如你编译OpenMVS的Release版,却把OpenCV的Debug libes链接进来了,那LNK2001就是家常便饭。
- 确认库路径没有被vcpkg和系统路径同时污染。Windows下有无数人因为
PATH环境变量同时指向多个OpenCV版本而链接错库,排查方式就是打开Visual Studio的链接器输入选项,一条条看链接的是不是你想用的lib文件。
Linux上链接报错较少,但如果是自己从源码编译的Ceres,可能会遇到unresolved symbol glog::...,这是没装glog导致的。补一个:
sudo apt install libgoogle-glog-dev如果还报suitesparse相关错误:
sudo apt install libsuitesparse-dev5.4 一个完整的排查链路示例
说了这么多,我放一个自己之前实打实遇到过的排查过程,方便你理解我是怎么一步步找到根因的。
现象:Windows上CMake配置顺利,VS编译时报LNK2019: unresolved external symbol "void __cdecl cv::copyMakeBorder(...)"。
第一步,我先去查这是不是OpenCV的符号。copyMakeBorder是OpenCV核心库的函数,在opencv_core4.dll里。于是我去VS的链接器设置里看,发现工程链接的是opencv_world460.lib。
问题就来了:我的OpenCV安装版本是4.5.5,而vcpkg里装的可能是4.6.0,这两者的lib命名和符号版本都不一样。CMake的OpenCV_DIR指向了vcpkg里的4.6.0,但我手动装过一个4.5.5的包,系统环境变量里有一个路径干扰了。
解决办法很简单:把CMake缓存清掉,重新指定OpenCV_DIR到vcpkg的share/opencv目录,然后重新Configure、Generate。编译立刻通过。
这个例子说明什么?OpenMVS编译报错,绝大多数时候不是源码问题,而是环境里有多份同名的路径互相打架。所以我一直建议:开始配置之前就把系统的PATH、CMAKE_PREFIX_PATH先检查一遍,确保只有一个版本的依赖库在一个可控目录下。
5.5 CUDA相关的报错
最后快速提一下CUDA的坑。如果你开了-DMVS_USE_CUDA=ON,那么编译时可能会遇到:
CMake Error: CUDA unknown architecture 'compute_30'这是因为新版CUDA Toolkit里已经移除了对旧架构(比如Kepler/compute_30)的支持。解决办法是在CMake时指定一个合理的架构,比如Pascal架构对应compute_60,老一点的卡用compute_50:
-DCUDA_GENERATION=Pascal如果编译时提示cuda_runtime.h找不到,那是CUDA_TOOLKIT_ROOT_DIR没有指定正确。Windows下通常是这样:
-DCUDA_TOOLKIT_ROOT_DIR="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.8"装CUDA的时候如果是默认路径,一般就是这个目录。注意不要写成有空格的形式,路径如果带空格,CMake里一定要加引号。
6. 编译完成之后:验证产物、跑通最小示例、后续还能怎么扩展
编译通过不代表万事大吉。打开build目录,你会看到一堆可执行文件,其中有几个核心工具是每天都用得上的:
DensifyPointCloud:稠密点云重建ReconstructMesh:网格重建RefineMesh:网格细化TextureMesh:纹理映射InterfaceViewer:可视化查看器(需要编译时开相关开关)
先跑一个最简单的示例,把整个流程验证一遍。OpenMVS官方提供了测试数据和一些处理脚本,一般来说你会准备一个图片文件夹,然后按顺序执行:
# 1. 用OpenMVG或者其他SfM工具生成位姿文件(.mvs格式) # 2. 稠密重建 ./DensifyPointCloud input.mvs -o output.mvs -w working_dir # 3. 网格重建 ./ReconstructMesh output.mvs -o mesh.mvs -w working_dir # 4. 细化网格 ./RefineMesh mesh.mvs -o refined.mvs -w working_dir # 5. 纹理映射 ./TextureMesh refined.mvs -o textured.mvs -w working_dir如果这几步都能顺利跑通,你的编译就完全成功了。这里有一个关键点:OpenMVS的输入不是直接的图片,而是的.mvs文件,里面记录的是相机位姿和稀疏点云。所以一般需要配合OpenMVG一起使用。OpenMVG也是一个独立的开源项目,也需要编译。两个项目之间的衔接,本质就是OpenMVG导出OpenMVS能识别的场景格式。
如果你只想测OpenMVS本身,官网的Tests目录里有一些现成的数据可以直接跑,不必从头做SfM。流程虽然短,但能验证你的二进制文件是否真的正常工作。
编译完成之后,后续还能怎么扩展?我提供几个方向:
- 整合自己的采集流程:用手机拍一组照片,跑一遍OpenMVG+OpenMVS,看看整个重建效果。
- 调优参数:OpenMVS的命令行参数非常多,比如
--resolution-level控制处理分辨率,--dense-config-file控制稠密重建策略。不同数据集差别很大,值得花时间做几组对照实验。 - 二次开发:OpenMVS的代码结构清晰,你完全可以修改
DensifyPointCloud的逻辑,替换深度图融合策略,或者改TextureMesh的纹理映射算法。想在工程里落地的话,这一步的意义远大于单纯的使用。
我个人的建议是:第一次编译别追求把CUDA和所有模块全开,先把CPU版本跑通,后面再逐步开CUDA优化。这个做法的好处是缩小问题排查范围——如果CPU版能编过跑通,说明依赖和源码本身没问题,后面加CUDA如果失败,问题一定出在CUDA相关配置上,不会有“什么都可能出问题”的困扰。
7. 把这一步走通之后的一些个人体会
做三维重建的人,绕不开OpenMVS。它不像一些商业软件装了就能用,正因为它开源、可定制,才需要你先亲手把它编译出来。这个过程可能会耗掉你两三天甚至更久的时间,但请相信,绝大多数时间不是花在最后那几步,而是花在“环境为什么对不上”上。
我的建议是别硬扛,多用CMake的日志信息,报错时仔细读输出内容里的路径提示。output.txt里每一条Found XXX at /path/to/xxx都值得扫一眼,确认路径是否指向了你想用的那个库。路径对不上,八成就是问题所在,这比反复清缓存重来快得多。
编译通了之后,手里的这套工具就是真正属于你的了。后续不管是跑公开数据集还是自己采集的照片,能随时改参数、看中间结果、调试算法,这种掌控感是直接用预编译binaries的人体会不到的。这也是我为什么愿意花专门的时间写这篇OpenMVS源码编译的文章——它值得被认真对待,也值得你亲手走一遍。