ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Win10+VS2015下OpenCV3.3.0扩展contrib编译完整指南

Win10+VS2015下OpenCV3.3.0扩展contrib编译完整指南

如果你最近在一个老项目里接手了OpenCV 3.3.0,大概率也绕不开这个标题本身:Win10 + VS2015 + OpenCV3.3.0下扩展OpenCV_contrib3.3.0。OpenCV 3.3.0官方只提供标准模块的预编译包,SIFT、SURF、face、aruco这些扩展模块都放在contrib仓库里,而官方二进制是不包含它们的。更麻烦的是,这个版本太老,网上教程大多已经转向OpenCV 4.x,照着新版流程做,第一轮CMake配置就对不上。这篇文章就把我自己完整走通过一遍的流程记录下来,从版本选择、环境准备到CMake配置、VS2015编译、安装,再到用SIFT验证contrib是否真正生效。适合正在搭建VS2015环境下OpenCV 3.3.0源码编译环境的人,也适合买了带旧SDK的工控机、只能锁死在VC14工具集里做视觉开发的人。

1. 为什么一个2017年的老组件,到现在还有人折腾

1.1 contrib扩展包到底补了什么

OpenCV的主仓库在3.x时代就已经把标准模块拆得很细,核心、图像处理、视频、DNN等都在里面,但历史遗留的xfeatures2d、face、aruco、text、optflow这些实验性或专利敏感模块,一直放在独立仓库里。典型的就是SIFT和SURF。OpenCV 3.3.0里,你去主仓库的opencv2/features2d.hpp里找SIFT,是找不到的,它被挪到了opencv2/xfeatures2d.hpp,而这个头文件只存在于contrib扩展仓库的模块里。

所以“扩展contrib”不是一个可选项,而是必须做的编译步骤。很多人下载了官方提供的opencv-3.3.0-vc14解压包,然后发现代码里写#include <opencv2/xfeatures2d.hpp>时报文件找不到,或者SIFT函数链接时报未定义符号,本质原因就是官方预编译包里没有编译contrib模块。

更关键的是,你不能指望把contrib的源码文件夹塞进已有官方包里碰运气。OpenCV的构建系统在主仓库源码里通过CMake组织,contrib是构建时参与进来的扩展模块集合,最终会被一起编进opencv_world330.dll或者拆分的多个lib中。想用SIFT,就必须从源码重新编译主仓库加contrib,没有捷径。

1.2 版本三方咬合,错一步满盘皆输

这个标题里的三个版本号其实咬得很死。

  • Windows 10对VS2015本身没有兼容问题,但VS2015必须打到Update 3,否则OpenCV 3.3.0的C++11代码在编译中会遇到一些编译器内部错误。
  • OpenCV 3.3.0主仓库的构建脚本对CMake版本有隐形的最低要求,CMake太老无法识别VS2015的x64生成器,CMake太新又可能因为策略变更产生一堆莫名其妙的问题。
  • contrib仓库必须和主仓库选择同一个tag。主仓库下载的是3.3.0,contrib也要下载3.3.0。如果一不留神拉了master分支,OpenCV主仓库3.3.0的某些内部API签名和contrib最新代码对不上,后期编译报错会成片出现,且错误信息根本不像版本冲突。

我见过不少配置失败案例,最后查下来都是contrib版本拉错。遇到过拉成3.4.x分支然后去配3.3.0主仓库的,编译xfeatures2d模块时,几十个头文件找不到。这属于白花一小时,换回匹配版本后立刻通过。

2. 环境准备里最容易翻车的三个点

2.1 VS2015 Update 3不是可选项

如果你是第一次在这台机器上安装VS2015,安装器里务必把“Visual C++”工具集勾上,同时勾选“Windows 10 SDK”。许多人只装了.NET相关组件,等到CMake一跑发现找不到C++编译器,那叫一个摸不着头脑。

