ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VS2019配置Qt+OpenCV:Mat转QImage与Canny边缘检测实战

VS2019配置Qt+OpenCV:Mat转QImage与Canny边缘检测实战 简介在VS2019中结合Qt与OpenCV实现图像显示与处理的实战工程适合刚开始接触计算机视觉、希望掌握Qt界面与OpenCV算法集成的开发者也可用于课程设计或视觉应用原型验证。整个工程以Qt Widgets程序为主线从环境准备到核心代码均给出可参考样例包含cpp/h源码同时提供sln/vcxproj/props等工程配置exe/pdb/obj等编译结果以及jpg/png测试图像压缩包约12.34MB共46个文件目录组织清晰方便直接打开、编译和修改。目前已有867人学习下载。工程源码实现了OpenCV Mat与Qt QImage的相互转换通过QLabel控件展示图像并包含转灰度、平滑滤波等基础图像处理示例项目属性中已配置OpenCV依赖路径接口衔接方式可直接沿用关键代码便于定位。读者可借助这些代码快速掌握在VS2019下组合Qt与OpenCV的常见流程减少环境配置与API调用上的弯路无论是学习计算机视觉还是搭建实际界面都能从这套可直接运行的工程中获得有效参考。1. 先把环境走通VS2019 里让 Qt 和 OpenCV 首次对话很多做图像处理的人在 VS2019 里第一次同时用 Qt 和 OpenCV不是被算法难住而是被环境卡住Qt 装好了工程却编译不过OpenCV 的头文件怎么都链不进去好不容易跑起来图像还偏色。这篇文章就沿着「在 VS2019 中显示图像和处理图像测试 qt 和 opencv」这条最小路径走一遍选对版本、配好工程、把 OpenCV 的 Mat 转成 Qt 的 QImage 并显示再点按钮做灰度化和 Canny 边缘检测。适合刚接触 C 图像处理、准备做课程设计或毕设以及想把算法快速搬到界面上的开发者。下文每一步都会给出参数和翻车点照着做就能把环境打通。2. 版本选型是决定成败的第一步VS2019、Qt 5.15.2 与 OpenCV 4.x 怎么搭2.1 VS2019 对应 Qt 哪个版本直接选 msvc2019_64 套件VS2019 默认的 C 工具集是 MSVC v142Qt 官方从 5.12 开始为它出过配好的套件。我在给新机器装这套组合时一般直接上 Qt 5.15.2 的 msvc2019_64。理由很简单5.15.2 是 Qt 5 分支里补丁最完整的一版网上教程和踩坑记录最多遇到问题搜得到答案。5.15.0 或 5.15.1 也有不少人在用但部分版本有字体渲染和 QImage 格式相关的边角 bug没必要为了省一个小版本号给自己添堵。Qt 6 不是不能配 VS2019但这里我明确不建议新手上来就选 Qt 6。OpenCV 本身与 Qt 版本没有绑定你完全可以在 Qt 6 里用 OpenCV可老教程里 QImage::Format_RGB888、QImage 构造参数这些接口在 Qt 5 上资料最全照着写不容易跑偏。等 Qt 5 这套打通再换 Qt 6 就是改几个类名的事。需要重点强调的是 64 位。Qt 安装器里写得很清楚msvc2019_64OpenCV 官方预编译包默认也只提供 x64 版本。如果你机器上还有老旧项目依赖 32 位 dll强烈建议单独开一台虚拟机来跑不要让 32 位库和 64 位库混在同一个 VS 工程里。装 Qt 时注意勾选项在 Qt 5.15.2 下只勾 MSVC 2019 64-bit 组件即可别顺手把 Android、iOS、Qt Charts 等用不上的模块全勾上。这些模块下载体积大而且个别模块的安装脚本在部分网络环境下会拖慢整个安装过程。VS2019 安装时选「使用 C 的桌面开发」工作负载右侧细节里勾上 Windows 10 SDK 和 CMake 工具其他保持默认。安装项勾选建议理由Qt 5.15.2 / MSVC 2019 64-bit必选与 VS2019 v142 工具集匹配Qt Debug Symbols可选出问题想定位到 Qt 源码时有用VS2019 C 桌面开发必选提供 MSVC 编译器与 Windows SDKWindows 10 SDK必选编译 Qt 工程需要CMake 工具建议勾选后续可能要用 CMake 方式引用 OpenCV提示装完 Qt 后第一件事是打开「Qt 5.15.2 (MSVC 2019 64-bit)」这个命令行快捷方式在里面运行 qmake -v确认 qmake 指向的是 msvc2019_64 而不是 MinGW 套件。2.2 OpenCV 安装教程官方预编译包与 CMake 编译如何取舍大多数人的需求只是读图、灰度化、边缘检测那直接用官方预编译包最快。下载 opencv-4.x.x-windows.exe双击解压到 D:\opencv里面 build\x64\vc16 就是给 VS2019 用的库目录。vc16 对应 VS2019 的 MSVC 版本VS2022 装的是 vc17 工具集直接链 vc16 的库多数情况下也能用。那什么时候要走 opencv cmake 编译步骤我一般只在三种情况才自己编第一要用 opencv_contrib 里的扩展模块比如 SIFT、KAZE、Aruco第二要编译 CUDA 版本让 GPU 加速第三要用 MinGW 编译器配套 OpenCV因为官方预编译包只有 MSVC 版。如果你只是跟着这个标题做「显示图像 处理图像」预编译包就够了不需要把时间花在 CMake 上。万一你真要自编译命令也不复杂。假设源码在 D:\opencv\sources先建一个 build 目录再执行 CMake 配置cmake -G Visual Studio 16 2019 -A x64 ^ -DCMAKE_BUILD_TYPERelease ^ -DBUILD_opencv_worldON ^ -DWITH_CUDAOFF ^ -DWITH_OPENGLON ^ -DOPENCV_EXTRA_MODULES_PATHD:/opencv/contrib/modules ^ D:/opencv/sources说明一下几个关键参数。-G Visual Studio 16 2019 指定生成 VS2019 工程VS2022 要换成 Visual Studio 17-A x64 强制 64 位目标避免后面连库的时候平台不一致BUILD_opencv_worldON 会把所有模块合并成一个 opencv_world.lib后续 Qt 工程链接时只写一个库名省去逐个模块添加的麻烦WITH_CUDAOFF 是告诉 CMake 别探测 CUDA 环境没装 CUDA 的机器上不关这一项配置阶段就会报错OPENCV_EXTRA_MODULES_PATH 只有在需要 contrib 时才填不需要就删掉这一行。CMake 配置完成后再执行cmake --build . --config Release --target INSTALL编译输出的库会集中到 install\x64\vc16\lib 下面。这个过程慢则半小时快则十来分钟取决于机器配置。之后把这 install 目录当成 OpenCV 根目录看待就行第 3 章的 .pro 路径指向这里。2.3 PATH 与 Qt VS Tools装完不等于环境配好预编译包解压完下一步不是立刻写代码而是把三个环境相关的配置一次性做掉不然运行阶段会反复报「找不到 dll」。第一把 D:\opencv\build\x64\vc16\bin 加进系统 PATH否则程序跑起来会提示找不到 opencv_world480.dll。第二Qt 的 bin 目录C:\Qt\5.15.2\msvc2019_64\bin也建议加进 PATH方便在任意终端里调用 windeployqt 等工具。第三VS2019 里需要装 Qt Visual Studio Tools 扩展。这个扩展装完后在 VS 里才能直接创建 Qt Widgets Application 工程且能在 VS 的菜单里配置 Qt 版本。配置路径扩展 → Qt VS Tools → Qt Versions把 Qt 版本路径指到 C:\Qt\5.15.2\msvc2019_64。注意这里选的是套件根目录不是 bin 目录填错会让 VS 提示找不到 qmake。最后一个检查点确认 VS 工程属性里的平台是 x64而不是 Win32。这个平台设置会在 qmake、CMake 和链接器之间传递一旦错位会出现头文件找得到、库却链接失败的情况。很多新手在这一步反复折腾其实是 32 位和 64 位在打架。3. 把 Qt 和 OpenCV 链进同一个工程.pro 配置与 Mat 转 QImage3.1 用 Qt VS Tools 创建 Widgets 工程并写对 .pro打开 VS2019执行 扩展 → Qt VS Tools → Create New Qt Project选择 Qt Widgets Application。VS 会自动生成一个带 .ui 文件和 main.cpp 的工程。如果你更习惯从空 C 项目开始也可以右键项目 → Qt Project Settings把 qmake 和 Qt 版本指过去效果一样。接下来把 OpenCV 的头文件和库写进 .pro。这里贴一份我常用的最小配置# Qt 模块core/gui/widgets 足够显示图像和按钮 QT core gui widgets CONFIG c11 # 改成你自己的 OpenCV 解压路径路径中别出现空格 INCLUDEPATH D:/opencv/build/include CONFIG(debug, debug|release) { # 注意 d 后缀Debug 版本链接带 d 的库 LIBS -LD:/opencv/build/x64/vc16/lib -lopencv_world480d } else { LIBS -LD:/opencv/build/x64/vc16/lib -lopencv_world480 }逐行解释一下。INCLUDEPATH 让编译器在 #include opencv2/opencv.hpp 时能找到头文件路径统一用正斜杠避免 C 字符串里反斜杠转义的麻烦。CONFIG(debug, debug|release) 是 qmake 的条件判断语法它保证 Debug 工程只链接带 d 的调试库Release 工程只链接 release 库。这个区分非常重要MSVC 下 debug 和 release 的运行时库不兼容混着链接轻则告警重则运行崩溃我在第 5 章会专门展开。如果你在 VS 里用的是 CMake 工程而不是 qmake那这一章的思路依然适用。CMake 方案一般这样写find_package(OpenCV REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE ${OpenCV_LIBS})Qt 这边通过 CMAKE_PREFIX_PATH 指明 Qt 套件根目录。两种方式都能跑但 qmake 方案与 Qt Creator、Qt VS Tools 的配合最顺下文代码统一按 qmake 工程来写。3.2 图像显示的核心转换matToQImage 的通道顺序与内存拷贝配置好工程后先解决 Qt 和 OpenCV 之间显示图像的核心问题OpenCV 读进来的图像是 BGR 三通道而 Qt 的 QImage 默认按 RGB 解释直接拿 Mat 的 data 指针构造 QImage你会看到一张整体发蓝的图。这里我习惯封装一个转换函数所有显示入口都走它// matToQImageOpenCV Mat 转 Qt QImage QImage matToQImage(const cv::Mat mat) { switch (mat.type()) { case CV_8UC3: { // BGR - RGBOpenCV 默认三通道顺序与 Qt 相反 cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); return QImage(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888).copy(); } case CV_8UC1: { // 单通道灰度图直接用 Format_Grayscale8 return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8).copy(); } default: // 其他类型如 CV_16U、CV_32F先转 8UC1 再显示 return QImage(); } }这里有两个关键点。第一CV_8UC3 分支必须经过 cvtColor 把 BGR 换成 RGB灰度图没有通道顺序问题直接走 CV_8UC1 分支。第二QImage 构造出来之后一定要 .copy()。带 data 指针的 QImage 构造函数默认不拷贝像素数据它只是引用外部内存等 Mat 被重新赋值或函数返回后这块内存可能已被释放界面上的图就会花掉。copy() 是处理这类问题的“后悔药”代价是多一次内存拷贝但对单张图片显示来说完全可以接受。另外值得留意的是 mat.step 参数。QImage 构造函数里有一个 bytesPerLine 参数对应 Mat 的 step。如果图像宽度恰好是 4 字节对齐不传也能显示一旦图像宽度不对齐不传 step 就会看到图像呈斜纹状或只有部分显示。所以封装函数里始终带上 step 是最稳妥的写法。3.3 最小可运行代码在 QLabel 里显示第一张图有了 matToQImage读图并显示到 QLabel 上的代码非常短#include QApplication #include QLabel #include opencv2/opencv.hpp int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label; cv::Mat mat cv::imread(D:/test.jpg, cv::IMREAD_COLOR); if (mat.empty()) { label.setText(图片打开失败请检查路径); } else { // 显示路径走统一的 matToQImage label.setPixmap(QPixmap::fromImage(matToQImage(mat))); } label.show(); return app.exec(); }cv::imread 的第二个参数 IMREAD_COLOR 表示以三通道彩色方式读入读进来就是 CV_8UC3 的 BGR 图。路径这里故意写成 D:/test.jpg 而不是带反斜杠的 Windows 路径因为在 C 字符串里反斜杠会被当成转义符写 D:\test.jpg 虽然也行但正斜杠更省眼。empty() 判断比文件是否存在更可靠因为 imread 遇到文件被占用、格式不支持等情况都会返回空 Mat。运行起来后窗口里出现一张图说明 VS2019、Qt、OpenCV 三者已经打通。接下来把它扩展成带按钮和多种处理结果的图像处理测试工程。4. 做一个图像处理测试界面打开图片、灰度化、Canny 边缘检测三连4.1 用 Qt Designer 把界面搭起来控件命名与信号槽绑定这个测试界面不需要复杂布局。我一般放三个按钮打开图片、灰度、Canny、两个 QLabel原图显示区、处理结果显示区。在 VS2019 里双击工程里的 .ui 文件会打开 Qt Designer 界面设计器从左侧控件栏拖进来就行。为了让信号槽生效几个控件的 objectName 必须认真取。Qt Designer 自动连接槽的机制是槽函数名写成 on_ _信号名比如按钮 btnOpen 的 clicked 信号对应 on_btnOpen_clicked()。我用这几个名称控件objectName作用QPushButtonbtnOpen打开图片QPushButtonbtnGray转灰度QPushButtonbtnCannyCanny 边缘检测QLabellabelShow显示原图QLabellabelResult显示处理结果布局上用两个水平布局并排装 label外侧套垂直布局把按钮和图像区上下分开。特别提醒大图直接放在 QLabel 里会把窗口撑爆最好把 label 放进 QScrollArea或对 label 调用 setScaledContents(true)。前者保持原始大小可拖动后者简单省事但会拉伸变形。我习惯先 setScaledContents(true) 保证界面不崩等算法验证通过再优化缩放逻辑。4.2 打开图片并保留原图imread 与中文路径在 Widget 类的头文件里声明一个成员 cv::Mat m_src; 用来保存原始图像后续灰度、边缘检测都从 m_src 重新处理而不是在上一张结果上继续叠加。打开图片的槽函数如下void ImageWidget::on_btnOpen_clicked() { QString file QFileDialog::getOpenFileName( this, 选择图片, QString(), Images (*.jpg *.png *.bmp)); if (file.isEmpty()) { return; } // 中文路径不能直接用 toStdString m_src cv::imread(file.toLocal8Bit().toStdString(), cv::IMREAD_COLOR); if (m_src.empty()) { QMessageBox::warning(this, 提示, 图片读取失败); return; } ui-labelShow-setPixmap(QPixmap::fromImage(matToQImage(m_src))); ui-labelResult-clear(); }这里有一个非常容易踩的坑cv::imread 在 Windows 上对中文路径支持不好。如果你的图片路径里包含“测试.jpg”这类中文字符直接调 file.toStdString() 再传给 imread 经常返回空 Mat。用 toLocal8Bit() 转成本地编码的字节串大多数情况下能解决。更稳妥的方案是用 std::ifstream 配合 imdecode 读取但作为测试实例toLocal8Bit 已经覆盖 90% 的场景。原图显示到 labelShow 后m_src 就作为唯一数据源。4.3 灰度与 Canny 边缘检测处理结果实时上屏灰度化是这个测试工程里最基础的处理。OpenCV 的 cvtColor 一次调用即可void ImageWidget::on_btnGray_clicked() { if (m_src.empty()) { return; } cv::Mat gray; cv::cvtColor(m_src, gray, cv::COLOR_BGR2GRAY); ui-labelResult-setPixmap(QPixmap::fromImage(matToQImage(gray))); ui-labelResult-setScaledContents(true); }cvtColor 的 COLOR_BGR2GRAY 按 BT.601 加权公式把三通道合成单通道得到的就是 CV_8UC1 灰度图。灰度图进入 matToQImage 后走 CV_8UC1 分支不需要再做颜色转换。边缘检测用 Canny。它内部先计算梯度幅值和方向再用双阈值做滞后阈值处理找到图像中梯度变化明显的像素连成边缘。代码结构跟灰度几乎一样void ImageWidget::on_btnCanny_clicked() { if (m_src.empty()) { return; } cv::Mat gray, edges; cv::cvtColor(m_src, gray, cv::COLOR_BGR2GRAY); // 双阈值 50 和 150比例 1:3 cv::Canny(gray, edges, 50, 150); ui-labelResult-setPixmap(QPixmap::fromImage(matToQImage(edges))); }Canny 的两个阈值参数值得说几句。低阈值 50 决定哪些梯度变化能成为边缘候选高阈值 150 决定哪些强边缘直接保留。介于两者之间的边缘只有与强边缘相连才会被保留。经验上低阈值与高阈值比例取 1:2 到 1:3 比较合理50/150 就在这个范围。如果画面噪声多先加一步 cv::GaussianBlur(gray, gray, cv::Size(3, 3), 0) 再调 Canny边缘会干净不少。后面如果你要做直线检测、轮廓提取处理结果都可以像这样直接在 labelResult 上更新套路是一致的。5. 避坑VS2019 Qt OpenCV 最常见的 5 个翻车现场5.1 编译期与链接期的两个经典报错第 1 个常见报错是 fatal: cannot mix incompatible qt library (version ex50601) with this library。现象程序在编译或链接时突然报出这个 fatal 错误后面通常还跟着一堆版本号信息有时候 qmake 也能过一编译就挂。原因是工程里混用了两个不同版本的 Qt。ex50601 是 Qt 5.6.1 的版本标记而当前工程用的是 Qt 5.15.2说明编译器拿到的头文件或库文件来自另一个套件。常见诱因是机器上装了多个 Qt 版本qmake 的查找路径先后顺序不对或者 CMake 里 CMAKE_PREFIX_PATH 指到了旧版本 Qt。解决先在命令行运行 qmake -v 确认当前生效的 qmake 指向哪个套件再检查 VS 里 Qt VS Tools → Qt Versions 的配置是不是 msvc2019_64最后检查 .pro 或 CMakeLists 里有没有手写的 Qt include 路径有就删掉。把不用的 Qt 版本移出 PATH能从根上消除这类冲突。第 2 个报错是 LNK2019 无法解析的外部符号 cv::imread。现象include 和代码都没问题编译能过但链接时报一堆 unresolved external symbol指向 imread、cvtColor 等函数。原因是链接器没找到 OpenCV 的库。常见情况有两种一是 Debug 工程里链接了不带 d 的 release 库或反之二是“附加库目录”写错VS 在 x64 平台下却去 Win32 的 lib 目录找库。这类错误看着像玄学实际是配置偏差。解决把链接器输入整理成两张表。Debug 配置用 opencv_world480d.lib、Release 用 opencv_world480.lib同时确认项目属性 → 配置属性 → 链接器 → 常规 → 附加库目录指向 D:\opencv\build\x64\vc16\lib且解决方案平台是 x64。qmake 工程里用第 3 章的 CONFIG(debug, debug|release) 写法也能规避。5.2 运行期的三个地雷platform 插件、偏色和 debug/release 混用第 3 个坑程序启动时弹窗 qt.qpa.plugin: could not find the qt platform plugin “windows”。现象在 VS2019 里按 F5程序还没进 main 就报错退出错误信息提示找不到 windows 平台插件。原因是 Qt 的 plugins\platforms\qwindows.dll 没在加载路径里。VS 调试时默认不会把 Qt 的插件目录加进可执行文件的搜索路径程序自然找不到。解决优先使用 Qt 安装目录下的「Qt 5.15.2 (MSVC 2019 64-bit)」命令行快捷方式启动 VS它会自动设置好 Qt 环境或者在 exe 同目录放一个 qt.conf 文件[Paths] Plugins C:/Qt/5.15.2/msvc2019_64/plugins发布程序时更专业的做法是用 windeployqt 自动收集插件这个放到第 6 章讲。第 4 个坑图像显示偏色人物肤色发蓝。现象图片能显示但整体色调不对红色区域变成蓝紫色。原因是 OpenCV 读入的图片是三通道 BGRQt 的 QImage 按 RGB 解释红色通道和蓝色通道换了个位置。很多教程在 imread 后直接构造 QImage就会出现这种问题。解决显示前必须做一次 cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB)。我在第 3 章封装 matToQImage 时已经把转换写进去了只要所有显示入口都走这个函数就不会出现偏色。如果你是在其他工程里复制代码留意一下是否漏了这一步。第 5 个坑Debug 正常Release 崩或者反过来。现象同一份代码Debug 编译运行正常Release 编译也通过但运行几十秒后崩溃报错常见为访问到无效内存。原因是 debug 库和 release 库混用了。MSVC 下 Debug 配置默认链接动态运行时库的调试版本Release 配置链接发布版本这两种库的内存管理不能混。如果工程在 Debug 下链接了 OpenCV 的 release 库或 Qt 的 debug 库与 release 库交错就会出现这种“时好时坏”的崩溃。解决把 Qt 和 OpenCV 的库都按 Debug/Release 严格分开。Qt 的 debug 库带 d 后缀比如 Qt5Cored.dllOpenCV 的 debug 库就是 opencv_world480d.lib。用第 3 章的 qmake 条件写法可以一劳永逸避免这种手滑。提示遇到“编译正常、显示斜纹或只有部分图像”的问题优先检查 QImage 构造时有没有传 bytesPerLine对应 Mat 的 step。这是最容易忽略的参数不传就会出这类半张图的怪问题。6. 把测试实例升级成顺手的小工具滑动条调参、耗时验证与发布界面和基础处理打通后最值得加的功能是根据图像实时调 Canny 阈值。放一个 QSlider 和 QLabel 显示当前阈值滑动时直接触发处理void ImageWidget::on_sliderThreshold_valueChanged(int value) { if (m_src.empty()) { return; } cv::Mat gray, edges; cv::cvtColor(m_src, gray, cv::COLOR_BGR2GRAY); // 低阈值取高阈值的 1/3随滑动条整体变化 cv::Canny(gray, edges, value / 3, value); ui-labelResult-setPixmap(QPixmap::fromImage(matToQImage(edges))); ui-labelThreshold-setText(QString(Canny 高阈值: %1).arg(value)); }滑动条范围建议设 30 到 250让低阈值在 1083 之间变化。这样你能直观看到低阈值太小时边缘碎片很多、阈值太大时边缘信息丢失的过程比单纯看示例代码更清楚。验证处理耗时用 QElapsedTimer 就够了不必引额外框架QElapsedTimer timer; timer.start(); // 在这里调用 Canny 或任何 OpenCV 处理 qDebug() 处理耗时: timer.elapsed() ms;把耗时打印留在代码里换机器、换分辨率时能快速估算算法在这个工程里的成本也为以后换 CUDA 版本留一个对比基准。发布时提醒自己带全 dll。qmake 工程生成 exe 后在 Qt 命令行里执行 windeployqt --release 你的 exe 路径它会自动把 Qt 运行库和 platforms 插件复制到 exe 目录OpenCV 这边手动复制 opencv_world480.dll注意是 release 版别把 debug 的 d 版本发出去到同一目录即可。这两步做好双击 exe 就能独立运行。我平时搭这类图像处理界面习惯把 matToQImage 和阈值参数写成一个独立模块后续每个新工程直接复用省去重复调颜色和内存的功夫。这个标题看起来只是“显示图像 处理图像”实际上把 VS2019、Qt 和 OpenCV 三者的版本、链接和运行机制全部串了一遍走通了它后面做轮廓检测、物体识别、实时摄像头显示都只是在这个骨架上加算法而已。希望帮到你。本文还有配套的精品资源点击获取
返回列表