《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——5. 集成OpenCV:让程序拥有“视力”

目录

  • 一、概述
    • 1.1 背景介绍:赋予应用“视力”
    • 1.2 学习目标
  • 二、集成OpenCV
    • 2.1 安装OpenCV
    • 2.2 在Qt项目中配置CMake
  • 三、项目数据集介绍与准备
  • 四、图像的桥梁:`ImageProvider`与格式转换
  • 五、加载、转换并显示图像
  • 六、总结与展望

一、概述

1.1 背景介绍:赋予应用“视力”

在此前的文章中,我们已经成功搭建了UI与逻辑之间的通信桥梁。现在,我们的应用程序拥有了美观的“面孔”和响应迅速的“神经系统”。然而,作为一个视觉检测应用,它还缺少最核心的器官——“眼睛”

本篇文章的核心任务,就是为我们的应用安装一双强大的“眼睛”——集成全球最流行的开源计算机视觉库OpenCV (Open Source Computer Vision Library)。通过集成OpenCV,我们的程序将首次获得处理和显示真实图像的能力,这是后续实现一切AI视觉功能的前提和基础。

1.2 学习目标

通过本篇的学习,读者将能够:

  1. 在Windows环境下正确配置Qt项目以集成OpenCV库。
  2. 掌握在C++中OpenCV的图像数据结构cv::Mat与Qt的QImage之间的相互转换,这是两者协同工作的关键。
  3. 实现从本地加载一张螺丝图片,并通过我们架设好的前后端桥梁,最终在QML界面上成功显示出来。

二、集成OpenCV

2.1 安装OpenCV

首先,需要在开发环境中准备好OpenCV。

  1. 下载: 访问OpenCV官网的发布页面,下载适用于Windows的最新预编译版本(例如 4.12.0)。
  2. 解压: 运行下载的.exe文件,它实际上是一个自解压程序。将其解压到一个不含中文和空格的稳定路径并重命名为opencv4.12.0,例如 D:\toolplace\opencv4.12.0。解压后,关键的目录是 D:\toolplace\opencv4.12.0\build

2.2 在Qt项目中配置CMake

要让我们的ScrewDetector项目能够找到并使用OpenCV,需要在CMakeLists.txt中添加配置。这是集成任何第三方C++库的标准流程。

1. 编写代码 (CMakeLists.txt)
打开项目根目录下的CMakeLists.txt文件,在find_package(Qt6 ...)之后,添加以下代码:

# --- 开始集成OpenCV ---
# 1. 设置OpenCV的根目录,请根据您的实际安装路径修改
set(OpenCV_DIR "D:/toolplace/opencv4.12.0/build")# 2. 查找OpenCV包,Core和Imgproc是我们目前需要的核心和图像处理模块
find_package(OpenCV REQUIRED COMPONENTS core imgproc)# 3. 包含OpenCV的头文件目录,以便#include指令能够找到它们
include_directories(${OpenCV_INCLUDE_DIRS})
# --- 结束集成OpenCV ---# ... (qt_add_executable等保持不变) ...# 在链接Qt库之后,链接OpenCV库
target_link_libraries(appScrewDetector PRIVATE# ... (原有的Qt6::Core, Qt6::Gui等)${OpenCV_LIBS} # 链接OpenCV库
)

关键代码分析:
(1) set(OpenCV_DIR ...): 这一行是关键,它明确告诉CMake去哪里寻找OpenCV的配置文件。路径必须指向包含OpenCVConfig.cmake文件的build目录。请务必根据自己的实际解压路径进行修改,并注意使用正斜杠/
(2) find_package(OpenCV REQUIRED ...): CMake会根据OpenCV_DIR的路径,查找OpenCV的配置,并加载其头文件路径、库文件路径等信息到CMake变量中(如OpenCV_INCLUDE_DIRSOpenCV_LIBS)。
(3) include_directories(...)target_link_libraries(...): 这两行分别将OpenCV的头文件目录和库文件添加到了我们项目的编译和链接步骤中。

3. 验证集成
backend.h的顶部添加以下两行:

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>

然后按Ctrl+B重新构建项目。如果项目能成功编译,没有任何“找不到文件”的错误,则说明OpenCV已经成功集成!

三、项目数据集介绍与准备

【核心概念:数据是AI的燃料】

无论是传统图像处理还是现代AI,一切算法都离不开数据。一个高质量、标准化的数据集是项目成功的基石。从本章开始,我们将引入一个贯穿后续所有算法章节的公开数据集。

