【基于Qt和OpenCV的多线程图像识别应用】

基于Qt和OpenCV的多线程图像识别应用

  • 前言
  • 多线程编程
    • 为什么需要多线程
    • Qt如何实现多线程
    • 线程间通信
  • 图像识别
  • 项目代码
    • 项目结构
    • 各部分代码
  • 项目演示
  • 小结

前言

这是一个简单的小项目,使用Qt和OpenCV构建的多线程图像识别应用程序,旨在识别图像中的人脸并将结果保存到不同的文件夹中。这个项目结合了图像处理、多线程编程和用户界面设计。 用户可以通过界面选择要识别的文件夹和保存结果的文件夹。然后,启动识别进程。图像识别线程并行处理选定文件夹中的图像,检测图像中的人脸并将其保存到一个文件夹,同时将不包含人脸的图像保存到另一个文件夹。进度和结果将实时显示在用户界面上。

多线程编程

为什么需要多线程

1、并行处理:在处理大量图像时,使用单线程可能会导致应用程序变得非常慢,因为它必须依次处理每个图像(这里我没有去实现,感兴趣的小伙伴可以自己尝试一下)。多线程允许应用程序同时处理多个图像,从而提高了处理速度。

2、防止阻塞:如果在主线程中执行耗时的操作,比如图像识别,会导致用户界面在操作执行期间被冻结,用户无法与应用程序互动。多线程可以将这些耗时操作移到后台线程,以避免界面阻塞。

3、利用多核处理器:现代计算机通常具有多核处理器,多线程可以充分利用这些多核来加速任务的执行。

Qt如何实现多线程

在项目中,多线程编程主要使用了Qt的 QThread 类来实现。以下是在项目中使用多线程的关键步骤
1、继承QThread类: 首先,创建一个自定义的线程类,继承自 QThread 类。这个类将负责执行多线程任务。在项目中,这个自定义线程类是 ImageRecognitionThread。

2、重写run函数: 在自定义线程类中,重写 run 函数。run 函数定义了线程的执行体,也就是线程启动后会执行的代码。在本项目中,run 函数包含了图像识别的逻辑。

3、创建线程对象: 在应用程序中,创建自定义线程类的对象,例如 ImageRecognitionThread 的对象。然后,通过调用 start 函数来启动线程。

4、信号和槽机制: 使用Qt的信号和槽机制来实现线程间的通信。在项目中,使用信号来更新识别进度和结果,以便主线程可以实时显示这些信息。

5、线程安全性: 要确保多个线程安全地访问共享资源,例如文件系统或图像数据,通常需要使用互斥锁(Mutex)等机制来防止竞争条件和数据损坏。

线程间通信

在线程间进行通信是多线程编程中的关键概念,特别是在项目中,其中一个线程负责图像识别任务,另一个线程用于用户界面更新。在这个项目中,使用了Qt的信号和槽机制来实现线程间的通信,以便更新识别进度和结果。具体步骤如下:
1、信号和槽的定义
首先定义了信号和槽函数,分别用于更新进度和结果:

signals:void updateProgress(int progress);void updateResult(const QString& result);private slots:void onProgressUpdate(int progress);void onResultUpdate(const QString& result);

updateProgress 信号用于更新识别进度,它接受一个整数参数,表示识别进度的百分比。
updateResult 信号用于更新识别结果,它接受一个字符串参数,表示识别的结果信息。
onProgressUpdate 槽函数用于接收进度更新信号,并在主线程中更新用户界面的进度条。
onResultUpdate 槽函数用于接收结果更新信号,并在主线程中更新用户界面的结果文本。

2、信号的发射
在 ImageRecognitionThread 类的 run 函数中,根据图像识别的进度和结果,使用以下方式发射信号:

// 发射进度更新信号
emit updateProgress(progress);// 发射结果更新信号
emit updateResult("图像 " + imageFile + " 中检测到人脸并已保存。");

通过 emit 关键字,可以发射定义的信号,并传递相应的参数。

