在 VS 里用 OpenCV,本质是三处配置:头文件去哪找、库文件去哪找、链接哪几个。 Debug 与 Release 必须各配一遍,这是最容易漏的一步。
前四章用的是 Qt,一路都在「调用别人的成品」。 这一章开始自己动像素——OpenCV 就是那个「把像素操作写成一行」的库。 但在 C++ 里用它之前,得先让编译器找得到它。 这一节讲的就是这件事,跟算法无关,纯粹是工程配置。
一、三处配置:包含目录、库目录、附加依赖项
C++ 的库分两步用:编译时要看到「长什么样」(头文件),链接时要连上「真实现」(lib)。 所以配置也是三处:
| 配在哪 | 填什么 | 解决什么问题 |
|---|---|---|
| C/C++ → 常规 → 附加包含目录 | ...\opencv\build\include |
#include 找得到头文件 |
| 链接器 → 常规 → 附加库目录 | ...\opencv\build\x64\vc15\lib |
找得到 .lib 文件 |
| 链接器 → 输入 → 附加依赖项 | opencv_world450.lib |
链接期能解析到符号 |
第三行那个文件名有两种流派:
opencv_world 版:所有模块打成一个库,一行搞定,代价是体积大、换模块要重编。
分离版:opencv_core.lib、opencv_imgproc.lib、opencv_highgui.lib……按需写,链接更快,
但容易漏。
漏了会怎样:报 LNK2019,说某个符号无法解析。
这个错的名字叫「未解析的外部符号」,九成就是附加依赖项少写了一个 lib。
库目录还有个位数陷阱:x64\vc15\lib 与 x86\vc15\lib 是两套,
项目是 x64 却指到 x86 目录,会报一堆 LNK2038 或 模块计算机类型冲突。
这类错误跟代码完全无关,但看起来很吓人。
二、Debug 与 Release 各配一遍
这一节的重点在这儿。 VS 的项目属性是「按配置分开存」的:Debug 一套、Release 一套, 你在 Debug 里配完,切到 Release 全是空的。
而且库名也不一样:Debug 用带 d 后缀的版本(opencv_world450d.lib),Release 用不带后缀的。
两套 CRT 的运行时不兼容,混用会在链接期或运行时炸。
三种做法,从差到好:
| 做法 | 优点 | 缺点 |
|---|---|---|
| 只在 Debug 里配 | 快 | 切 Release 就崩,回头查半天 |
| 两个配置各配一遍 | 能用 | 改一次要改两处 |
用属性表 .props |
配一次、两个配置共用 | 要学一个新概念 |
第三种是正解:视图 → 其他窗口 → 属性管理器,右键 Debug|x64 添加「新建属性表」,
在里面配好三处,再把这个属性表加到 Release|x64 上。
这样两个配置共享一份设置,
而且这个 .props 文件可以直接发给同事——他双击导入就配好了,不用照着文档点十几次。
还有一个「运行时缺 dll」的坑要提前说:
链接成功不等于能跑。程序启动时找不到 opencv_world450.dll,
报的是「计算机中丢失 xxx.dll」,看着像系统坏了。
做法是把 bin 目录加进 PATH,或者把 dll 拷到 exe 旁边。
三、本机这套环境长什么样
本机装的是 OpenCV 5.0.0 的 Python 绑定,不是教材那套 VS 2015 + OpenCV 4。 这件事要说清楚,因为两者的差别不只是版本号。
| 对比项 | 教材:VS 2015 + OpenCV 4 | 本机:Python + OpenCV 5 |
|---|---|---|
| 怎么用 | 配三处属性,写 C++ | import cv2,写完就跑 |
| 缺库怎么办 | 自己下、自己配、自己拷 dll | pip install |
| 算法 API | cv2::GaussianBlur |
cv2.GaussianBlur,同名 |
| 界面部分 | MFC / HighGUI 窗口 | 无窗口,结果落盘看 |
第三行是这节课能真跑的原因:算法层的 API 几乎同名同参,GaussianBlur、bilateralFilter、
medianBlur 一个一个对得上。所以「磨皮那段逻辑」在两边是同一段话。
第四行是不能真跑的原因:imshow 需要一个窗口系统,沙箱里没有桌面。
所以本课的处理是「算出来,把数字和文件落盘」,
图像本身用数值指标代替眼睛——这反而更严格:眼睛看不出「边缘保留 33% 还是 85%」。
还要提醒一句:OpenCV 5 移除了一批旧 API(cv2.CV_... 那批老常量、
还有一些 cv::legacy 里的东西)。
教材里 4.x 能编过的代码,拿 5.x 可能编不过,但这一章用到的都是核心函数,没有受影响的。
四、动手 Demo:OpenCV 在这台机器上是什么样子
这段先查版本,再从 getBuildInformation() 里挑三行关键的,
最后验证「Mat 就是 ndarray」这件事。
FFMPEG = YES 那一行决定了 VideoCapture 能不能直接开视频文件,
DIY 编译的 OpenCV 常常是 NO。
import cv2, numpy as np
print(cv2.__version__)
info = cv2.getBuildInformation()
for line in info.split("\n"):
s = line.strip()
if s.startswith("FFMPEG") or s.startswith("Parallel"):
print(s)
img = np.zeros((480, 640, 3), dtype=np.uint8)
print(img.shape, img.dtype, img.strides[0])
px = np.zeros((1, 1, 3), dtype=np.uint8)
px[0, 0] = (10, 20, 240) # 写成 B=10, G=20, R=240
print(cv2.cvtColor(px, cv2.COLOR_BGR2RGB))
实跑输出:
--------------------------------------------------------------------
OpenCV 在这台机器上是什么样子
--------------------------------------------------------------------
**先确认环境,再谈代码。**
**这一节要的三样东西:版本、有没有编进 FFmpeg、并行框架是谁。**
版本:**5.0.0**
**`getBuildInformation()` 里挑三行看**(这是判断「能不能用」的地方):
FFMPEG = YES (prebuilt binaries)
Parallel framework = Concurrency
**第一行最关键**:**`FFMPEG = YES` 意味着 `VideoCapture` 能直接开 mp4、rtsp、http**,
不用你自己解码再把帧喂进来。
**DIY 版 OpenCV 常常是 NO**,那时候 `VideoCapture("a.mp4")` 直接返回空,报错还只有一句「打不开」。
**再看 Mat 与 ndarray 的关系**——**Python 里的 OpenCV 图像就是一个 numpy 数组。**
**同一块内存,没有包装层。**
造一张 640×480 的彩色图:
`img.shape` = (高, 宽, 通道)
实际值 = **(480, 640, 3)**
`img.dtype` = **uint8**,元素个数 = **921600**
`img.nbytes` = **921600 字节**
连续吗 = **True**,行跨度 = **1920 字节**
**「行跨度 1920」就是「宽 640 × 3 通道」**,**说明 numpy 这边是紧凑排列、没有填充。**
**这一点到 05.03 讲 MFC 显示时会变**——**Windows 的位图要求每行按 4 字节对齐,那时就要补空字节。**
**通道顺序是 OpenCV 最著名的坑**:
一个像素写成 `(10, 20, 240)`,**OpenCV 认为它是 B=10、G=20、R=240,也就是接近纯红。**
转成 RGB 之后是 **(240, 20, 10)**——**红蓝换位了。**
**所以这三条要一起记**:
**OpenCV 读进来的、`imread` 得到的、`VideoCapture` 抓到的一律是 BGR。**
**matplotlib、PIL、Qt 的 `QImage` 一律当 RGB 用。**
**中间那一步转换(`cvtColor`)漏掉,颜色就是蓝脸。**
三行关键信息都在:版本 5.0.0、FFMPEG = YES (prebuilt binaries)、
并行框架是 Concurrency(Windows 上的并发运行时)。
(prebuilt binaries) 这个后缀值得注意:它说明 FFmpeg 是用预编译二进制链进去的,
不是从源码编的。
后果是「这个 OpenCV 带了哪些编码器,由那个预编译包决定」,你改不了——除非自己重编 OpenCV。
最后那行 (240, 20, 10) 是全章最该记住的一个数:写进去的是 (10, 20, 240),
转 RGB 之后变成 (240, 20, 10),首尾两个数换了位置。
这就是「BGR」这三个字母的全部含义。
strides[0] = 1920 也要留意:它是「一行多少字节」,等于宽 640 乘 3 通道,一点空隙都没有。
到 05.03 讲 MFC 显示时,这个假设就不成立了——Windows 的位图要求每行 4 字节对齐,
那时每行要补空字节。
30 秒实验
- 在 VS 里故意把「附加依赖项」那一行删掉,看报的错是什么(应该是
LNK2019)。 - 算一下:
opencv_world450.lib与分离版差在哪——分离版要写几个 lib? - 判断一下:项目从 Debug 切到 Release 之后崩了,第一个该查什么?
常见误区
- 只在 Debug 里配属性。 切到 Release 全是空的,报
LNK2019。 正确做法是用属性表.props,两个配置共用一份。 - Debug 配 Release 的 lib。 Debug 要用带
d后缀的版本,两套 CRT 运行时不兼容, 混用会在链接期或运行期炸。 - 库目录指到
x86。 项目是 x64 却链 32 位库,报「模块计算机类型冲突」,跟代码毫无关系。 - 链接成功就以为万事大吉。 还得把
opencv_world450.dll放到 exe 旁边或者加进PATH, 否则启动就报「丢失 dll」。
自测三问(点开对答案)
1. 在 VS 里用 OpenCV 要配哪三处? —— 附加包含目录(头文件)、附加库目录(lib 在哪)、附加依赖项(链接哪个 lib)。 前两处是「找得到」,第三处是「连得上」。 2. 为什么 Debug 与 Release 要分开配? —— 两套配置的属性是分开存的,而且 Debug 版的库带 `d` 后缀、用的是另一套 CRT 运行时,不能混用。 3. `FFMPEG = YES` 这一行有什么用? —— 它说明 `VideoCapture` 能直接读 mp4、rtsp、http 这类格式,不用自己解码再喂帧。 DIY 编译的 OpenCV 常是 NO,那时只能读图片序列。环境配好了,下一节开始动像素。第一个例子就是「磨皮美颜」——它听起来像美工活,其实是三种滤波器之间的取舍。
- ☐ (本节完,下一节 05.02 OpenCV显示摄像头及磨皮美颜)