我们将采用 MVTec Anomaly Detection Dataset (MVTec AD)。这是一个用于异常检测(在工业领域通常等同于瑕疵检测)的行业基准数据集,由德国MVTec公司发布,质量极高且下载方便。它包含了多种工业品类,我们正好可以使用其中的**“螺丝(screw)”**类别。

  • 官方网站:https://www.mvtec.com/company/research/datasets/mvtec-ad

1. 下载与组织数据集

  • 从上述官网链接进入,填写相关信息后跳转到下载页面,找到对应的Screw下载链接并下载screw.tar.xz文件。这是一个压缩包,需要使用支持.tar.xz格式的解压软件(如7-Zip)进行解压。
    在这里插入图片描述

  • 在我们的ScrewDetector项目根目录(与CMakeLists.txt同级)下,创建一个名为dataset的文件夹。

  • 将解压后的screw文件夹完整地拷贝到这个dataset文件夹中。

最终的项目目录结构应如下所示:

ScrewDetector/
├── CMakeLists.txt
├── main.cpp
├── Main.qml
├── logo.rc
├── backend.h
├── backend.cpp
├── icons/
│   └── appicon.png
│   └── appicon.ico
└── dataset/      <-- 新建的数据集文件夹└── screw/    <-- 从压缩包解压出的文件夹├── train/│   └── good/│       ├── 000.png│       └── ...└── test/├── good/├── scratch_head/│   ├── 000.png│   └── ...└── ...

说明:将数据集放在项目源码目录之外,是一种良好的工程实践,可以保持代码仓库的整洁,避免将庞大的数据文件包含进版本控制系统。

数据集部分样本图片如下:
在这里插入图片描述

四、图像的桥梁:ImageProvider与格式转换

为了让QML能够显示由C++动态加载的图像,我们需要一个特殊的桥梁——QQuickImageProvider。您可以把QQuickImageProvider想象成一个内置在您应用中的、私有的、轻量级的“图像服务器”。

  1. QML (客户端): 像浏览器一样,向一个特殊的URL地址发起请求,例如 image://liveImage/current_screw
  2. QQuickImageProvider (服务器): 它会接收到这个请求,解析出URL中的ID(current_screw),然后在C++代码中找到或生成对应的QImage对象,并将其作为响应“发回”给QML。同时,我们还需要实现cv::MatQImage的转换。

【例5-1】 创建ImageProvider并实现格式转换。

1. 创建ImageProvider

  • 在Qt Creator中,右键点击项目,添加新文件... -> C++ -> C++ Class
    • 类名: ImageProvider
    • 基类: 选择 QObject
  • 完成后,我们需要手动修改imageprovider.hImageProvider.cpp使其继承自QQuickImageProvider

2. 编写代码 (imageprovider.h)

#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H#include <QQuickImageProvider>
#include <QImage>// 继承自 QQuickImageProvider
class ImageProvider : public QQuickImageProvider
{
public:ImageProvider();// QML引擎会调用这个纯虚函数来请求图片QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;// 一个公共方法,用于从C++的Backend更新图片void updateImage(const QImage &image);private:// 用于存储当前要显示的图像QImage m_image;
};#endif // IMAGEPROVIDER_H

3. 编写代码 (imageprovider.cpp)

#include "imageprovider.h"ImageProvider::ImageProvider()// 构造函数中必须指定Provider的类型: QQuickImageProvider(QQuickImageProvider::Image)
{
}QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{Q_UNUSED(id);Q_UNUSED(requestedSize);if (size) {*size = m_image.size();}return m_image;
}void ImageProvider::updateImage(const QImage &image)
{if (m_image != image) {m_image = image;}
}

4. cv::MatQImage的转换函数 (backend.cpp)

修改backend.cpp,添加图像转换函数:

// 在 backend.cpp 的顶部
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>static QImage matToQImage(const cv::Mat &mat) {if (mat.empty()) { return QImage(); }if (mat.type() == CV_8UC3) {return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).rgbSwapped();} else if (mat.type() == CV_8UC1) {return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8);}return QImage();
}

五、加载、转换并显示图像

现在,我们将把BackendImageProvider串联起来,完成整个流程。

【例5-2】 改造Backend以使用ImageProvider

