前言
這是一個(gè)簡單的小項(xiàng)目,使用Qt和OpenCV構(gòu)建的多線程圖像識(shí)別應(yīng)用程序,旨在識(shí)別圖像中的人臉并將結(jié)果保存到不同的文件夾中。這個(gè)項(xiàng)目結(jié)合了圖像處理、多線程編程和用戶界面設(shè)計(jì)。 用戶可以通過界面選擇要識(shí)別的文件夾和保存結(jié)果的文件夾。然后,啟動(dòng)識(shí)別進(jìn)程。圖像識(shí)別線程并行處理選定文件夾中的圖像,檢測圖像中的人臉并將其保存到一個(gè)文件夾,同時(shí)將不包含人臉的圖像保存到另一個(gè)文件夾。進(jìn)度和結(jié)果將實(shí)時(shí)顯示在用戶界面上。
多線程編程
為什么需要多線程
1、并行處理:在處理大量圖像時(shí),使用單線程可能會(huì)導(dǎo)致應(yīng)用程序變得非常慢,因?yàn)樗仨氁来翁幚砻總€(gè)圖像(這里我沒有去實(shí)現(xiàn),感興趣的小伙伴可以自己嘗試一下)。多線程允許應(yīng)用程序同時(shí)處理多個(gè)圖像,從而提高了處理速度。
2、防止阻塞:如果在主線程中執(zhí)行耗時(shí)的操作,比如圖像識(shí)別,會(huì)導(dǎo)致用戶界面在操作執(zhí)行期間被凍結(jié),用戶無法與應(yīng)用程序互動(dòng)。多線程可以將這些耗時(shí)操作移到后臺(tái)線程,以避免界面阻塞。
3、利用多核處理器:現(xiàn)代計(jì)算機(jī)通常具有多核處理器,多線程可以充分利用這些多核來加速任務(wù)的執(zhí)行。
Qt如何實(shí)現(xiàn)多線程
在項(xiàng)目中,多線程編程主要使用了Qt的 QThread 類來實(shí)現(xiàn)。以下是在項(xiàng)目中使用多線程的關(guān)鍵步驟:
1、繼承QThread類: 首先,創(chuàng)建一個(gè)自定義的線程類,繼承自 QThread 類。這個(gè)類將負(fù)責(zé)執(zhí)行多線程任務(wù)。在項(xiàng)目中,這個(gè)自定義線程類是 ImageRecognitionThread。
2、重寫run函數(shù): 在自定義線程類中,重寫 run 函數(shù)。run 函數(shù)定義了線程的執(zhí)行體,也就是線程啟動(dòng)后會(huì)執(zhí)行的代碼。在本項(xiàng)目中,run 函數(shù)包含了圖像識(shí)別的邏輯。
3、創(chuàng)建線程對(duì)象: 在應(yīng)用程序中,創(chuàng)建自定義線程類的對(duì)象,例如 ImageRecognitionThread 的對(duì)象。然后,通過調(diào)用 start 函數(shù)來啟動(dòng)線程。
4、信號(hào)和槽機(jī)制: 使用Qt的信號(hào)和槽機(jī)制來實(shí)現(xiàn)線程間的通信。在項(xiàng)目中,使用信號(hào)來更新識(shí)別進(jìn)度和結(jié)果,以便主線程可以實(shí)時(shí)顯示這些信息。
5、線程安全性: 要確保多個(gè)線程安全地訪問共享資源,例如文件系統(tǒng)或圖像數(shù)據(jù),通常需要使用互斥鎖(Mutex)等機(jī)制來防止競爭條件和數(shù)據(jù)損壞。
線程間通信
在線程間進(jìn)行通信是多線程編程中的關(guān)鍵概念,特別是在項(xiàng)目中,其中一個(gè)線程負(fù)責(zé)圖像識(shí)別任務(wù),另一個(gè)線程用于用戶界面更新。在這個(gè)項(xiàng)目中,使用了Qt的信號(hào)和槽機(jī)制來實(shí)現(xiàn)線程間的通信,以便更新識(shí)別進(jìn)度和結(jié)果。具體步驟如下:
1、信號(hào)和槽的定義:
首先定義了信號(hào)和槽函數(shù),分別用于更新進(jìn)度和結(jié)果:
signals:
void updateProgress(int progress);
void updateResult(const QString& result);
private slots:
void onProgressUpdate(int progress);
void onResultUpdate(const QString& result);
updateProgress 信號(hào)用于更新識(shí)別進(jìn)度,它接受一個(gè)整數(shù)參數(shù),表示識(shí)別進(jìn)度的百分比。
updateResult 信號(hào)用于更新識(shí)別結(jié)果,它接受一個(gè)字符串參數(shù),表示識(shí)別的結(jié)果信息。
onProgressUpdate 槽函數(shù)用于接收進(jìn)度更新信號(hào),并在主線程中更新用戶界面的進(jìn)度條。
onResultUpdate 槽函數(shù)用于接收結(jié)果更新信號(hào),并在主線程中更新用戶界面的結(jié)果文本。
2、信號(hào)的發(fā)射:
在 ImageRecognitionThread 類的 run 函數(shù)中,根據(jù)圖像識(shí)別的進(jìn)度和結(jié)果,使用以下方式發(fā)射信號(hào):
// 發(fā)射進(jìn)度更新信號(hào)
emit updateProgress(progress);
// 發(fā)射結(jié)果更新信號(hào)
emit updateResult("圖像 " + imageFile + " 中檢測到人臉并已保存。");
通過 emit 關(guān)鍵字,可以發(fā)射定義的信號(hào),并傳遞相應(yīng)的參數(shù)。
3、槽函數(shù)的連接:
在主線程中,當(dāng)創(chuàng)建 ImageRecognitionThread 的對(duì)象時(shí),需要建立信號(hào)和槽的連接,以便接收來自線程的信號(hào)并執(zhí)行槽函數(shù)。這通常在主窗口類的構(gòu)造函數(shù)中完成。例如:
// 創(chuàng)建ImageRecognitionThread對(duì)象
imageThread = new ImageRecognitionThread(this);
// 連接信號(hào)和槽
connect(imageThread, &ImageRecognitionThread::updateProgress, this, &MainWindow::onProgressUpdate);
connect(imageThread, &ImageRecognitionThread::updateResult, this, &MainWindow::onResultUpdate);
這些連接操作確保當(dāng) ImageRecognitionThread 中的信號(hào)被發(fā)射時(shí),相關(guān)的槽函數(shù)會(huì)在主線程中執(zhí)行。
4、槽函數(shù)的執(zhí)行:
槽函數(shù)會(huì)在主線程中執(zhí)行,因此可以直接更新用戶界面的進(jìn)度條和結(jié)果文本。例如:
void MainWindow::onProgressUpdate(int progress)
{
ui->progressBar->setValue(progress);
}
void MainWindow::onResultUpdate(const QString& result)
{
ui->resultTextEdit->append(result);
}
在這里,onProgressUpdate 槽函數(shù)更新了主窗口中的進(jìn)度條,而 onResultUpdate 槽函數(shù)更新了結(jié)果文本框。
通過信號(hào)和槽機(jī)制,項(xiàng)目中的不同線程能夠安全地進(jìn)行通信,而不會(huì)導(dǎo)致競爭條件或數(shù)據(jù)損壞。這種機(jī)制允許圖像識(shí)別線程實(shí)時(shí)更新識(shí)別進(jìn)度和結(jié)果,同時(shí)保持了用戶界面的響應(yīng)性,提供了更好的用戶體驗(yàn)。
圖像識(shí)別
圖像識(shí)別的流程是這個(gè)項(xiàng)目的核心部分,它包括了加載圖像、使用OpenCV的人臉檢測器識(shí)別人臉、以及根據(jù)結(jié)果保存圖像等步驟。以下是詳細(xì)描述的圖像識(shí)別流程:
1、加載圖像:
首先,從用戶選擇的識(shí)別文件夾中加載圖像。這個(gè)步驟包括以下操作:
獲取用戶選擇的識(shí)別文件夾路徑。
遍歷該文件夾中的所有圖像文件。
逐個(gè)加載圖像文件。在項(xiàng)目中,可以使用OpenCV庫的 cv::imread 函數(shù)來加載圖像。
// 從文件夾中加載圖像
cv::Mat image = cv::imread(imageFile.toStdString());
2、 人臉識(shí)別:
一旦圖像加載完成,接下來的任務(wù)是識(shí)別圖像中的人臉。這個(gè)項(xiàng)目使用OpenCV提供的人臉檢測器來完成這個(gè)任務(wù),通常使用Haar級(jí)聯(lián)分類器或深度學(xué)習(xí)模型。在本項(xiàng)目中,我們使用了OpenCV內(nèi)置的Haar級(jí)聯(lián)分類器。
創(chuàng)建一個(gè) cv::CascadeClassifier 對(duì)象并加載Haar級(jí)聯(lián)分類器的XML文件。
cv::CascadeClassifier faceCascade;
faceCascade.load("haarcascade_frontalface_default.xml");
使用加載的分類器檢測圖像中的人臉。這將返回一個(gè)矩形列表,每個(gè)矩形表示一個(gè)檢測到的人臉的位置。
std::vector<cv::Rect> faces;
faceCascade.detectMultiScale(image, faces, scaleFactor, minNeighbors, flags, minSize, maxSize);
根據(jù)檢測到的人臉位置,可以在圖像上繪制矩形框,以標(biāo)記人臉的位置。
for (const cv::Rect& faceRect : faces) {
cv::rectangle(image, faceRect, cv::Scalar(0, 255, 0), 2); // 在圖像上繪制矩形框
}
3、 結(jié)果保存:
最后,根據(jù)識(shí)別的結(jié)果,將圖像保存到相應(yīng)的文件夾。在本項(xiàng)目中,根據(jù)是否檢測到人臉,有兩個(gè)不同的保存路徑:一個(gè)用于保存包含人臉的圖像,另一個(gè)用于保存不包含人臉的圖像。
如果檢測到了人臉,將圖像保存到包含人臉的文件夾中。
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);
}
以上就是圖像識(shí)別的主要流程。通過這個(gè)流程,項(xiàng)目能夠加載、識(shí)別和保存圖像,根據(jù)識(shí)別結(jié)果將圖像分別保存到兩個(gè)不同的文件夾中,以實(shí)現(xiàn)人臉識(shí)別功能。這個(gè)流程結(jié)合了OpenCV的圖像處理能力,為圖像識(shí)別提供了一個(gè)基本框架。
項(xiàng)目代碼
項(xiàng)目結(jié)構(gòu)
項(xiàng)目分為兩個(gè)主要部分:
1、用戶界面:使用Qt框架創(chuàng)建,包括選擇識(shí)別文件夾、選擇保存結(jié)果文件夾、啟動(dòng)和停止識(shí)別等功能。
2、圖像識(shí)別線程:使用Qt的QThread類創(chuàng)建,負(fù)責(zé)加載圖像、識(shí)別人臉、保存結(jié)果,并通過信號(hào)和槽機(jī)制與用戶界面通信。
各部分代碼
1、imagerecognitionthread.h
#ifndef IMAGERECOGNITIONTHREAD_H
#define IMAGERECOGNITIONTHREAD_H
#include <QThread>
#include <QString>
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 <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"; // 用于保存不包含人臉的圖像的文件夾
// 創(chuàng)建保存結(jié)果的文件夾
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("識(shí)別完成,結(jié)果保存在相應(yīng)文件夾中");
}
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_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 <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(); // 獲取保存結(jié)果的文件夾路徑
// 創(chuàng)建并啟動(dòng)識(shí)別線程
recognitionThread = new ImageRecognitionThread(this);
recognitionThread->setFolderPath(folderPath);
recognitionThread->setSaveFolderPath(saveFolderPath); // 設(shè)置保存結(jié)果的文件夾路徑
connect(recognitionThread, &ImageRecognitionThread::updateProgress, this, &MainWindow::updateProgress);
connect(recognitionThread, &ImageRecognitionThread::updateResult, this, &MainWindow::updateResult);
recognitionThread->start();
}
void MainWindow::stopRecognition()
{
// 如果識(shí)別線程正在運(yùn)行,終止它
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()
{
// 創(chuàng)建和布局UI組件
folderPathLineEdit = new QLineEdit(this);
saveFolderPathLineEdit = new QLineEdit(this); // 用于保存結(jié)果的文件夾路徑
startButton = new QPushButton("開始識(shí)別", this);
stopButton = new QPushButton("停止識(shí)別", this);
selectRecognitionFolderButton = new QPushButton("選擇識(shí)別文件夾", this); // 選擇識(shí)別文件夾按鈕
selectSaveFolderButton = new QPushButton("選擇保存文件夾", this); // 選擇保存文件夾按鈕
progressLabel = new QLabel("進(jìn)度:", this);
progressBar = new QProgressBar(this);
resultsLabel = new QLabel("結(jié)果:", this);
resultsList = new QListWidget(this);
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(folderPathLineEdit);
layout->addWidget(selectRecognitionFolderButton); // 添加選擇識(shí)別文件夾按鈕
layout->addWidget(saveFolderPathLineEdit); // 添加用于保存結(jié)果的文件夾路徑輸入框
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); // 連接選擇識(shí)別文件夾按鈕的槽函數(shù)
connect(selectSaveFolderButton, &QPushButton::clicked, this, &MainWindow::selectSaveFolder); // 連接選擇保存文件夾按鈕的槽函數(shù)
}
void MainWindow::selectRecognitionFolder()
{
QString folderPath = QFileDialog::getExistingDirectory(this, "選擇識(shí)別文件夾", "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
folderPathLineEdit->setText(folderPath);
}
void MainWindow::selectSaveFolder()
{
QString saveFolderPath = QFileDialog::getExistingDirectory(this, "選擇保存結(jié)果的文件夾", "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
saveFolderPathLineEdit->setText(saveFolderPath);
}
項(xiàng)目演示
文章來源:http://www.zghlxwxcb.cn/news/detail-754212.html
小結(jié)
特別提醒:在使用OpenCv的時(shí)候一定要配置好環(huán)境哦,這也是一個(gè)相對(duì)比較麻煩的事情,可以看看其他博主的教程!
點(diǎn)贊加關(guān)注,從此不迷路??!文章來源地址http://www.zghlxwxcb.cn/news/detail-754212.html
到了這里,關(guān)于【基于Qt和OpenCV的多線程圖像識(shí)別應(yīng)用】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!