Qt主线程和子线程协作更新UI

发布时间 2023-11-30 14:09:50作者: 飘杨......

一、概述

  现有一个需求:Qt+OpenCV执行角点检测。使用Qt当做UI界面进行角点检测。我们知道像角点检测这种算法需要大量的计算,是比较耗时的一个操作。如果把计算+UI显示全放入主线程中计算,那么

    UI界面有可能就会卡主,进而出现应用程序无响应的情况。

  要求:

    使用QtThread进行角点检测,让Qt的主线程更新UI,即子线程角点检测完成后主线程(UI线程)再更新UI。这样可以避免主线程由于计算量打被卡死的情况。

  

  解决步骤:

    1.使用Qt的信号槽来解决问题

    2.在线程中发射一个信号,主线程的UI界面去接收这个信号。(emit 信号方法)

    3.UI界面更新UI即可

  

二、代码示例

  1.自定义一个QtThread。UpdateImageThread.h/.cpp

#pragma once

#include <QThread>
#include <QPixmap>
#include <QImage>
#include "../base/UIInterface.h"
#include <QDebug>

class UpdateImageThread : public QThread
{
    Q_OBJECT

public:
    UpdateImageThread(QObject *parent, UIInterface *base);
    UpdateImageThread( UIInterface* base);
    ~UpdateImageThread();

protected:
    void run();
    
signals:
    void updateUi(QPixmap pixmap);
private:
    UIInterface* base;
};
#include "UpdateImageThread.h"

UpdateImageThread::UpdateImageThread(QObject* parent, UIInterface* base)
    : QThread(parent)
{
    this->base = base;
}
UpdateImageThread::UpdateImageThread(UIInterface* base) {
    this->base = base;
}
//程序执行的run方法
void UpdateImageThread::run() {
    QPixmap pixmap = base->generate();
    qDebug() << "QThread 已执行完成,发送信号更新UI";
    emit updateUi(pixmap);
}
UpdateImageThread::~UpdateImageThread()
{

}

  这里面有一个自定义的UIInterface.h的头文件,里面定义了一个UIInterface的类,这个类被需要更新UI的界面继承,即CornerHarrisWindow类继承。类里定义了一个generate的方法,用于生成一个QPixmap。从外部传递这个地对象给当前线程,当前线程拿到这个对象后在run方法中调用generate方法。类定义如下:

#pragma once

#include <QPixmap>
class UIInterface
{

public:
    virtual QPixmap generate() {
        return NULL;
    }
};

  2.定义CornerHarrisWindow.h/.cpp,其中CornerHarrisWindow类继承IInterface类,并重写generate方法

#pragma once

#include <QWidget>
#include "CornerHarris.h"
#include "../../common/CommonGraphicsView.h"
#include <QPixmap>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QLabel>
#include <thread>
#include "../../common/base/UIInterface.h"
#include "../../common/thread/UpdateImageThread.h"
using namespace std;

class CornerHarrisWindow : public CommonGraphicsView, UIInterface
{
    Q_OBJECT

public:
    CornerHarrisWindow(QWidget* parent = nullptr);
    ~CornerHarrisWindow();

protected:
    QPixmap generate();

private:
    Mat src, gray, resultImage;
    CornerHarris* cornerHarris;
    int thresh = 130;


    QLineEdit* edit_blockSize;
    QLineEdit* edit_kSize;
    QLineEdit* edit_k;
    QLineEdit* edit_thresh;
    QPushButton* btn_submit;
    UpdateImageThread* mThread;

public:
    QPixmap showCornerHarris();
    void dropEvent(QDropEvent* event) override;

public slots:
    void updateImage(QPixmap pixmap);
};
#include "CornerHarrisWindow.h"

void runFun(CornerHarrisWindow* window) {
    qDebug() << "显示角点";
    window->showCornerHarris();
}