安装完以后,打开Visual Studio的“帮助–关于”,确认版本号是14.0.25420.00以上。像这样的版本号对应VS2015 Update 3。Update 3修复了不少对Windows 10 SDK的识别问题,也补了C++标准库实现的一大堆bug。OpenCV 3.3.0源码里大量使用C++11特性,所以在VS2015上至少要这个Update。

还有一个坑:VS2015的内部版本号是14.0,所以CMake生成器名称叫Visual Studio 14 2015 Win64,不要以为是选错了。

2.2 CMake版本不要盲目追新

很多人习惯去CMake官网下载一个“最新稳定版”,然后就开始configure。OpenCV 3.3.0是2017年发布的,它的CMake脚本默认不认太新的策略规则。我常用的稳定组合是CMake 3.8.2,这套组合在Win10 + VS2015 + OpenCV 3.3.0上跑了很多次,CMake GUI没有红色告警,生成OpenCV.sln也干净。

如果你电脑里已经装了新版本CMake,也没必要卸载重来。配置时看到一串关于CMP0025、CMP0075的策略警告时,就要有心理准备:新CMake可能在后续步骤里因为不同策略给OpenCV生成不同的项目结构。我实测过用CMake 3.28去配置OpenCV 3.3.0,能生成工程,但部分contrib模块的下载路径处理方式变了,导致xfeatures2d的依赖文件下载位置异常,最后编译阶段报文件找不到。老老实实换回3.8.2,一次通过。

2.3 源码目录结构与下载方式

下载OpenCV源码时,我建议在GitHub上按tag下载zip,不要用master分支。主仓库选择3.3.0这个tag,文件解压后目录名一般是opencv-3.3.0。contrib仓库同样选择3.3.0,解压后目录名一般是opencv_contrib-3.3.0。

目录结构我习惯这样摆:

D:\ ├── opencv-3.3.0 ├── opencv_contrib-3.3.0 └── opencv-3.3.0-build

opencv-3.3.0-build是CMake的build目录,也就是最终生成VS工程文件的地方。三个路径不要出现中文、空格或者圆括号。别看这条像废话,曾经有人放在C:\Users\张三\下载\里,CMake的下载缓存路径处理中文有问题,configure阶段一直报路径找不到。

3. CMake配置过程逐项拆解

3.1 第一次Configure与生成器选择

打开CMake GUI,Where is the source code填D:/opencv-3.3.0,Where to build the binaries填D:/opencv-3.3.0-build。然后点Configure。

弹出选择框后,下拉列表里选Visual Studio 14 2015 Win64,注意一定要带Win64,这是关键。如果你选成不含Win64的Visual Studio 14 2015,生成的是32位工程的solution,后面链接x64的OpenCV库全部失败。平台选完以后,下面的编译器默认值不要动,直接Finish。

第一次Configure结束以后,红色高亮条目会很多,这是正常的。因为很多依赖还没有解析完成,你接下来要做的就是在搜索框里逐个设置关键变量,然后再次Configure。

3.2 OPENCV_EXTRA_MODULES_PATH设置错误是最普遍原因

在CMake GUI搜索OPENCV_EXTRA_MODULES_PATH,把这个变量指向contrib仓库里的modules目录。

正确写法:

D:/opencv_contrib-3.3.0/modules

千万别写成D:/opencv_contrib-3.3.0。OpenCV构建系统会在这个路径下直接扫描modules子目录里各个模块的CMakeLists.txt。如果你填的是contrib的根目录,虽然也能配置通过,但最后生成的模块列表里只有主仓库的标准模块,contrib根本没有参与构建。

如果你想判断contrib是否真正被识别,不用急着Generate。在CMake的输出窗口里找到OpenCV modules那一节,正常情况下应该能看到:

xfeatures2d face aruco text ...

看到这些模块,才说明扩展路径生效了。

3.3 必勾与可不勾的选项清单

第二次Configure以后,红色大部分会消失。这时候在搜索框里继续处理几个关键开关。

