本文详细讲解了如何利用 Qt 和 OpenCV 实现一个可从视频和图片中检测二维码的软件。代码实现了视频解码、多线程处理和界面更新等功能,是一个典型的跨线程图像处理项目。以下分模块对代码进行解析。
一、项目的整体结构
项目分为以下几部分:
- 主窗口 (
MainWindow
) :负责界面的加载、初始化和用户交互。 - 工作线程 (
mThread
):处理耗时的图像处理任务(如二维码识别)。 - 二维码检测逻辑:使用 OpenCV 进行二维码检测,支持图片和视频两种数据来源。
- 多线程通信:通过信号与槽机制,在主线程和工作线程之间传递状态与数据。
二、主窗口功能解析
1. 初始化界面和变量
MainWindow
类的构造函数调用了 initializeUI()
和 initializeVariable()
,分别完成了界面的样式加载和核心变量的初始化。
cpp
void MainWindow::initializeVariable()
{
m_tip = nullptr;
m_lamp[0] = QImage(":/Img/e.png");
m_lamp[1] = QImage(":/Img/i.png");
m_lamp[2] = QImage(":/Img/w.png");
mthread = new mThread(); // 创建工作线程
m_Threadrun = false;
// 线程信号与主窗口槽函数的连接
connect(mthread, SIGNAL(RuningState(bool)), this, SLOT(onRespondThreadRuningState(bool)));
connect(mthread, SIGNAL(errors(QString)), this, SLOT(onRespondThreaderrors(QString)));
connect(mthread, SIGNAL(infors(QString)), this, SLOT(onRespondThreadinfors(QString)));
connect(mthread, SIGNAL(warings(QString)), this, SLOT(onRespondThreadwarings(QString)));
connect(mthread, &mThread::imageProcessed, this, &MainWindow::processImage);
}
2. 启动和停止线程
用户点击按钮后,调用 on_btn_Start_Stop_clicked
,判断当前线程状态以启动或停止工作线程。
cpp
void MainWindow::on_btn_Start_Stop_clicked()
{
m_Threadrun ? mthread->stop() : mthread->start(); // 根据当前状态启动或停止线程
}
3. 文件选择
QFileDialog
被用来让用户选择视频或图像文件,并将这些参数传递到线程处理。
cpp
void MainWindow::on_btn_Loadfile_clicked()
{
QString fileName = QFileDialog::getOpenFileName(nullptr, tc("选择视频文件"), "", tc("视频文件(*.mp4)"));
mthread->setFunId(0); // 设置功能 ID:0 表示处理视频
if (!fileName.isEmpty())
mthread->setThreadParams(fileName); // 传递参数到线程
}
void MainWindow::on_btn_Loadimages_clicked()
{
QStringList fileNames = QFileDialog::getOpenFileNames(nullptr, tc("选择图像文件"), "", tc("图片文件(*.jpg *.bmp *.png)"));
mthread->setFunId(1); // 设置功能 ID:1 表示处理图片
if (!fileNames.isEmpty())
mthread->setThreadParams(fileNames);
}
三、工作线程实现
mThread
类继承自 QThread
,用于处理耗时的二维码检测任务。其主要功能包括:
- 根据功能 ID 分别处理视频或图片。
- 在每帧中调用 OpenCV 的
QRCodeDetector
进行二维码检测。 - 通过信号将处理后的图像和数据传递回主线程。
1. 核心线程逻辑
线程的运行逻辑集中在 run()
方法中。getFunId()
决定了是处理视频还是图片,分别调用 anayVideo()
或 anayImages()
。
cpp
void mThread::run()
{
m_isRun = true;
emit RuningState(true); // 通知主线程:线程开始运行
emit infors(tc("线程启动"));
switch (getFunId()) {
case 0:
anayVideo(); // 处理视频
break;
case 1:
anayImages(); // 处理图片
break;
default:
break;
}
emit RuningState(false); // 通知主线程:线程结束运行
emit infors(tc("线程退出"));
}
2. 视频处理
在 anayVideo()
中,使用 OpenCV 的 VideoCapture
解码视频逐帧处理。每一帧调用 delectDecoded()
检测二维码,并通过信号将结果传回主线程。
cpp
void mThread::anayVideo()
{
cv::VideoCapture cap;
if (!cap.open(m_Params.toString().toLocal8Bit().data()) || !cap.isOpened())
{
emit errors(tc("视频未打开"));
m_isRun = false;
}
else
{
cv::Mat frame;
int frameCount = cap.get(cv::CAP_PROP_FRAME_COUNT);
while ((frameCount--) > 0 && m_isRun) // 帧循环
{
cap >> frame; // 读取一帧
if (frame.empty())
break;
QString msg;
delectDecoded(frame, msg); // 检测二维码
emit imageProcessed(MatToQImage(frame), msg); // 发射处理信号
cv::waitKey(50);
}
cap.release();
}
}
3. 图片处理
图片处理逻辑与视频类似,只是直接从文件路径中读取。
cpp
void mThread::anayImages()
{
QStringList files = m_Params.toStringList();
for (auto file : files)
{
cv::Mat frame = cv::imread(file.toStdString().c_str());
if (frame.empty() && !m_isRun)
break;
QString msg;
delectDecoded(frame, msg);
emit imageProcessed(MatToQImage(frame), msg); // 发射信号
cv::waitKey(1000);
}
}
四、二维码检测实现
1. 使用 OpenCV 进行检测
在 delectDecoded()
方法中,利用 OpenCV 的 QRCodeDetector
类进行二维码检测和解码,并将结果绘制到图像中。
cpp
int mThread::delectDecoded(cv::Mat &image, QString &code)
{
cv::Mat bbox, rectifiedImage;
std::string data = qrDecoder.detectAndDecode(image, bbox, rectifiedImage);
if (data.length() > 0)
{
code = QString::fromStdString(data); // 将结果返回
std::vector<cv::Point> points;
for (int i = 0; i < bbox.cols; i++)
{
points.push_back(cv::Point(static_cast<int>(bbox.at<cv::Point2f>(0, i).x), static_cast<int>(bbox.at<cv::Point2f>(0, i).y)));
}
for (size_t i = 0; i < points.size(); i++)
{
cv::line(image, points[i], points[(i + 1) % points.size()], cv::Scalar(0, 255, 0), 3); // 绘制绿色边框
}
int minY = points[0].y;
for (const auto &point : points) {
minY = std::min(minY, point.y);
}
cv::putText(image, data, cv::Point(points[0].x, minY - 10), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 0), 2); // 显示二维码信息
}
else
{
code = tc("未检测到二维码!");
}
return 0;
}
2. Mat 转 QImage
为了在 Qt 界面中显示 OpenCV 的图像,MatToQImage()
将 OpenCV 的 cv::Mat
转换为 Qt 的 QImage
。
五、多线程与信号槽
在本项目中,多线程通过信号与槽实现以下功能:
- 更新主界面状态 :线程的运行状态(如启动和停止)通过
RuningState
信号通知主线程。 - 实时更新图像和检测结果 :
imageProcessed
信号传递处理后的图像和二维码信息,更新界面。
cpp
connect(mthread, &mThread::imageProcessed, this, &MainWindow::processImage);
void MainWindow::processImage(const QImage &image, const QString &msg)
{
ui->lab_disp->setPixmap(QPixmap::fromImage(image).scaled(image.width() / 2, image.height() / 2)); // 显示缩放后的图像
ui->lab_disData->setText(msg); // 显示检测到的信息
}