
简介面向VS2019环境下的Qt与OpenCV集成开发实例适合刚接触计算机视觉与图像处理的开发者用于解决在Visual Studio中配置Qt插件与OpenCV库、显示图像并完成灰度转换和平滑滤波等基础操作的问题。压缩包共46个文件、12.34MB包含cpp/h源码、sln/vcxproj/props工程配置、tlog编译日志、jpg/png示例图片等可支撑从编译到调试运行的完整流程。目前已有867人学习下载可视为在Qt界面框架下调用OpenCV实现图像显示与处理的入门参考。除exe可执行文件外还保留了工程目录与中间输出便于对照源码理解Mat与QImage转换、QLabel控件展示等关键步骤并在此基础上扩展更多视觉算法。整体结构清晰能减少初学者在依赖库配置与项目属性上反复排查的困扰适合作为OpenCVQt学习或快速搭建图像处理项目的基础模板。1. 在 VS2019 里把 Qt 和 OpenCV 撮合到同一个工程这件事到底卡在哪做图像处理的人几乎都会撞上同一个需求算法用 OpenCV 写好了可 HighGUI 那两三个窗口实在拿不出手想用 Qt 做个像样的界面去显示图像、响应鼠标、调参数。把 VS2019、Qt、OpenCV 装到一起本以为是装三个软件的事实际做起来才发现真正的坑全在“让它们在一个工程里互相认得出对方”上。网上教程多是 Qt Creator 配 OpenCV或者是 VS 配 OpenCV 但不用 Qt真正把 VS2019 Qt OpenCV 三样串起来、按步骤走完的完整实例反而少中间还藏着 Qt 版本选错、附加依赖项列表写漏、Debug 和 Release 库混用这类能卡人一下午的细节。这篇文章按一条可复现的路径走装什么版本、每个配置项填什么值、最小代码怎么写、报错怎么解。新手照着敲能跑起来熟手也能对照着看自己的工程哪里埋了雷。2. 先搭骨架VS2019 里装 Qt 扩展与 Qt 版本的选择逻辑2.1 为什么不用 Qt Creator 而非要挤进 VS2019很多人问Qt 自己有 Creator为什么还要在 VS2019 里折腾常见理由是调试顺手、已有工程在 VS 里维护、或者团队统一开发环境。但更现实的原因是OpenCV 的 C 工程、相机 SDK 的示例代码、Halcon 的接口大多默认给 VS 准备属性表如果你主力工具已经是 VS2019非要在 Creator 里重建一套 CMake 工程反而多一层转换成本。我一般建议如果项目里只有 Qt OpenCV用 Creator 更省心如果项目里还有别的 SDK、插件、或者要和同事共享同一个 .sln那就走 VS2019 这条路。两者各有适用场景没必要互相鄙视。还有一点必须提前讲清楚在 VS2019 里做 Qt 开发需要装Qt Visual Studio Tools 扩展。这个扩展负责把 .ui 文件、moc、rcc 这些 Qt 的构建步骤接入到 MSBuild 里没有它你没法在 VS 里直接编译带.ui的工程也没法自动生成 moc 文件。2.2 Qt 版本选择msvc2019_64 是唯一不需要纠结的选项Qt 的 Windows 安装包按编译器分目录常见的有 MinGW 和 msvc2017/msvc2019 等。VS2019 的 C 编译器是 MSVC v142配套的 Qt 构建版本得是msvc2019_64这点选错后面会出现一堆 LNK 链接错误比如 msvcrt 版本冲突或无法解析的外部符号。具体下载路径是 Qt 官网或清华镜像里选 5.15.2 或 6.x。如果你要同时兼容 OpenCV 4.x 的旧接口稳妥一点选 5.15.2因为它的模块划分和 QImage 转换示例在网上最全踩坑答案也最多。6.x 不是不行只是QRegExp、部分控件 API 有变动教程匹配度要低一些。安装组件里记得勾选MSVC 2019 64-bit这一项别只装 MinGW。装完后记录一下安装路径比如D:\Qt\5.15.2\msvc2019_64后面配环境变量和工程属性都要用。2.3 用开发人员命令提示符验证 Qt 环境是否就绪环境变量QTDIR不是必需的但配上有助于后续自定义构建步骤少踩坑。设置方法环境变量里新建QTDIR指向上面那个 msvc2019_64 目录同时在Path末尾追加%QTDIR%\bin。加 Path 的目的是让程序运行时能找到Qt5Core.dll等动态库。做个快速验证打开“x64 Native Tools Command Prompt for VS 2019”输入qmake --version。如果输出QMake version 3.1之类就说明 Qt 的 qmake 你能调用了。这一步不是每个教程都会强调但能提前暴露 Qt 装错位数或路径没配好的问题。哪怕你后续用 CMake 或 VS 插件而不直接用 qmake这个验证也能帮你确认基本盘是好的。提示如果qmake提示“不是内部或外部命令”先检查Path里有没有 Qt 的 bin 目录再检查位数是不是 64 位。此现象大概率是环境变量没生效重开终端再测。3. OpenCV 的准备下载、环境变量与一套能复用的属性表3.1 OpenCV Windows 包的目录结构你需要哪个目录说过很多次还是有人填错从官网或镜像下载 OpenCV 4.x Windows 版解压后是一个opencv文件夹。里面真正有货的是build它的结构大致是build\include\opencv2头文件目录包含全部模块的头文件。build\x64\vc15\lib静态库.lib文件注意 vc15 对应 VS2017/2019v142 工具集兼容 vc15 的库。build\x64\vc15\bin运行时要加载的.dll文件。初学者最容易犯的错是去build\x86或build\mingw里找库。既然 VS2019 工程是 x64 平台就必须用x64\vc15。如果你下载的是 OpenCV 3.x目录里叫vc14或vc15道理一样。3.2 环境变量 OPENCV_DIR 与 Path不是必须但能救你于水火OpenCV 不像 Qt 那样强制要求环境变量因为 VS 工程里可以直接写绝对路径。但把路径写死在工程里有个坏处换台电脑、换个人拉代码路径全要改一遍。我习惯的做法是设置系统环境变量OPENCV_DIR指向D:\opencv\build然后在工程属性里用$(OPENCV_DIR)引用。这样整个团队只需要统一一个变量名。同时把%OPENCV_DIR%\x64\vc15\bin加进Path。这一步决定程序运行时能否找到opencv_world4xx.dll。有人问“我编译过了为什么双击 exe 报错找不到 DLL”多半就是这一步漏了。OpenCV 属于运行时依赖 DLL 的库VS 调试时会把 exe 所在目录和系统 DLL 搜索路径都找一遍Path里没有它就直接崩。3.3 VS2019 属性表的设置把 OpenCV 的 include、lib、附加依赖项一次配好打开 VS2019新建一个“Qt Widgets Application”或直接从“控制台应用”改造成 Qt 工程这里推荐用 Qt 扩展自带的工程模板省去手动加 moc 的麻烦。建好工程后打开“属性管理器”不是“属性页”在 Debug | x64 配置下右键添加“属性表”比如命名为OpenCV.props。然后在属性表里设置三项第一VC 目录 - 包含目录填入$(OPENCV_DIR)\include注意不要写成$(OPENCV_DIR)\include\opencv2或opencv三个头文件子目录是平级关系。第二VC 目录 - 库目录填入$(OPENCV_DIR)\x64\vc15\lib第三链接器 - 输入 - 附加依赖项填入opencv_world4xx.libDebug 版用opencv_world4xxd.lib带 d 后缀这里的 4xx 对应你下载的版本号比如 4.5.5 就写opencv_world455d.lib。稍微留意一下 Debug 和 Release 的库后缀混用会触发“_ITERATOR_DEBUG_LEVEL”之类的报错。把这三个配置做好后保存属性表以后新建工程直接添加这个 .props 就能复用。4. OpenCV 与 Qt 的数据桥Mat、QImage 互转与第一个可运行实例4.1 图像显示的本质问题OpenCV 的 Mat 和 Qt 的 QImage 是两套东西在写代码之前得先明白一个基础问题OpenCV 读进来的图像是cv::MatQt 能显示的是QImage或QPixmap两者内存布局不同不能直接赋值。Mat 默认是 BGR 三通道连续存储而 QImage 常见格式是 RGB888、ARGB32还有每行字节对齐的问题。所以“在 VS2019 里用 Qt 显示 OpenCV 图像”的核心工作就是写一个转换函数。这个函数写一次后面所有图像显示、视频帧显示全部复用。转换的逻辑不复杂先根据 Mat 的通道数和深度判断要转成哪种 QImage 格式再把数据拷贝过去注意要把 BGR 的顺序转成 RGB。最简单可靠的办法是用cv::cvtColor先把 Mat 转成 RGB再构造 QImage。4.2 最小可用的转换函数与信号槽显示链路下面这段代码是我在项目里一直沿用的转换函数兼容 8UC3 和 8UC1 两种最常见的图像类型// mat_to_qimage.h #pragma once #include QImage #include opencv2/opencv.hpp inline QImage cvMatToQImage(const cv::Mat mat) { if (mat.type() CV_8UC3) { // BGR 转 RGB否则显示出来蓝色和红色是反的 cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); // 注意 QImage 的 scanline 和 Mat 的 data 对齐方式可能不同 // 这里用拷贝构造确保 QImage 拥有独立内存避免悬空 return QImage(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888).copy(); } else if (mat.type() CV_8UC1) { // 单通道灰度图直接映射不需要转格式 return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8).copy(); } return QImage(); }这段代码的第一处关键点是cv::cvtColor把 BGR 转成 RGB不做这一步Qt 显示出来的图像颜色会偏蓝偏红。第二处关键点是.copy()因为QImage的构造函数只包装了 Mat 的内存指针如果 Mat 在函数返回后析构了QImage 就成了悬空指针显示时会出现花屏或崩溃。.copy()是把图像数据完整复制一份代价是每帧多一次内存拷贝但对于显示场景这点开销完全可以接受换来的是安全。有了转换函数之后主界面的逻辑就很简单了。在窗口类里放一个QLabel作为显示区域用QFileDialog选择图片读取后用上面的函数转成 QImage再setPixmap显示。下面是一个最小完整的处理流程// MainWindow::onLoadImage 槽函数 void MainWindow::onLoadImage() { QString filePath QFileDialog::getOpenFileName( this, 选择图片, , Images (*.png *.jpg *.bmp)); if (filePath.isEmpty()) { return; } // 用 OpenCV 读图IMREAD_COLOR 读成三通道 BGR m_srcMat cv::imread(filePath.toLocal8Bit().toStdString(), cv::IMREAD_COLOR); if (m_srcMat.empty()) { // 文件存在但解码失败时会走到这里 statusBar()-showMessage(无法读取图片请检查文件格式); return; } // 转成 QImage 并显示 QImage img cvMatToQImage(m_srcMat); QPixmap pixmap QPixmap::fromImage(img); // setScaledContents 设置成 true 后图片会拉伸填满 label // 但可能变形等比缩放建议用 scaled() 手动处理 ui-labelDisplay-setPixmap(pixmap); ui-labelDisplay-setScaledContents(false); statusBar()-showMessage(QString(已加载 %1尺寸 %2x%3) .arg(filePath) .arg(m_srcMat.cols) .arg(m_srcMat.rows)); }这里值得展开说的是toLocal8Bit()。Visual Studio 2019 默认的工程字符集是 Unicodecv::imread接受的是窄字符串路径。如果图片路径里有中文直接用toStdString()转出来的 UTF-8 路径在 Windows 中文系统上会读取失败必须用toLocal8Bit()转成当前代码页的窄字符串。这个问题在中文 Windows 上极其常见属于那种“别人能跑你不能跑”的经典案例。再把处理函数补上比如做一次高斯模糊void MainWindow::onProcessImage() { if (m_srcMat.empty()) { statusBar()-showMessage(请先加载一张图片); return; } cv::Mat blurred; // 5x5 高斯核sigma 取 0 时由核尺寸自动计算 cv::GaussianBlur(m_srcMat, blurred, cv::Size(5, 5), 0); QImage img cvMatToQImage(blurred); ui-labelDisplay-setPixmap(QPixmap::fromImage(img)); }GaussianBlur的第三个参数是核尺寸必须是正奇数。第五个参数是标准差传 0 表示根据核尺寸自动算。把“加载原图”和“模糊处理”放到两个按钮的点击信号里整个实例的骨架就闭合了。跑起来之后用 OpenCV 读图、转 QImage、Qt 显示、再处理、再显示这一整条链路就通了。5. 避坑指南VS2019 Qt OpenCV 最常见的 5 个报错与排查路径5.1 报:-1: error: dependent ..\..\Qt\5.15.2\msvc2019_64\include\QtWidgets does not exist这个报错在网上的出现频率很高现象是编译刚开始就失败提示某个 Qt include 路径不存在。原因几乎都是 Qt 安装路径和工程里记录的路径不一致有人换过 Qt 版本、有人把 Qt 装到 D 盘但工程里写的是 C 盘、还有人目录层级不对。解决方法是右键工程 - “Qt Project Settings”检查Qt Installation下拉框选中的是不是当前可用的版本。如果列表里是空的说明 Qt VS Tools 没有检测到已安装的 Qt回到“扩展 - Qt VS Tools - Qt Versions”里手动添加路径指向msvc2019_64目录。这里有个小技巧添加版本时路径要精确到msvc2019_64不要只给到5.15.2。5.2 链接阶段报一堆 LNK2019 无法解析的外部符号这是 OpenCV 库配置问题中最多的一个。现象是编译过了链接时报LNK2019 unresolved external symbol符号名里带着cv::前缀。最常见的原因是附加依赖项写错了Debug 配置里用了不带 d 的 release 库或 Release 里用了带 d 的 debug 库还有一种是把库文件路径指到了vc14但工程是 VS2019。解决办法是打开属性管理器确认当前活动配置。在 Debug 下确保附加依赖项是opencv_world4xxd.libRelease 下是opencv_world4xx.lib。如果两种库都不想区分最粗暴的做法是在附加依赖项里把两个都写上编译时能自动匹配但这不是规范做法治标不治本。5.3 运行时弹窗“找不到 opencv_world455d.dll”编译都过了但一按 F5 就弹这个错。原因非常直白exe 启动时需要加载 OpenCV 的 DLL系统在 exe 目录、系统目录、Path 环境变量里都没找到。前面配好的%OPENCV_DIR%\x64\vc15\bin如果没有生效就会看到这个弹窗。处理方式按优先级一共三种第一种是把bin目录加入系统Path重开 VS2019第二种是把 DLL 直接复制到 exe 输出目录比如x64\Debug但每换一次版本就要复制一次第三种是在工程属性 - 调试 - 环境里写PATH%OPENCV_DIR%\x64\vc15\bin;%PATH%这样只有调试时生效发布时再单独处理。我建议用第一种因为后面跑视频、跑相机都会依赖这个 DLL 搜索路径。5.4 图像颜色发蓝或发红BGR 和 RGB 通道序没转这不是崩溃报错是“看起来错了”。现象是原图是正常彩色显示出来红蓝互换。原因是 OpenCV 的imread默认把图读成 BGR 三通道而 QImage::Format_RGB888 要求内存里是 RGB 顺序。不转换直接包装数据Qt 把 B 通道当成 R 显示颜色自然偏。解决就是在cvMatToQImage里加一次cvtColor(mat, rgb, cv::COLOR_BGR2RGB)。这个坑不算难但几乎每个刚接触 Qt OpenCV 的人都会踩一次属于“不出问题想不到一出问题一眼懵”的类型。5.5 Debug 模式下 QImage 显示花屏、程序崩溃Mat 和 QImage 生命周期没管理好用QImage(mat.data, ...)构造出的 QImage 并不持有图像数据它只是把 Mat 的内存地址记下来。函数返回后 Mat 被释放QImage 就变成悬空指针。Qt 的QLabel::setPixmap内部会做拷贝但如果你在信号槽连接时不注意生命周期中间某个环节用到了悬空对象就会花屏或崩溃。解决方法是前面代码里已体现的.copy()。注意看 QImage 的构造里用了bytesPerLine这个参数传成rgb.step这个细节也很关键。Mat 的每一行有字节对齐QImage 同样有行字节数要求不对称可能导致图像倾斜或错位。如果不想每帧都.copy()多一次内存拷贝有一个优化思路把 QImage 构造在堆上并用智能指针管理或者让 Mat 的生命周期长于 QImage。但相比崩溃风险我认为.copy()的浅代价是最值得做的安全牌。6. 进阶玩法视频帧实时显示与 Mat 到 QImage 的零拷贝优化跑通单张图片之后下一步十有八九是接摄像头或者读视频流。视频帧的处理和静态图一样需要 Mat 转 QImage但多了一个频率问题每秒 25 帧到 30 帧每帧都做一次cvtColor加一次.copy()CPU 占用会明显上升。如果你的处理算法本身就重转格式这部分开销就更扎眼。这里给一个优化后的转换版本inline QImage cvMatToQImageFast(const cv::Mat mat) { if (mat.type() CV_8UC3) { cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); return QImage(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888).copy(); } return QImage(); }这个函数单看和前面版本没有本质区别。真的零拷贝方案是把 Mat 的通道顺序改成 RGB 后把它的数据指针交给 QImage并确保 Mat 在 QImage 使用期间不释放。一种可靠的做法是把 Mat 存成成员变量m_frameMat在更新界面时先转 RGB、再赋给成员、再构造 QImage// 成员变量声明cv::Mat m_showRgb; void MainWindow::updateFrame(const cv::Mat bgrFrame) { // 避免每帧重新分配内存能复用的 Mat 尽量复用 cv::cvtColor(bgrFrame, m_showRgb, cv::COLOR_BGR2RGB); QImage image(m_showRgb.data, m_showRgb.cols, m_showRgb.rows, m_showRgb.step, QImage::Format_RGB888); // 注意这里不能 .copy()必须保证 m_showRgb 在下次更新前不被改写 ui-labelDisplay-setPixmap(QPixmap::fromImage(image)); }这里有个取舍你要想清楚如果每帧都.copy()界面线程用到的 QImage 就是独立数据不会因为下一帧来了而被覆盖如果为了性能去掉.copy()就必须保证这个函数是同步执行的界面完成绘制之前下一帧的数据不能被写入。Qt 里setPixmap是异步的内部会拷贝 pixmap 数据所以严格来说 QImage 的生存期只需要覆盖到setPixmap调用结束。另外要补充一个 QTimer 驱动视频帧的思路这样做比用单独的线程简单可控得多// 定时器每 33ms 触发一次约 30 帧每秒 m_timer new QTimer(this); m_timer-setInterval(33); connect(m_timer, QTimer::timeout, this, MainWindow::onCaptureFrame);然后在onCaptureFrame里从VideoCapture读取一帧、处理、显示。这个方案只适用于处理时间足够快的场景。如果算法要 100ms 才能算完一帧就别用这个方案了得换成工作线程 信号槽把处理结果发回界面线程避免界面卡顿。这是另一个话题但方向是清晰的Qt 负责界面和事件循环OpenCV 负责图像计算两者通过信号和槽做隔离。验证你的工程是否稳定的一个直接标准加载一张大图连续切换灰度、模糊、边缘检测等操作几十次再切回原图。如果出现过一次花屏、崩溃或颜色错乱就回去检查上面这几条。我自己的习惯是写完这个最小实例后再补一个“视频播放”按钮把这种实时链路也验证一遍因为静态图能跑通不代表视频链路稳定尤其是 Mat 生命周期问题往往在高速刷新时才暴露。项目里踩过两三次坑之后我把上面几点整理成了团队的 Qt OpenCV 工程初始化清单新人照着配一遍基本不会再被这些基础问题绊住。希望这份整理能帮你把 VS2019 Qt OpenCV 的环境一次配稳把更多时间花在图像算法本身而不是工具链上。本文还有配套的精品资源点击获取