必勾的:

变量名推荐值原因
BUILD_opencv_worldON把所有OpenCV模块统一编译成一个opencv_world330库,测试程序链接时只需要一个库文件
OPENCV_ENABLE_NONFREEON启用SIFT、SURF等受专利保护的算法,不勾的话即使编译成功,运行时也会提示算法被禁用

可以不勾的:

变量名推荐值原因
BUILD_EXAMPLESOFF示例代码不是必需品,关掉能省不少编译时间
BUILD_TESTSOFF官方测试模块很大,只做二次开发没必要编译
BUILD_PERF_TESTSOFF同理
BUILD_opencv_python2/BUILD_opencv_python3按需如果不用Python调用OpenCV,可以关掉,少编两个大模块
BUILD_opencv_javaOFF用不到就关掉

如果你只做C++图像处理和特征提取,关掉测试、示例和Python,编译时间能少三分之一以上。四核八线程的机器,实测半小时左右能把Release版本编完。

这里有个容易被忽略的点:BUILD_opencv_world开启以后,contrib里的xfeatures2d等模块也会被编进world库,不冲突。所以后面链接时只写opencv_world330.lib就够用。

3.4 网络下载项:IPP与FFmpeg的处理思路

CMake一旦开始Configure,会尝试从远程服务器下载一些第三方依赖。OpenCV 3.3.0主要涉及两个东西:IPP加速库和FFmpeg预编译包。

IPP本身是对图像计算的硬件加速优化,默认情况下OpenCV会下载一份编进库中。如果网络比较波,configure会卡在这里,提示超时或CRC校验错误。解决办法很简单:图像特征提取这类应用场景,IPP关掉不影响功能,只会让某些像素级操作慢一点。

在搜索框里找WITH_IPP,把它取消勾选。同理,如果不用视频读写功能,可以连WITH_FFMPEG一起关掉。配置完以后再次Configure,这一步能绕开大部分下载问题。

如果你确实需要用OpenCV读取视频,FFmpeg预编译包又是必须的话,那就在configure时保持WITH_FFMPEG打开,多试几次,或者找一个网络空闲的时间段继续。不要无限重试,下载文件会缓存在C:/Users/<用户名>/.cache/opencv3/ffmpeg下,某一次成功后文件会留在那里,后续重新configure就不用再下载了。

3.5 第三次Configure与Generate

所有选项调整完后,再点Configure。观察输出窗口,如果已经没有红色高亮,而且OpenCV modules列表里出现了contrib模块,就可以点Generate了。Generate速度很快,基本一两秒结束,生成完会在build目录下产生一个OpenCV.sln。

到了这一步,CMake环节才算真正通过。很多教程在这里就默认成功了,其实不算。我见过不少人在Generate时弹出CMake Error: The following variables are used in this project, but they are set to NOTFOUND之类的提示,大意是某些依赖没找到。如果你的界面里也出现这个,先不要Generate,逐条看哪个变量是NOTFOUND,常见的是PYTHON_EXECUTABLE或JAVA_JVM_LIBRARY。既然用不到Python和Java,就把对应的BUILD_opencv_python*和BUILD_opencv_java关闭,再Configure。

4. VS2015编译与INSTALL安装

4.1 用Release x64生成ALL_BUILD

双击OpenCV.sln,Visual Studio打开以后,先把解决方案配置切到Release,把解决方案平台确认在x64。这时候你会看到一个庞大的项目列表,项目数量几十到上百不等,取决于你的模块开关。

在CMakeTargets目录下找到ALL_BUILD,右键,选择“生成”。注意第一次编译不要只单独生成INSTALL,因为INSTALL依赖所有模块的lib,单独生成会报错“无法打开opencv_core330.lib”之类。