3、槽函数的连接
在主线程中,当创建 ImageRecognitionThread 的对象时,需要建立信号和槽的连接,以便接收来自线程的信号并执行槽函数。这通常在主窗口类的构造函数中完成。例如:

// 创建ImageRecognitionThread对象
imageThread = new ImageRecognitionThread(this);// 连接信号和槽
connect(imageThread, &ImageRecognitionThread::updateProgress, this, &MainWindow::onProgressUpdate);
connect(imageThread, &ImageRecognitionThread::updateResult, this, &MainWindow::onResultUpdate);

这些连接操作确保当 ImageRecognitionThread 中的信号被发射时,相关的槽函数会在主线程中执行。

4、槽函数的执行

槽函数会在主线程中执行,因此可以直接更新用户界面的进度条和结果文本。例如:

void MainWindow::onProgressUpdate(int progress)
{ui->progressBar->setValue(progress);
}void MainWindow::onResultUpdate(const QString& result)
{ui->resultTextEdit->append(result);
}

在这里,onProgressUpdate 槽函数更新了主窗口中的进度条,而 onResultUpdate 槽函数更新了结果文本框。
通过信号和槽机制,项目中的不同线程能够安全地进行通信,而不会导致竞争条件或数据损坏。这种机制允许图像识别线程实时更新识别进度和结果,同时保持了用户界面的响应性,提供了更好的用户体验。

图像识别

图像识别的流程是这个项目的核心部分,它包括了加载图像、使用OpenCV的人脸检测器识别人脸、以及根据结果保存图像等步骤。以下是详细描述的图像识别流程:

1、加载图像

首先,从用户选择的识别文件夹中加载图像。这个步骤包括以下操作:
获取用户选择的识别文件夹路径。
遍历该文件夹中的所有图像文件。
逐个加载图像文件。在项目中,可以使用OpenCV库的 cv::imread 函数来加载图像。

// 从文件夹中加载图像
cv::Mat image = cv::imread(imageFile.toStdString());

2、 人脸识别

一旦图像加载完成,接下来的任务是识别图像中的人脸。这个项目使用OpenCV提供的人脸检测器来完成这个任务,通常使用Haar级联分类器或深度学习模型。在本项目中,我们使用了OpenCV内置的Haar级联分类器。

创建一个 cv::CascadeClassifier 对象并加载Haar级联分类器的XML文件。

cv::CascadeClassifier faceCascade;
faceCascade.load("haarcascade_frontalface_default.xml");

使用加载的分类器检测图像中的人脸。这将返回一个矩形列表,每个矩形表示一个检测到的人脸的位置。

std::vector<cv::Rect> faces;
faceCascade.detectMultiScale(image, faces, scaleFactor, minNeighbors, flags, minSize, maxSize);

根据检测到的人脸位置,可以在图像上绘制矩形框,以标记人脸的位置。

for (const cv::Rect& faceRect : faces) {cv::rectangle(image, faceRect, cv::Scalar(0, 255, 0), 2); // 在图像上绘制矩形框
}

3、 结果保存
最后,根据识别的结果,将图像保存到相应的文件夹。在本项目中,根据是否检测到人脸,有两个不同的保存路径:一个用于保存包含人脸的图像,另一个用于保存不包含人脸的图像。

如果检测到了人脸,将图像保存到包含人脸的文件夹中。

if (!faces.empty()) {QString savePathWithFace = saveFolderPath + "/with_face/" + QFileInfo(imageFile).fileName();cv::imwrite(savePathWithFace.toStdString(), image);
}

如果没有检测到人脸,将图像保存到不包含人脸的文件夹中。

else {QString savePathWithoutFace = saveFolderPath + "/without_face/" + QFileInfo(imageFile).fileName();cv::imwrite(savePathWithoutFace.toStdString(), image);
}