1. 注册ImageProvider (main.cpp)
QML引擎必须“知道”我们的ImageProvider的存在。

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QIcon>
#include <QQmlContext>
#include "backend.h"
#include "imageprovider.h" // 1. 包含ImageProvider头文件int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);app.setWindowIcon(QIcon(":/icons/appicon.png"));QQmlApplicationEngine engine;// 2. 实例化ImageProviderImageProvider *imageProvider = new ImageProvider();// 3. 将Provider注册到QML引擎,并给它一个URL方案名,例如"liveImage"engine.addImageProvider(QLatin1String("liveImage"), imageProvider);// 4. 创建Backend实例时,将provider的指针传给它Backend backend(imageProvider);engine.rootContext()->setContextProperty("backend", &backend);// ... (后续代码不变)return app.exec();
}

2. 修改Backend (backend.h & backend.cpp)
Backend通过OpenCV读取图像,转换为QImage后更新ImageProvider,然后通知QML去刷新图像。

// backend.h
#ifndef BACKEND_H
#define BACKEND_H
#include <QObject>
#include <QString>
// 移除 #include <QImage>
class ImageProvider; // 使用前向声明class Backend : public QObject
{Q_OBJECT
public:// 构造函数需要接收ImageProvider的指针explicit Backend(ImageProvider *provider, QObject *parent = nullptr);Q_INVOKABLE void startScan();
signals:// 信号不再传递QImage,只传递一个“刷新”指令和图像IDvoid imageReady(const QString &imageId);void statusMessageChanged(const QString &message);
private:ImageProvider *m_imageProvider;
};
#endif // BACKEND_H
// backend.cpp
#include "backend.h"
#include "imageprovider.h" // 在cpp中包含完整的头文件
#include <QDebug>
#include <QDir>
#include <opencv2/imgcodecs.hpp>// ... (matToQImage辅助函数)Backend::Backend(ImageProvider *provider, QObject *parent): QObject(parent), m_imageProvider(provider) // 在构造函数中保存指针
{}void Backend::startScan()
{qDebug() << "C++: Loading image with OpenCV...";emit statusMessageChanged("正在从数据集加载图像...");// 1. 构建数据集图片的绝对路径// QDir::currentPath() 获取的是构建目录,需要向上两级到项目根目录// 我们从MVTec数据集中挑选一张带瑕疵的图片作为示例QString imagePath = QDir::currentPath() + "/../../dataset/screw/test/scratch_head/000.png";// 2. 使用OpenCV从文件系统加载图像// 注意:imread需要一个标准C++字符串cv::Mat imageMat = cv::imread(imagePath.toStdString());if (imageMat.empty()) {qDebug() << "Error: Could not load image from path:" << imagePath;emit statusMessageChanged("错误:无法从数据集加载图像!请检查路径。");return;}// 3. 将cv::Mat转换为QImageQImage imageQ = matToQImage(imageMat);if (imageQ.isNull()){emit statusMessageChanged("错误:图像格式转换失败!");return;}// 4. 更新ImageProvider中的图像m_imageProvider->updateImage(imageQ);// 5. 发射信号,只告诉QML需要刷新的图像IDemit imageReady("screw_sample");emit statusMessageChanged("图像显示成功!");
}

3. 修改QML以使用URL (Main.qml)
这是最后一步,让QML的Image组件使用我们自定义的image:// URL。

// Main.qml
import QtQuick
// ...Window {// ...Connections {target: backendfunction onStatusMessageChanged(message) {statusLabel.text = message;}function onImageReady(imageId) {// 1. 构建URL: "image://<provider_name>/<image_id>"// 2. 附加一个时间戳来强制刷新,避免QML使用缓存videoDisplay.source = "image://liveImage/" + imageId+ "?" + new Date().getTime();}}ColumnLayout {// ...Frame {id: videoFrame// ...Image {id: videoDisplayanchors.fill: parentfillMode: Image.PreserveAspectFitcache: false // 显式禁用缓存,增加刷新可靠性}}// ...}
}

4. 运行结果
现在再次运行程序,点击“开始检测”按钮。流程变为:

  1. QML: 调用 backend.startScan()
  2. C++ Backend: 加载cv::Mat -> 转换为QImage -> 调用 m_imageProvider->updateImage()将新图像存入Provider -> 发射 imageReady("screw_sample") 信号。
  3. QML Connections: 收到信号,执行 onImageReady("screw_sample")
  4. QML Image: source属性被设置为 "image://liveImage/screw_sample?..."
  5. QML引擎: 看到image://协议,将请求转发给名为liveImageImageProvider
  6. C++ ImageProvider: requestImage()被调用,它返回存储在m_image中的最新图像。
  7. QML Image: 成功获取并显示图像。

