ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

VS2019+Qt+OpenCV图像显示与处理:环境配置与避坑指南

VS2019+Qt+OpenCV图像显示与处理:环境配置与避坑指南 简介在VS2019集成环境下通过Qt与OpenCV结合完成图像显示与处理的完整示例工程面向计算机视觉初学者及需要快速搭建GUI图像应用的开发者。工程覆盖OpenCV环境配置、Mat与QImage互转、灰度转换、均值滤波等典型操作并附带Qt Widgets界面源码便于直接运行与二次修改。压缩包共46个文件以cpp源码、h头文件、ui界面文件、vcxproj工程文件和sln解决方案为主另有编译生成的exe、pdb调试文件及项目配置props等整体大小12.34MB已获867人学习。资源内工程名为helloworld目录包含.vs、x64等标准VS输出结构可对照学习Qt插件调用OpenCV的完整编译配置。配套代码从读取本地图片开始逐步演示界面展示、处理结果实时刷新的完整流程适合作为课设参考或视觉入门练手项目。1. 从零搭一个“能跑”的VS2019QtOpenCV实例真正的门槛不在代码很多人在VS2019里装好Qt和OpenCV后第一件事是满网找现成代码结果复制进去不是报一堆LNK错误就是窗口弹出来一片灰。原因很统一Qt用的是MinGW编译的库OpenCV却是为MSVC编译的两边编译器都不认识对方代码写得再对也白搭。这个标题里的“显示图像和处理图像”其实只占工作量的一小半前半场全耗在版本对齐、路径配置和运行库匹配上。这篇笔记把从VS2019新建Qt Widgets工程、接入OpenCV预编译库到真正显示和处理一张图片的全流程拆开每一步给可复现的操作和参数最后把最常见的几个翻车现场和解决办法列出来。适合刚把VS2019、Qt、OpenCV分别装好、但还没让三者“见上面”的人照着做一遍就知道自己卡在哪了。2. 版本匹配是第一道坎VS2019、Qt与OpenCV的编译器对齐2.1 为什么Qt要用msvc2019_64而不是MinGW在Qt官网下载页面会看到一堆目录名msvc2019_64、mingw81_64、wasm等等。VS2019用户第一步就选错的人不少选成MinGW之后Qt Creator里能编译一到VS2019里就报“编译器不兼容”。背后的逻辑不复杂Qt本身是源码包官方针对不同编译器提供预编译好的二进制库。MSVC系列对应的是Visual Studio的C编译器MinGW对应的是GCC的Windows移植版。VS2019默认用MSVC v142工具集编译C它只能链接同样由MSVC生成的静态库和导入库。如果你在VS2019里硬链接MinGW版的Qt库链接器会直接拒绝报错通常是“无法解析的外部符号”或“unknowntype”。所以VS2019用户应该选msvc2019_64这个目录64位对应Qt 5.15.2就是qt\5.15.2\msvc2019_64。如果你的VS2019装了别的工具集比如v141VS2017可以选msvc2017_64但VS2019默认装v142直接用msvc2019_64最省事。安装时还有一个细节Qt安装器默认只勾选Qt Creator的组件不会勾选MSVC 2019 64-bit那一项。需要进到“Qt 5.15.2”节点下把“MSVC 2019 64-bit”勾上否则你只有Qt Creator没有MSVC库后面在VS2019里创建Qt工程会直接失败。2.2 OpenCV预编译库的版本选择与目录规划OpenCV的Windows预编译包下载下来是一个自解压的exe解压后目录结构大致是opencv\build\include、opencv\build\x64\vc15\lib、opencv\build\x64\vc15\bin。这里的vc15不是指VS2015而是指VS2017及以后的所有MSVC编译器VS2019用它没有问题。版本对应关系上OpenCV 4.5.x和4.8.x是当前比较稳的选择预编译包里已包含opencv_world450.dll这类合并库文件不需要再处理一大堆离散的dll。你要看的只有两件事是x64还是x86以及vc14还是vc15。VS2019装64位就用x64\vc15别再纠结下载页面上写的“Windows”字样。还有个容易踩的坑OpenCV的预编译库自带Debug版和Release版Debug版文件名带d后缀opencv_world450d.libRelease版不带opencv_world450.lib。在VS2019里如果Debug配置链接了Release库运行时十有八九崩在内存分配上而且没有报错提示。这一点我会在配置工程时再次强调。规划目录时我一般会把Qt和OpenCV放在磁盘根目录下的平级路径比如D:\Qt\5.15.2\msvc2019_64和D:\opencv。路径里不要出现中文、不要出现空格更不要嵌套得太深。Windows对长路径的兼容性一直不怎么样后面有个“路径过长”的编译错误就与此有关。如果是从旧项目迁移OpenCV 3.x的目录布局和4.x略有差异但上面说的核心规则不变。建议新项目一律用OpenCV 4.x因为4.x统一了模块头文件opencv2/opencv.hpp这一个头文件就能覆盖大部分常用功能。3. 在VS2019里创建Qt工程并接入OpenCV一步步配置3.1 用Qt VS Tools插件创建第一个Qt Widgets工程VS2019默认不能直接新建Qt工程需要先安装扩展插件。打开“扩展”菜单选“管理扩展”在联机搜索里输入“Qt Visual Studio Tools”下载安装后重启VS2019。这个过程需要VS2019关闭所有窗口插件安装完会自动重启。插件装好后新建项目时搜索“Qt”会出现“Qt Widgets Application”模板。选这个模板而不是“Qt Empty Application”因为前者会自动生成main.cpp、mainwindow.h和.ui文件省去手工搭界面的功夫。创建工程时向导会让你选Qt版本。如果下拉列表是空的点旁边的“管理”按钮手动添加D:\Qt\5.15.2\msvc2019_64这个路径。添加成功后下拉里会出现msvc2019_64。这一步是新手最容易忽略的——插件是装好了但没有告诉它Qt库在哪。工程创建完成后打开.vcxproj的属性页确认“配置属性”里的“常规”页显示的是“Visual Studio 2019 (v142)”。如果显示v141或v143后面编译会报工具集不匹配。v142是VS2019的默认工具集v143是VS2022的如果你的机器上装了多个VS版本Qt VS Tools可能生成指向错误工具集的工程文件。3.2 配置OpenCV的include、lib和运行库路径在解决方案资源管理器里右键工程名选“属性”在“VC 目录”里配置三个路径。也可以在“C/C”的“常规”里单独改include在“链接器”的“常规”里改库目录但用“VC 目录”一次性配置更直观。“包含目录”添加两项D:\opencv\build\include和D:\opencv\build\include\opencv2。其实只需前一个但后一个有时会被老的第三方头文件引用加上无妨。“库目录”添加D:\opencv\build\x64\vc15\lib。然后进入“链接器”的“输入”在“附加依赖项”里填opencv_world450d.libDebug配置或opencv_world450.libRelease配置。这里是最容易出事的位置因为很多人图省事只填一个版本导致Debug和Release切换时出现奇怪的链接错误。更规范的做法是在Debug配置里填opencv_world450d.lib切到Release配置填opencv_world450.lib。方法是在属性页左上角的配置下拉框里分别选择“Debug”和“Release”各改一次。别嫌麻烦这一步能挡掉后续大量LNK错误。最后还要把D:\opencv\build\x64\vc15\bin加到系统的PATH环境变量里或者每次运行前手动把opencv_world450d.dll复制到生成目录下。推荐加PATH因为后续调试时会频繁重建手动复制dll很快会烦到想放弃。加完PATH后记得重启VS2019否则环境变量不会生效。3.3 编译前自检验证Qt和OpenCV真的被“认”出来了配置完不要急着写处理代码先做一个最小编译测试。把VS自动生成的main.cpp替换成下面这段只包含Qt和OpenCV的头文件然后再编译#include QApplication #include QtWidgets/QLabel #include opencv2/opencv.hpp int main(int argc, char *argv[]) { QApplication app(argc, argv); cv::Mat m cv::Mat::zeros(100, 100, CV_8UC1); qDebug() rows: m.rows cols: m.cols; QLabel label(OpenCV Qt OK); label.show(); return app.exec(); }这段代码的逻辑很简单cv::Mat::zeros创建一张100x100的黑色单通道图像qDebug输出行列数再显示一个QLabel窗口。如果这段代码能编译并通过说明include路径和lib路径都对了如果连这个都过不去问题一定出在配置上而不是你的图像处理代码上。编译时注意把解决方案平台从“x86”切到“x64”。很多项目模板默认是Win32平台而你下载的OpenCV库是x64的用Win32平台链接x64库必然报错。切法是在VS工具栏的“解决方案平台”下拉框里选“x64”如果没有这个选项就进“配置管理器”新建。4. 显示图像与处理图像一个完整的QImage OpenCV实例4.1 读取图片并用QLabel显示环境通了之后开始做标题里的正事显示一张图片并处理它。先写最简单的一条链路——读图、转格式、显示。#include QApplication #include QLabel #include QImage #include QPixmap #include opencv2/opencv.hpp #include opencv2/imgcodecs.hpp #include opencv2/imgproc.hpp int main(int argc, char *argv[]) { QApplication app(argc, argv); cv::Mat src cv::imread(D:/test.jpg); if (src.empty()) { qDebug() Failed to load image, check path!; return -1; } cv::Mat rgb; cv::cvtColor(src, rgb, cv::COLOR_BGR2RGB); QImage img((const uchar*)rgb.data, rgb.cols, rgb.rows, static_castint(rgb.step), QImage::Format_RGB888); QImage copy img.copy(); QLabel label; label.setPixmap(QPixmap::fromImage(copy)); label.resize(copy.size()); label.show(); return app.exec(); }这段代码的核心是cvtColor。OpenCV默认用BGR通道顺序而QImage的Format_RGB888要求RGB顺序如果直接拿src.data构造QImage图像会变成红蓝互换的怪样子。COLOR_BGR2RGB转换之后数据顺序才对得上。另一个关键点是img.copy()。QImage用外部数据指针构造后默认不持有数据所有权如果rgb这个cv::Mat在局部作用域里析构了QImage就成了野指针。copy()会深拷贝一份数据放进QImage自己管理的内存里。丢了这一步你会遇到“图像偶尔显示、偶尔崩溃”的玄学问题。还有一点值得说明QPixmap::fromImage接收QImage后创建的是适合屏幕显示的图像对象但QPixmap的转回和传递成本较高在窗口中反复刷新时尤其明显。性能敏感场合请保留QImage形式只在绘制时临时转换。4.2 显示处理后的效果灰度化与高斯模糊的组合显示原图没问题后再加处理步骤。这里做一个最常见的操作把原图灰度化再高斯模糊把处理结果显示在另一个QLabel里。#include QApplication #include QVBoxLayout #include QLabel #include QWidget #include opencv2/opencv.hpp int main(int argc, char *argv[]) { QApplication app(argc, argv); cv::Mat src cv::imread(D:/test.jpg); if (src.empty()) return -1; cv::Mat gray, blur; // 灰度化BGR - GRAY本质是加权求和单通道 cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 高斯模糊核越大越模糊标准差0表示由核大小自动推导 cv::GaussianBlur(gray, blur, cv::Size(5, 5), 0); QImage grayImg((const uchar*)gray.data, gray.cols, gray.rows, static_castint(gray.step), QImage::Format_Grayscale8); QImage blurImg((const uchar*)blur.data, blur.cols, blur.rows, static_castint(blur.step), QImage::Format_Grayscale8); QLabel labelGray; labelGray.setPixmap(QPixmap::fromImage(grayImg.copy())); QLabel labelBlur; labelBlur.setPixmap(QPixmap::fromImage(blurImg.copy())); QWidget w; QVBoxLayout layout(w); layout.addWidget(labelGray); layout.addWidget(labelBlur); w.show(); return app.exec(); }处理链路中有一个容易被忽视的坑灰度图是单通道的构造QImage时格式必须用Format_Grayscale8如果你仍用Format_RGB888QImage会把每一行数据按3通道解释图像会严重错位并且颜色失真。这两种格式的step计算逻辑不同这也是为什么构造时不能省略rgb.step参数。GaussianBlur的参数里Size(5,5)的两个值必须是正的奇数常见的选择是3、5、7。核越大模糊越强但耗时越高。第三个参数0表示sigma由核宽度自动计算自己手填sigma反而容易模糊过渡不自然。如果你要实时处理视频帧把核降到3会快很多。4.3 在Qt界面上重复处理多张图像帧数据的管理上面的代码一次只处理一张图。实际项目里经常要连续打开多张图片或者从摄像头逐帧读取。这时要特别注意cv::Mat和QImage的生命周期。正确做法是在类的成员变量里保存cv::Mat每次更新图像时先释放旧数据再重新赋值然后用copy()深拷贝一份给QLabel显示。class ImageViewer : public QWidget { Q_OBJECT public: void updateFrame(const cv::Mat frame) { currentFrame frame.clone(); // 存一份 cv::Mat rgb; cv::cvtColor(currentFrame, rgb, cv::COLOR_BGR2RGB); QImage qimg((const uchar*)rgb.data, rgb.cols, rgb.rows, static_castint(rgb.step), QImage::Format_RGB888); label-setPixmap(QPixmap::fromImage(qimg.copy())); } private: cv::Mat currentFrame; QLabel* label; };frame.clone()把输入帧完整复制到成员变量里避免外部传入的临时Mat被释放后悬空。如果你不clone只是保存引用回调函数返回后数据就没了。每次setPixmap之前调用qimg.copy()则保证QPixmap持有独立内存不会和rgb的缓冲区纠缠。另一个性能细节在循环里频繁构造QImage和QPixmap会有不小的开销。假如只是实时预览可以预先分配好QImage缓冲区用memcpy把cv::Mat的数据拷贝进去省去QImage构造析构的成本。不过对于图像处理入门实例先把生命周期管好性能优化放到后面再说。5. VS2019环境常见避坑五个让Qt和OpenCV工程翻车的问题5.1 LNK2019无法解析的外部符号Debug和Release的库混用了现象编译报LNK2019提示无法解析的外部符号cv::imread之类有时还连带一堆__imp_开头的符号。原因附加依赖项里只填了Release版opencv_world450.lib但当前活动配置是Debug或者反过来。此外还有可能是配置成x86平台但库是x64的。解决属性页里把Debug和Release分开配置。Debug填opencv_world450d.libRelease填opencv_world450.lib。然后检查“解决方案平台”是否为x64。改完属性后重建解决方案。5.2 运行时提示找不到Qt5Cored.dll或OpenCV_world450d.dll现象编译通过了双击exe弹出系统提示找不到某个dll或者VS调试器直接报“无法启动程序”。原因运行程序时Windows会按PATH环境变量搜索依赖的dllQt的bin目录和OpenCV的bin目录没加进PATH。解决把D:\Qt\5.15.2\msvc2019_64\bin和D:\opencv\build\x64\vc15\bin追加到系统环境变量PATH里然后重启VS2019。不想改系统的做法是把相关dll拷贝到生成目录比如x64\Debug下但我建议直接加PATH一劳永逸。5.3 编译报错dependent ............\qt\5.15.2\msvc2019_64\include\qtwidgets 路径过长现象VS2019的“VC 目录”配置好后编译时出现长长一串dependent ..\..\..\..\..\qt\5.15.2\...\qtwidgets错误细看文件路径被截断了。原因Windows默认路径长度上限为260字符。Qt安装路径自身就包含Qt\5.15.2\msvc2019_64\include加上项目文件位置和VS生成的中间文件一起很容易超过限制这个报错在热词里出现频率非常高。解决把Qt安装在更短的路径下比如C:\Qt而不是C:\Program Files\Qt把工程文件也放到短路径。还可以在VS里启用在注册表中启用LongPathsEnabled需要管理员权限但最省心的还是把路径整体缩短。千万别用“..”相对路径去“猜”VS的工程转换工具经常会把这串相对路径展开成超长绝对路径。5.4 cv::imread读不出图片但不报错打开窗口一片空白现象代码执行到imread后src.empty()返回true程序不崩溃但图像显示不出来。Debug里断点查看时发现src的rows和cols都是0。原因文件路径不对或者路径里有中文。OpenCV的imread底层走的是Windows ANSI字符串接口对中文路径支持不好。另一个原因是工作目录不对相对路径是相对于VS的调试工作目录而不是exe所在目录。解决用绝对路径路径中不要有中文。调试时在VS项目属性页的“调试”里设置“工作目录”为图片所在目录这样相对路径才能按预期工作。如果你必须处理中文路径暂时换用英文目录或把图片拷到纯英文路径下这是最稳的。5.5 窗口显示但图像颜色怪异蓝色和红色互换现象图像能显示出来但颜色完全偏色原本红色的部分显示成蓝色蓝色显示成红色看起来像反面胶片。原因cv::Mat的数据顺序是BGR而QImage按RGB顺序解释内存。数据本质上没问题是两种库的通道顺序约定不同。解决在构造QImage前调用cv::cvtColor(src, rgb, cv::COLOR_BGR2RGB)。也可以直接用QImage的rgbSwapped()方法调换通道但那样会多做一次内存遍历不如在转换时一起处理。灰度图没有这个问题但格式要用Format_Grayscale8而不是RGB888。6. 从“能显示”到“能调试”验证环境真正的边界整个流程跑通后建议花十分钟做一次系统性的手动验证确认你的环境不止“能跑”而是“能控”。第一件事是在main.cpp里加一个cv::VideoCapture摄像头读帧的测试摄像头打不开不一定是你代码问题而是OpenCV没有绑定VS的SDK相关的视频后端。检查capture.isOpened()返回值如果为false先确认摄像头不被其他进程占用。第二件事是验证Debug和Release两个配置都能独立编译。很多人只测了Debug等发布时切到Release又冒出LNK错误。你可以在Release配置下重新修改附加依赖项为不带d后缀的lib然后重新生成。这一步其实应该在一开始配置时就做漏掉的请现在补上。第三件事是学会看崩溃日志。如果程序在退出时崩溃多半是析构顺序问题。我之前在开发一个视频批处理小工具时把cv::Mat成员变量放在QWidget之后声明结果关闭窗口时QWidget先析构再去释放内部的QLabel而此时Mat已经先行析构触发访问越界。把Mat声明放在类的最前面保证它最后析构问题就解决了。这种崩溃在Debug里表现明显Release有时会“假装没事”但内存泄漏已经埋下。日常调试时我习惯在QImage构造和setPixmap之间加一个qDebug()输出img.size()和img.format()。图像异常时先看这两个值能快速判断是加载失败、通道顺序问题还是尺寸超出显示范围。这个小习惯帮我节省过很多次排查时间。最后提一个方向这个实例继续往下做的自然延伸是“实时摄像头图像处理”也就是在Qt界面上用定时器循环抓帧、做边缘检测或模板匹配再显示回QLabel。VS2019下Qt和OpenCV这条路走通一次之后后面都是组合这些积木的事。希望这篇笔记能帮你在自己的机器上把这套链路搭起来少走我当年走过的弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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