以上就是图像识别的主要流程。通过这个流程,项目能够加载、识别和保存图像,根据识别结果将图像分别保存到两个不同的文件夹中,以实现人脸识别功能。这个流程结合了OpenCV的图像处理能力,为图像识别提供了一个基本框架。

项目代码

项目结构

项目分为两个主要部分:
1、用户界面:使用Qt框架创建,包括选择识别文件夹、选择保存结果文件夹、启动和停止识别等功能。
2、图像识别线程:使用Qt的QThread类创建,负责加载图像、识别人脸、保存结果,并通过信号和槽机制与用户界面通信。

各部分代码

1、imagerecognitionthread.h

#ifndef IMAGERECOGNITIONTHREAD_H
#define IMAGERECOGNITIONTHREAD_H#include <QThread>
#include <QString>class ImageRecognitionThread : public QThread
{Q_OBJECTpublic:explicit ImageRecognitionThread(QObject* parent = nullptr);void setFolderPath(const QString& folderPath);void setSaveFolderPath(const QString& saveFolderPath); protected:void run() override;signals:void updateProgress(int progress);void updateResult(const QString& result);private:QString folderPath;QString saveFolderPath; 
};#endif 

2、imagerecognitionthread.cpp

#include "imagerecognitionthread.h"
#include <opencv2/opencv.hpp>
#include <QDir>
ImageRecognitionThread::ImageRecognitionThread(QObject* parent): QThread(parent), folderPath(""), saveFolderPath("")
{}void ImageRecognitionThread::setFolderPath(const QString& folderPath)
{this->folderPath = folderPath;
}void ImageRecognitionThread::setSaveFolderPath(const QString& saveFolderPath)
{this->saveFolderPath = saveFolderPath;
}void ImageRecognitionThread::run()
{QString faceCascadePath = "D:\\DownLoad\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_default.xml";cv::CascadeClassifier faceCascade;if (!faceCascade.load(faceCascadePath.toStdString())){emit updateResult("无法加载人脸检测器");return;}QDir imageDir(folderPath);QStringList imageFilters;imageFilters << "*.jpg" << "*.png";QStringList imageFiles = imageDir.entryList(imageFilters, QDir::Files);int totalImages = imageFiles.size();int processedImages = 0;QString faceSaveFolderPath = saveFolderPath + "/faces"; // 用于保存包含人脸的图像的文件夹QString noFaceSaveFolderPath = saveFolderPath + "/no_faces"; // 用于保存不包含人脸的图像的文件夹// 创建保存结果的文件夹QDir().mkpath(faceSaveFolderPath);QDir().mkpath(noFaceSaveFolderPath);for (const QString& imageFile : imageFiles){processedImages++;int progress = (processedImages * 100) / totalImages;emit updateProgress(progress);QString imagePath = folderPath + "/" + imageFile;cv::Mat image = cv::imread(imagePath.toStdString());if (!image.empty()){std::vector<cv::Rect> faces;faceCascade.detectMultiScale(image, faces, 1.1, 4, 0 | cv::CASCADE_SCALE_IMAGE, cv::Size(30, 30));if (!faces.empty()){QString targetPath = faceSaveFolderPath + "/" + imageFile;cv::imwrite(targetPath.toStdString(), image);emit updateResult("图像 " + imageFile + " 中检测到人脸并已保存到人脸文件夹。");}else{QString targetPath = noFaceSaveFolderPath + "/" + imageFile;cv::imwrite(targetPath.toStdString(), image);emit updateResult("图像 " + imageFile + " 中未检测到人脸并已保存到非人脸文件夹。");}}}emit updateResult("识别完成,结果保存在相应文件夹中");
}

3、mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QProgressBar>
#include <QListWidget>
#include "imagerecognitionthread.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget* parent = nullptr);private slots:void startRecognition();void stopRecognition();void updateProgress(int progress);void updateResult(const QString& result);void selectRecognitionFolder();void selectSaveFolder();private:void setupUi();void connectSignalsAndSlots();QLineEdit* folderPathLineEdit;QLineEdit* saveFolderPathLineEdit; QPushButton* startButton;QPushButton* stopButton;QPushButton* selectRecognitionFolderButton; QPushButton* selectSaveFolderButton; QLabel* progressLabel;QProgressBar* progressBar;QLabel* resultsLabel;QListWidget* resultsList;ImageRecognitionThread* recognitionThread;
};#endif // MAINWINDOW_H