最终的运行效果如下:
在这里插入图片描述

六、总结与展望

在本篇文章中,我们完成了一个关键的里程碑:成功地将强大的OpenCV视觉库集成到了Qt项目中。我们不仅掌握了在CMake中配置第三方库的方法,引入了项目后续将持续使用的真实、高质量数据集,还攻克了核心技术难点——cv::MatQImage之间的无缝转换。

现在,我们的应用程序不再只是一个空壳,它真正拥有了处理图像的“视力”。这座连接C++后端和QML前端的桥梁,已经成功地运输了第一批“货物”——图像数据。

在下一篇文章【《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——6. 传统算法实战:用OpenCV测量螺丝尺寸】中,我们将利用OpenCV的能力,对图像进行更深入的分析,实现第一个真正的机器视觉功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90432.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90432.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧驾驶疲劳检测算法的实时性优化

智慧驾驶疲劳检测&#xff1a;从技术突破到场景革命全球每年因疲劳驾驶引发的交通事故占比超20%&#xff0c;夜间及长途驾驶场景中这一比例更高。当驾驶员出现疲劳甚至晕倒等危险驾驶行为时&#xff0c;传统检测手段因依赖单一传感器或受环境干扰&#xff0c;存在误报率高、响应…

USRP X440

产品概述 USRP X440 是 Ettus Research 推出的高性能、多通道、宽带软件定义无线电&#xff08;SDR&#xff09;系统。基于 Xilinx Zynq UltraScale RFSoC 架构&#xff0c;它提供高密度、相干性的信号收发能力&#xff0c;帮助您快速构建雷达、电子战&#xff08;EW&#xff0…

[特殊字符] GitHub 2025年7月月度精选项目 Top5

&#x1f680; GitHub 2025年7月月度精选项目 Top5 本月GitHub有哪些值得关注的优质开源项目&#xff1f;我从数千个新项目中&#xff0c;精选了5个有趣 实用 可演示的仓库 无论你是开发者、AI爱好者、工具控&#xff0c;还是正在做副业产品&#xff0c;这篇文章都值得收藏&a…

微服务架构下的自动化测试策略调优经验分享

微服务架构下,自动化测试策略需针对分布式特性、服务自治性和高耦合风险进行针对性调整的关键调整方向及实施方法: 一、​​测试策略重构:分层与契约驱动​​ 1. ​​测试金字塔升级为钻石模型​​ ​​调整逻辑​​:传统金字塔中UI测试占比过高,而微服务需强化契约测试与…

图论:并查集

入门 久闻并查集的大名&#xff0c;今天来一探究竟&#xff0c;到底什么是并查集&#xff0c;并查集有什么用&#xff1f; 并查集(Disjoint Set Union, DSU)是一种处理不相交集合的合并及查询问题的数据结构。 其实并查集的作用主要就有两个&#xff1a; 1、将两个元素添加到…

告别静态文档!Oracle交互式技术架构图让数据库学习“活“起来

&#x1f5fa;️ 当数据库架构图学会"互动" 想象一下&#xff0c;你正在学习Oracle数据库架构&#xff0c;面对密密麻麻的静态文档和复杂的组件关系图&#xff0c;是不是常常感到&#xff1a; 像在迷宫里找路&#xff0c;不知道组件间如何协作&#xff1f;想深入了…

day62-可观测性建设-全链路监控zabbix+grafana

&#x1f31f;监控api接口 &#x1f50d;监控zabbix-api接口 生成API tokens命令行测试 curl -s -X POST -H "Content-Type: application/json-rpc" -d {"jsonrpc": "2.0","method": "host.get","params": {&quo…

通过Deepseek找工作

推送的结果如下,对应的AI提示词在底部: 计算机方向远程工作职位汇总 整合全球远程技术岗位 | 支持全地域远程办公 | 涵盖开发、安全、云计算等方向 覆盖方向:8+个技术领域 薪资范围:10K-40K/月 工作模式:100%远程 远程技术职位列表 职位名称 技能要求 经验要求 薪资…

vscode文件颜色,只显示自己更改的文件颜色、刚git下来的库,vscode打开后,显示所有文件都被修改了

问题&#xff1a;git新的库&#xff0c;然后我用vscode打开&#xff0c;默认显示所有的文件都更改了&#xff0c;但是我打开他们修改的对比&#xff0c;没有显示任何有被修改的地方&#xff0c;是怎么回事 linux/wsl下这么设置就可以了&#xff1a;git config core.autocrlf in…