CornerHarrisWindow::CornerHarrisWindow(QWidget* parent)
    : CommonGraphicsView(parent)
{
    this->setWindowTitle("角点检测(cornerHarris)");
    cornerHarris = new CornerHarris();
    QLabel* blockSizeLabel = new QLabel(this);
    blockSizeLabel->setText("block_size:");
    blockSizeLabel->setFixedSize(QSize(80, 30));
    //创建一个输入框
    edit_blockSize = new QLineEdit(this);
    edit_blockSize->setPlaceholderText("请输入block_size");
    edit_blockSize->setText("2");
    edit_blockSize->setFixedSize(QSize(50, 30));
    edit_blockSize->move(blockSizeLabel->x() + blockSizeLabel->width() + 10, 0);

    QLabel* kLabel = new QLabel(this);
    kLabel->setText("k");
    kLabel->setFixedSize(QSize(80, 30));
    kLabel->move(0, edit_blockSize->y() + edit_blockSize->height() + 10);
    edit_k = new QLineEdit(this);
    edit_k->setPlaceholderText("请输入k");
    edit_k->setText("0.04");
    edit_k->setFixedSize(QSize(50, 30));
    edit_k->move(kLabel->x() + kLabel->width() + 10, edit_blockSize->y() + edit_blockSize->height() + 10);

    QLabel* kSizeLabel = new QLabel(this);
    kSizeLabel->setText("ksize:");
    kSizeLabel->setFixedSize(QSize(80, 30));
    kSizeLabel->move(0, edit_k->y() + edit_k->height() + 10);
    edit_kSize = new QLineEdit(this);
    edit_kSize->setText("3");
    edit_kSize->setFixedSize(QSize(50, 30));
    edit_kSize->move(kSizeLabel->x() + kSizeLabel->width() + 10, edit_k->y() + edit_k->height() + 10);

    QLabel* threshLabel = new QLabel(this);
    threshLabel->setText("thresh:");
    threshLabel->setFixedSize(QSize(80, 30));
    threshLabel->move(0, edit_kSize->y() + edit_kSize->height() + 10);
    edit_thresh = new QLineEdit(this);
    edit_thresh->setText("130");
    edit_thresh->setFixedSize(QSize(50, 30));
    edit_thresh->move(threshLabel->x() + threshLabel->width() + 10, edit_kSize->y() + edit_thresh->height() + 10);

    btn_submit = new QPushButton(this);
    btn_submit->setText("开始检测");
    btn_submit->move(0, edit_thresh->y() + edit_thresh->height() + 10);

    mThread = new UpdateImageThread(this);
    connect(btn_submit, &QPushButton::clicked, [=]() {
        qDebug() << "您点击了检测按钮:";
        qDebug() << "thresh:" << edit_thresh->text().toInt();
        qDebug() << "block_size:" << edit_blockSize->text().toInt();
        qDebug() << "ksize:" << edit_kSize->text().toInt();
        qDebug() << "k:" << edit_k->text().toDouble();
        //showCornerHarris();
        //std::thread myThread;
        //myThread = std::thread(runFun, this);
        mThread->start();
        });
    connect(mThread, &UpdateImageThread::updateUi, this, [=](QPixmap pixmap) {
        updateImage(pixmap);
        });


}
void CornerHarrisWindow::dropEvent(QDropEvent* event) {
    QString mPath = event->mimeData()->urls().at(0).toLocalFile();
    qDebug() << mPath;
    QFileInfo file(mPath);
    filePath = file.absoluteFilePath();
    //Mat src = imread(filePath.toStdString());
    //imshow("src", src);
   /* showCornerHarris();*/
    QPixmap pixmap = generate();
    updateImage(pixmap);
}
/**
 * 角点检测
 * @brief CornerHarrisWindow::showCornerHarris
 */
QPixmap CornerHarrisWindow::showCornerHarris() {
    cornerHarris->updateParams(edit_thresh->text().toInt(),
        edit_blockSize->text().toInt(),
        edit_kSize->text().toInt(),
        edit_k->text().toDouble());
    cornerHarris->showCornerHarris(filePath.toStdString().c_str(), resultImage);
    QImage image = ImageUtils::matToQImage(resultImage);
    QPixmap pixmap = QPixmap::fromImage(image);
    return pixmap;
    /*QGraphicsPixmapItem* item = new QGraphicsPixmapItem(pixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
    scene.addItem(item);*/
}

QPixmap CornerHarrisWindow::generate() {
    return showCornerHarris();
}

//更新UI
void CornerHarrisWindow::updateImage(QPixmap pixmap) {
    qDebug() << "收到信号开始更新UI";
    QGraphicsPixmapItem* item = new QGraphicsPixmapItem(pixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
    scene.addItem(item);
}
CornerHarrisWindow::~CornerHarrisWindow()
{

}

  1.在CornerHarrisWindow.h中定义了一个槽函数updateImage,这个槽函数就是用于接收UI的。

public slots:
    void updateImage(QPixmap pixmap);

  2.信号是在UpdateImageThread.h中定义的updateUi

signals:
    void updateUi(QPixmap pixmap);

  3.发射信号是在UpdateImageThread中的run方法,使用emit 信号发出的

void UpdateImageThread::run() {
    QPixmap pixmap = base->generate();
    qDebug() << "QThread 已执行完成,发送信号更新UI";
    emit updateUi(pixmap);
}

  4.关联信号和槽函数

    mThread = new UpdateImageThread(this);
    connect(btn_submit, &QPushButton::clicked, [=]() {
        //myThread = std::thread(runFun, this);
        mThread->start();
        });
    connect(mThread, &UpdateImageThread::updateUi, this, [=](QPixmap pixmap) {
        updateImage(pixmap);
        });

    所以信号是由QThread发出,并由CornerHarrisWindow类接收,最终触发槽函数updateImage,然后更新UI