4、mainwindow.cpp

#include "mainwindow.h"
#include "imagerecognitionthread.h"
#include <QVBoxLayout>
#include <QFileDialog>
#include <QDebug>MainWindow::MainWindow(QWidget* parent): QMainWindow(parent), recognitionThread(nullptr)
{setupUi();connectSignalsAndSlots();
}void MainWindow::startRecognition()
{// 获取文件夹路径QString folderPath = folderPathLineEdit->text();QString saveFolderPath = saveFolderPathLineEdit->text(); // 获取保存结果的文件夹路径// 创建并启动识别线程recognitionThread = new ImageRecognitionThread(this);recognitionThread->setFolderPath(folderPath);recognitionThread->setSaveFolderPath(saveFolderPath); // 设置保存结果的文件夹路径connect(recognitionThread, &ImageRecognitionThread::updateProgress, this, &MainWindow::updateProgress);connect(recognitionThread, &ImageRecognitionThread::updateResult, this, &MainWindow::updateResult);recognitionThread->start();
}void MainWindow::stopRecognition()
{// 如果识别线程正在运行,终止它if (recognitionThread && recognitionThread->isRunning()){recognitionThread->terminate();recognitionThread->wait();}
}void MainWindow::updateProgress(int progress)
{progressBar->setValue(progress);
}void MainWindow::updateResult(const QString& result)
{resultsList->addItem(result);
}void MainWindow::setupUi()
{// 创建和布局UI组件folderPathLineEdit = new QLineEdit(this);saveFolderPathLineEdit = new QLineEdit(this); // 用于保存结果的文件夹路径startButton = new QPushButton("开始识别", this);stopButton = new QPushButton("停止识别", this);selectRecognitionFolderButton = new QPushButton("选择识别文件夹", this); // 选择识别文件夹按钮selectSaveFolderButton = new QPushButton("选择保存文件夹", this); // 选择保存文件夹按钮progressLabel = new QLabel("进度:", this);progressBar = new QProgressBar(this);resultsLabel = new QLabel("结果:", this);resultsList = new QListWidget(this);QVBoxLayout* layout = new QVBoxLayout();layout->addWidget(folderPathLineEdit);layout->addWidget(selectRecognitionFolderButton); // 添加选择识别文件夹按钮layout->addWidget(saveFolderPathLineEdit); // 添加用于保存结果的文件夹路径输入框layout->addWidget(selectSaveFolderButton); // 添加选择保存文件夹按钮layout->addWidget(startButton);layout->addWidget(stopButton);layout->addWidget(progressLabel);layout->addWidget(progressBar);layout->addWidget(resultsLabel);layout->addWidget(resultsList);QWidget* centralWidget = new QWidget(this);centralWidget->setLayout(layout);setCentralWidget(centralWidget);
}void MainWindow::connectSignalsAndSlots()
{connect(startButton, &QPushButton::clicked, this, &MainWindow::startRecognition);connect(stopButton, &QPushButton::clicked, this, &MainWindow::stopRecognition);connect(selectRecognitionFolderButton, &QPushButton::clicked, this, &MainWindow::selectRecognitionFolder); // 连接选择识别文件夹按钮的槽函数connect(selectSaveFolderButton, &QPushButton::clicked, this, &MainWindow::selectSaveFolder); // 连接选择保存文件夹按钮的槽函数
}void MainWindow::selectRecognitionFolder()
{QString folderPath = QFileDialog::getExistingDirectory(this, "选择识别文件夹", "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);folderPathLineEdit->setText(folderPath);
}void MainWindow::selectSaveFolder()
{QString saveFolderPath = QFileDialog::getExistingDirectory(this, "选择保存结果的文件夹", "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);saveFolderPathLineEdit->setText(saveFolderPath);
}

项目演示

在这里插入图片描述在这里插入图片描述在这里插入图片描述

小结

特别提醒:在使用OpenCv的时候一定要配置好环境哦,这也是一个相对比较麻烦的事情,可以看看其他博主的教程!
点赞加关注,从此不迷路!!

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

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

相关文章

软件可靠性基础

软件可靠性基础 软件可靠性基本概念串并联系统可靠性计算软件可靠性测试软件可靠性建模软件可靠性管理软件可靠性设计容错&#xff0c;检错的技术 选择题考基本概念&#xff08;MTBF&#xff09;&#xff0c;很少考 非重点 软件可靠性基本概念 这个章节中&#xff0c;第一个…

【数据库——MySQL】(6)查询(1)

目录 1. 数据库查询1.1 输出项为列名1.2 输出项为表达式1.3 输出内容变换1.4 消除输出项的重复行1.5 聚合函数 2. 查询条件&#xff1a;逻辑条件2.1 比较运算2.2 模式匹配2.3 范围限定2.4 空值判断 3. 分组3.1 基本分组3.2 分组汇总 4. 分组后筛选5. 输出行排序5.1 ORDER BY5.2…

识别车牌的代码分享

接口 接口为车牌的自动定位和识别,返回地域编号和车牌号车牌颜色: # pip install poocr import poocr # 可以填写本地图片的地址:img_path,也可以填写在线图片的地址:img_url # 如果2个都填,则只用在线图片img_url # configPath是配置文件的信息,可以不填 Number…

找高清、4K图片素材就上这6个网站,免费下载!

不会还有人找图片素材直接上网去搜吧&#xff0c;告诉你们6个网站&#xff0c;轻松找到想要的图片素材&#xff0c;不仅质量高&#xff0c;还可以免费下载&#xff0c;重点是还可以商用。赶紧收藏起来吧~ 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 网站主要为…

Scrapy+Selenium自动化获取个人CSDN文章质量分

前言 本文将介绍如何使用Scrapy和Selenium这两个强大的Python工具来自动获取个人CSDN文章的质量分数。我们将详细讨论Scrapy爬虫框架的使用&#xff0c;以及如何结合Selenium浏览器自动化工具来实现这一目标。无需手动浏览每篇文章&#xff0c;我们可以轻松地获取并记录文章的…

查询PCIE设备的VID,DID,SVID,SDID

查询PCIE设备的VID,DID,SVID,SDID &#xff08; Vendor ID、 Device ID、Subsystem Vendor ID、Subsystem Device ID &#xff09; [rootlocalhost ~]# lspci -s 04:00.0 -xxxvvv 04:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03)C…

Docker部署ElasticSearch数据库+analysis-ik分词器插件

文章目录 一、部署ElasticSearch数据库二、添加分词器插件(analysis-ik)三、测试ElasticSearch数据库analysis-ik分词器插件 一、部署ElasticSearch数据库 1、准备工作 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.6 Pwd"/data/software/elasticse…

Casper Network 构建企业级区块链生态的野望

Casper Network 是基于 Layer1 且图灵完备 Wasm 的智能合约平台&#xff0c;它由唯一可操作的 CBC-Casper Proof-of-Stake (PoS) 共识算法&#xff08;称为 Highway&#xff09;支持&#xff0c;该网络是一个无需许可、去中心化的公共区块链。 Casper Network 主网在 2021 年 4…

架构案例-架构真题2016(四十)

&#xff08;2016&#xff09;嵌入式处理器是嵌入式系统的核心部件&#xff0c;一般可分为嵌入式微处理器&#xff08;MPU&#xff09;微控制器&#xff08;MCU&#xff09;、数字信号处理器&#xff08;DSP&#xff09;和片上系统&#xff08;SOC&#xff09;。以下叙述中&…

一篇文章让你熟悉unordered_map及其模拟实现

熟悉unordered_map及其模拟实现 unordered_map的定义1. unordered_map的模板定义2. unordered_map的成员类型 unordered_map构造函数unordered_map赋值运算符重载unordered_map容量函数&#xff08;Capacity&#xff09;unordered_map迭代器&#xff08;Iterators&#xff09;1…

慢性疼痛治疗服务公司Kindly MD申请700万美元纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉,慢性疼痛治疗服务公司Kindly MD近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码为&#xff08;KDLY&#xff09;,Kindly MD计划通过…

通信协议:Uart的Verilog实现(下)

4、UART接收器 UART接收器负责接收串行比特流&#xff0c;去除起始位和停止位&#xff0c;并以并行格式将数据保存到与主机数据总线相连的寄存器里。接收器无法获得发送时钟&#xff0c;因此尽管数据以标准比特率到达&#xff0c;但数据未必与接收主机内的时钟同步。同步问题可…

面试算法13:二维子矩阵的数字之和

题目 输入一个二维矩阵&#xff0c;如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和&#xff1f;对于同一个二维矩阵&#xff0c;计算子矩阵的数字之和的函数可能由于输入不同的坐标而被反复调用多次。例如&#xff0c;输入图2.1中的二维矩阵&#xff0c;以及左上角坐标…

python机器学习融合模型:Stacking与Blending(附代码)_论文_模型竞赛_企业建模

大家好&#xff0c;今天给大家总结归纳了python机器学习融合模型&#xff1a;Stacking与Blending&#xff08;附代码&#xff09;。 1 堆叠法Stacking 一套弱系统能变成一个强系统吗&#xff1f; 当你处在一个复杂的分类问题面前时&#xff0c;金融市场通常会出现这种情况&…

自定义热加载:如何不停机实现核心代码更新

文章目录 1. 常见的几种实现代码热更新的几种方式对于开发环境我们可以使用部署环境1. 使用 Arthas 的 redefine 命令来加载新的 class 文件2. 利用 URLClassLoader 动态加载3. 通过Java的Instrumentation API 也是可以实现的 2. 实现1. ClassScanner扫描目录和加载类2. 定时任…

Mybatis学习笔记7 参数处理专题

Mybatis学习笔记6 使用时的一些小技巧_biubiubiu0706的博客-CSDN博客 1.单个简单类型参数 2.Map参数 3.实体类参数 4.多参数 5.Param注解(命名参数) 6.Param源码分析 建表 插入点数据 新建模块 pom.xml <?xml version"1.0" encoding"UTF-8"?&…

SpringBoot 学习(七)Swagger

7. Swagger 7.1 简介 便于前后端集成联调RestFul Api 文档在线生成工具 > Api 文档与 Api 定义同步更新直接运行&#xff0c;在线测试 Api 接口 7.2 springboot 集成 swagger (1) 导入依赖 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger…

2001-2021年上市公司基于Jones 修正模型的盈余管理测度(含原始数据+stata 计算代码)

2001-2021年上市公司基于Jones 修正模型的盈余管理测度&#xff08;含原始数据stata 计算代码&#xff09; 1、时间&#xff1a;2001-2021 年 3、范围&#xff1a;沪深 A 股上市公司 4、指标&#xff1a;经营活动现金流、总资产、净利润、总收入、固定资产、应收账款、盈余管…

如何快速做跨业务测试?

当业务任务多且人力资源不充足的情况下&#xff0c;不同业务的同学可能需要去不同的业务进行临时支援&#xff0c;可能在时间方面有长有短&#xff0c;但是如何迈出第一步是很多人需要关心的一件事。 本文以实际跨业务测试经验&#xff08;订单业务测试人员如何测试售后业务&a…

什么是HTTP/2?它与HTTP/1.1相比有什么改进?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ HTTP/2 简介⭐ 主要的改进和特点⭐ 总结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端…