基于ENMeval包的MaxEnt模型参数优化总结

MaxEnt模型参数优化1. MaxEnt模型优化&#xff1a;增加RM&#xff0c;降低模型过拟合风险&#xff0c;简易模型&#xff0c;平滑响应曲线&#xff0c;增强模型可解释性和转移性&#xff08;生物入侵&#xff09;2. 默认参数&#xff1a;FCLQHP&#xff0c;RM12.1. 基于优化的 M…

Docker实践:使用Docker部署blog轻量级博客系统

Docker实践&#xff1a;使用Docker部署blog轻量级博客系统一、blog系统介绍1.1 blog介绍1.2 个人博客系统介绍1.3 个人博客使用场景二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本四、…

专题:2025电商增长新势力洞察报告:区域裂变、平台垄断与银发平权|附260+报告PDF、原数据表汇总下载

原文链接&#xff1a;https://tecdat.cn/?p43416 当茂名果农对着镜头用方言喊出“荔枝现摘现发”&#xff0c;2小时卖出83万元&#xff1b;当65岁的上海阿姨通过“子女代付”买到人生第一台智能冰箱——2025年的电商战场&#xff0c;正在上演三重革命&#xff1a;新兴市场的增…

数字化转型-AI落地金字塔法则

前言 人工智能必须要跟传统产业结合&#xff0c;融入传统产业&#xff0c;才能落地&#xff0c;才能产生巨大的倍增个几何级效果&#xff01;&#xff01; AI不应该停留在工具层面&#xff0c;AI不仅仅是工具&#xff0c;不仅仅是硬件和软件&#xff0c;而是软硬结合。人工智能…

SQL Server 字段类型选型指南:什么数据用什么字段

目录 一、数值型数据 二、日期与时间数据 三、字符串与文本数据 四、布尔值与状态码 五、二进制与文件数据 六、唯一标识符&#xff08;GUID&#xff09; 七、枚举与代码表设计 八、存储优化小结 九、总结 在数据库设计中&#xff0c;字段类型&#xff08;数据类型&am…

酷暑来袭,科技如何让城市清凉又洁净?

烈日下的身影&#xff0c;不该被“炙烤”的担当又是一年盛夏&#xff0c;城市的血管在高温下脉动&#xff0c;柏油马路仿佛要融化&#xff0c;空气中弥漫着灼热的气息。此刻&#xff0c;你是否曾留意过那些身影&#xff1f;在烈日下&#xff0c;他们依旧坚守岗位&#xff0c;用…

传统框架与减震楼盖框架地震动力响应分析与有限元模拟

传统框架与减震楼盖框架地震动力响应分析与有限元模拟 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家,觉得好请收藏。点击跳转到网站。 摘要 本文针对传统钢框架和减震楼盖钢框架两种结构体系,建立了水平地震作用下的动力学模型,推…

Java集合去重

✅ 方式一&#xff1a;TreeSet Comparator最优雅的一种&#xff0c;适用于对象中某个字段唯一的去重&#xff08;如 partyAId&#xff09;List<PartyACompanyVO> result contractDOS.stream().map(contract -> {PartyACompanyVO vo new PartyACompanyVO();vo.setPa…

Qt字符串处理与正则表达式应用

一、Qt字符串处理基础 在Qt应用程序开发中&#xff0c;字符串处理是一项常见且重要的任务。Qt提供了强大而灵活的字符串处理功能&#xff0c;能够满足各种复杂的文本处理需求。 1.1 QString类概述 QString是Qt中处理字符串的核心类&#xff0c;它基于Unicode编码&#xff0c…

qt5静态版本对应的pcre编译

下载 https://sourceforge.net/projects/pcre/files/pcre/8.45/ 不同版本qt对应不同pcre 编译 启动vs2013的开发人员命令&#xff0c;可以找到cl程序 nmake环境设置到系统path中 cd C:\pcre-8.45 mkdir build_static cd build_static cmake .. -G "NMake Makefiles" …

JimuReport 积木报表 v2.1.1 版本发布,免费开源的报表和大屏

项目介绍 积木报表&#xff0c;是一款免费的数据可视化报表&#xff0c;含报表、打印、大屏和仪表盘&#xff0c;像搭建积木一样完全在线设计&#xff01;功能涵盖&#xff1a;复杂报表、打印设计、图表报表、门户设计、大屏设计等&#xff01; 分两大模块&#xff1a;JimuRepo…