编译过程耗时很长。在这期间,不要随便点掉正在运行的其他编译任务。如果某个模块失败,VS的“错误列表”会直接定位到具体项目。这时候不要慌,先看错误是属于编译类还是链接类。编译类错误通常和源码版本不匹配有关,链接类错误通常是项目依赖没生成完整,重新生成ALL_BUILD一般能解决。

4.2 几个低概率但高破坏性的编译错误

编译过程中我实际遇到过两个比较典型的坑。

第一个是xfeatures2d模块编译时,需要下载boostdesc_bgm.i、vgg_generated_48.i这类训练数据文件。这个下载动作发生在VS编译阶段,如果网络不行,VS会报error MSB3073或者提示找不到指定文件。解决办法是在错误信息里找到下载URL,单独用浏览器把文件下载下来,放到错误信息提示的缓存目录,再重新生成该模块。

第二个坑是编译器报fatal error C1083: Cannot open include file: 'opencv2/ximgproc.hpp'。这种多半不是文件真的缺失,而是你在CMake配置时把某个可选模块关了,但xfeatures2d代码实际上依赖它。解决办法不是去改代码,而是回到CMake GUI里把被关闭的相关模块重新打开,重新Configure和Generate,然后重新编译。

还有一个不算错误但很影响效率的事情:默认ALL_BUILD是顺序编译还是并行编译,取决于VS设置。在VS菜单里“工具–选项–项目和解决方案–VC++项目设置”中,把“最大并行项目生成数”调到4或更高,编译速度会明显提升。如果内存只有8G,建议保持默认,防止多个编译进程同时打爆内存。

4.3 INSTALL项目与产物目录

ALL_BUILD编译成功后,找到INSTALL项目,右键生成。这个项目不会产出新的功能库,而是把编译结果统一复制到CMake里设置的安装目录。

默认安装路径是D:/opencv-3.3.0-build/install。生成完成后,目录结构应该是:

install ├── include │ └── opencv2 └── x64 └── vc14 ├── bin │ ├── opencv_world330.dll │ └── opencv_world330d.dll └── lib ├── opencv_world330.lib └── opencv_world330d.lib

vc14这个目录名很关键,它代表Visual Studio 2015的VC++工具集版本。如果你后面用VS2015建立测试工程,库目录要指到这里。另外验证一下bin下有没有opencv_version.exe,有的话直接命令行执行:

opencv_version.exe

输出3.3.0就说明库本身没问题。接下来才有资格谈验证contrib扩展。

5. 用SIFT在VS2015里验证contrib是否真的生效

5.1 新建工程和目录配置

打开VS2015,新建一个C++控制台工程。刚创建的时候默认可能是Win32平台,这一步必须改。点菜单“生成–配置管理器”,在“活动解决方案平台”里选择x64,如果没有就先新建一个x64平台。否则后面链接时会报“这是无效的Win32应用程序”或“无法加载DLL”的错。

然后设置工程属性:

配置项填写内容
C/C++ – 常规 – 附加包含目录D:/opencv-3.3.0-build/install/include
链接器 – 常规 – 附加库目录D:/opencv-3.3.0-build/install/x64/vc14/lib
链接器 – 输入 – 附加依赖项Release下填opencv_world330.lib,Debug下填opencv_world330d.lib

Debug和Release的库要匹配使用。如果把Debug工程链接Release版的opencv_world330.lib,多数情况能跑,但调试时变量监视可能不正常;如果Release工程链接了d版本库,运行时会因为找不到调试版DLL失败。所以最简单的方法是OpenCV编译时把两者都编出来,VS里切一次Debug生成ALL_BUILD和INSTALL,再切一次Release生成,这样lib目录下两个文件都在。

5.2 测试代码与NONFREE双保险

在工程里添加一个main.cpp,内容如下:

