基于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 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

#include

class ImageRecognitionThread : public QThread

{

Q_OBJECT

public:

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

#include

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 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

#include

#include

#include

#include

#include

#include "imagerecognitionthread.h"

class MainWindow : public QMainWindow

{

Q_OBJECT

public:

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

#include

#include

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的时候一定要配置好环境哦,这也是一个相对比较麻烦的事情,可以看看其他博主的教程! 点赞加关注,从此不迷路!!

参考阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。