#define OPENCV_ENABLE_NONFREE #include <opencv2/opencv.hpp> #include <opencv2/xfeatures2d.hpp> #include <iostream> int main() { cv::Mat gray = cv::imread("D:/test.jpg", cv::IMREAD_GRAYSCALE); if (gray.empty()) { std::cout << "image not found" << std::endl; return -1; } auto sift = cv::xfeatures2d::SIFT::create(); std::vector<cv::KeyPoint> keypoints; cv::Mat descriptors; sift->detectAndCompute(gray, cv::noArray(), keypoints, descriptors); std::cout << "keypoint count: " << keypoints.size() << std::endl; std::cout << "descriptor size: " << descriptors.rows << " x " << descriptors.cols << std::endl; return 0; }

注意第一行#define OPENCV_ENABLE_NONFREE必须出现在所有OpenCV头文件之前。虽然真正的“启不启用”在CMake阶段就已经决定,但这个宏会在头文件层面强制展开非自由算法的声明,相当于双保险。如果你在CMake阶段没有勾选OPENCV_ENABLE_NONFREE,运行时SIFT会直接报“该算法受专利保护,当前构建已禁用”,程序返回空特征点。出现这种情况,不要改宏,回到CMake GUI勾上选项重新编译OpenCV才是正解。

5.3 运行结果与常见报错排查

编译通过以后,运行程序前要确保opencv_world330.dll能被找到。最简单的办法是把install/x64/vc14/bin加进系统环境变量Path,然后重启VS。不想改全局环境变量的话,也可以把DLL直接复制到exe所在目录。

如果程序输出keypoint count: 几千这样的信息,说明contrib扩展真的生效了。哪怕只有几十个特征点,只要数量不是0,都说明SIFT模块已被正确编译进OpenCV库。

常见报错我再列一张表:

现象原因处理
编译时报找不到xfeatures2d.hpp工程包含目录没配,或者CMake阶段contrib没参与构建检查包含目录和CMake模块列表
链接时报LNK2019 unresolved external symbolworld库没链接上,或者链接的是非world库但没把contrib模块的lib逐个写上回到属性页确认附加依赖项只有opencv_world330.lib
运行时提示“算法已禁用”CMake阶段没勾选OPENCV_ENABLE_NONFREE重新CMake并编译OpenCV
运行时提示DLL缺失bin目录不在Path中,或dll版本是Debug/Release不匹配添加bin到Path或拷贝DLL到exe目录

6. 让配置环境可复用的一些习惯

6.1 环境变量建议只加局部

很多教程让你一上来就把install/x64/vc14/bin塞进系统Path,我建议换个思路。如果你的电脑上同时装了OpenCV 3.x和OpenCV 4.x,或者同时有VS2015和VS2019编译的库,全局Path一加,测试程序运行时就会加载错DLL,表现是程序启动后马上崩溃,或者明明调用了最新库却报找不到新函数。

我自己的习惯是:在VS工程属性里设置“调试–环境”,或者写一个setenv.bat脚本,在编译运行前临时把bin加进Path。这样不同项目可以用不同OpenCV版本,互不干扰。发布程序时再手动把需要的DLL放进exe同目录。

6.2 保留install目录比重新编译更省事

OpenCV源码编译这件事,难点全在configure阶段和第三方依赖下载,一旦成功,install目录就是一套完整可用的SDK。重装系统以后,只要你的VS2015还在,直接把install目录拷到新机器,然后在工程属性里指过去就行,不需要重新走一遍CMake和编译。

如果用Docker类隔离环境或者项目迁移,建议把opencv-3.3.0、opencv_contrib-3.3.0和opencv-3.3.0-build三个目录整体打包存档。以后任何人想复现这个环境,打开CMake GUI时只需要重新填路径,其余配置全部能从build目录的CMakeCache里恢复,比对着文档手点快得多。

最后再多说一句:如果你也被卡在某个奇怪的编译错误上,不要急着改代码,先把CMake输出窗口或者VS错误列表里第一条红色信息完整看一遍。老版本OpenCV的坑多数不是算法代码的问题,而是路径、版本、网络下载这三件事。把这三件事理顺,这篇配置流程基本就能一遍过。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进