一百行代码搞定Qt相机多线程

单线程

首先我们在Qt designer中创建一个简单的界面,只包含一个label用来显示相机图像,和两个pushButton分别用来开启和关闭相机。
在这里插入图片描述
其对应的ui_mainwindow.h内容为:

/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.14.2
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow
{
public:
    QWidget *centralwidget;
    QLabel *label;
    QPushButton *pushButton_open_camera;
    QPushButton *pushButton_close_camera;
    QMenuBar *menubar;
    QStatusBar *statusbar;

    void setupUi(QMainWindow *MainWindow)
    {
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
        MainWindow->resize(800, 600);
        centralwidget = new QWidget(MainWindow);
        centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
        label = new QLabel(centralwidget);
        label->setObjectName(QString::fromUtf8("label"));
        label->setGeometry(QRect(80, 20, 640, 480));
        label->setStyleSheet(QString::fromUtf8("background:rgb(255, 255, 255)"));
        pushButton_open_camera = new QPushButton(centralwidget);
        pushButton_open_camera->setObjectName(QString::fromUtf8("pushButton_open_camera"));
        pushButton_open_camera->setGeometry(QRect(150, 520, 120, 30));
        pushButton_close_camera = new QPushButton(centralwidget);
        pushButton_close_camera->setObjectName(QString::fromUtf8("pushButton_close_camera"));
        pushButton_close_camera->setGeometry(QRect(530, 520, 120, 30));
        MainWindow->setCentralWidget(centralwidget);
        menubar = new QMenuBar(MainWindow);
        menubar->setObjectName(QString::fromUtf8("menubar"));
        menubar->setGeometry(QRect(0, 0, 800, 21));
        MainWindow->setMenuBar(menubar);
        statusbar = new QStatusBar(MainWindow);
        statusbar->setObjectName(QString::fromUtf8("statusbar"));
        MainWindow->setStatusBar(statusbar);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
    } // setupUi

    void retranslateUi(QMainWindow *MainWindow)
    {
        MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
        label->setText(QString());
        pushButton_open_camera->setText(QCoreApplication::translate("MainWindow", "\346\211\223\345\274\200\347\233\270\346\234\272", nullptr));
        pushButton_close_camera->setText(QCoreApplication::translate("MainWindow", "\345\205\263\351\227\255\347\233\270\346\234\272", nullptr));
    } // retranslateUi

};

namespace Ui {
    class MainWindow: public Ui_MainWindow {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_MAINWINDOW_H

main.cpp不用修改:

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

接下来我们编写mainwindow的逻辑:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H


#include <opencv2/opencv.hpp>

#include <QMainWindow>
#include <QThread>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    MainWindow(QWidget *parent = nullptr);
    
    ~MainWindow();
    
private slots:
    void on_pushButton_open_camera_clicked();
    
    void on_pushButton_close_camera_clicked();
    
private:
    Ui::MainWindow *ui;
    
    cv::VideoCapture cap;
    
    cv::Mat frame;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_open_camera_clicked()
{
    cap.open(0);
    while (cv::waitKey(1) < 0)
    {
        cap.read(frame);
        QImage image = QImage((const uchar*)frame.data, frame.cols, frame.rows, QImage::Format_RGB888).rgbSwapped();
        ui->label->setPixmap(QPixmap::fromImage(image));
        ui->label->show();
    }
}

void MainWindow::on_pushButton_close_camera_clicked()
{
    cap.release();
    ui->label->clear();
}

这样,一个单线程Qt相机采图显示界面就做完了。

一个线程采图一个线程显示

修改上面的程序,引入相机采图类Capture并继承QThread类:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H


#include <opencv2/opencv.hpp>

#include <QMainWindow>
#include <QThread>
#include <QDebug>

class Capture : public QThread
{
  Q_OBJECT

public:
    cv::VideoCapture cap;

    bool is_opened = false;

private:
    void run();

signals:
    void send_image(const QImage &qimage);
};

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();

private slots:
    void on_pushButton_open_camera_clicked();

    void on_pushButton_close_camera_clicked();

    void show_image(const QImage &qimage);

private:
    Ui::MainWindow *ui;

    Capture *capture;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    capture = new Capture;
    connect(capture, &Capture::send_image, this, &MainWindow::show_image);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_open_camera_clicked()
{
    capture->cap.open(0);
    capture->start();
    capture->is_opened = true;
}

void MainWindow::on_pushButton_close_camera_clicked()
{
    capture->quit();
    capture->cap.release();
    ui->label->clear();
    capture->is_opened = false;
}

void MainWindow::show_image(const QImage &qimage)
{
    std::cout << std::this_thread::get_id() << " show image..." << std::endl;
    ui->label->setPixmap(QPixmap::fromImage(qimage));
    ui->label->show();
}

void Capture::run()
{
    cv::Mat frame;
    while (cv::waitKey(1) < 0 && is_opened)
    {
        std::cout << std::this_thread::get_id() << " grab image..." << std::endl;
        cap.read(frame);
        QImage qimage = QImage(frame.data, frame.cols, frame.rows, QImage::Format_RGB888).rgbSwapped();
        send_image(qimage);
    }
}

运行程序,相机采图正常,且打印如下:

14324 grab image...
7456 show image...
14324 grab image...
7456 show image...
14324 grab image...
7456 show image...
14324 grab image...
7456 show image...

可以看到采图和显示图像的线程id是不同的,分别在两个线程中运行。

一个线程采图一个线程运算一个线程显示

在上面的基础上继续增加图像处理类Process 并继承QThread类:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H


#include <opencv2/opencv.hpp>

#include <QMainWindow>
#include <QThread>
#include <QDebug>

class Capture : public QThread
{
  Q_OBJECT

public:
    cv::VideoCapture cap;

    bool is_opened = false;

private:
    void run();

signals:
    void send_image(const QImage &qimage);
};

class Process : public QThread
{
  Q_OBJECT

public:
    void receive_image(const QImage &qimage);

    bool is_opened = false;

private:
    void run();

    QImage image;

signals:
    void send_image(const QImage &qimage);
};

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

    ~MainWindow();

private slots:
    void on_pushButton_open_camera_clicked();

    void on_pushButton_close_camera_clicked();

    void show_image(const QImage &qimage);

private:
    Ui::MainWindow *ui;

    Capture *capture;

    Process *process;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    capture = new Capture;
    process = new Process;
    connect(capture, &Capture::send_image, process, &Process::receive_image);
    connect(process, &Process::send_image, this, &MainWindow::show_image);
}

MainWindow::~MainWindow()
{
    capture->quit();
    capture->cap.release();
    process->quit();
    delete ui;
    delete capture;
    delete process;
}

void MainWindow::on_pushButton_open_camera_clicked()
{
    capture->cap.open(0);
    capture->start();
    process->start();
    capture->is_opened = true;
    process->is_opened = true;
}

void MainWindow::on_pushButton_close_camera_clicked()
{
    capture->is_opened = false;
    process->is_opened = false;
    capture->quit();
    capture->cap.release();
    process->quit();
    ui->label->clear();
}

void MainWindow::show_image(const QImage &qimage)
{
    if(!capture->is_opened) return;
    std::cout << std::this_thread::get_id() << " show image..." << std::endl;
    ui->label->setPixmap(QPixmap::fromImage(qimage));
    ui->label->show();
}

void Capture::run()
{
    cv::Mat frame;
    while (is_opened && cv::waitKey(1) < 0)
    {
        std::cout << std::this_thread::get_id() << " grab image..." << std::endl;
        cap.read(frame);
        if (frame.empty())  continue;
        QImage qimage = QImage(frame.data, frame.cols, frame.rows, QImage::Format_RGB888).rgbSwapped();
        send_image(qimage);
    }
}

void Process::run()
{
    while(is_opened && cv::waitKey(1) < 0)
    {
         std::cout << std::this_thread::get_id() << " process image..." << std::endl;
         QImage qimage_processed = image.convertToFormat(QImage::Format_Grayscale8);
         send_image(qimage_processed);
    }
}

void Process::receive_image(const QImage &qimage)
{
    image = qimage;
}

程序运行时打印如下:

5768 grab image...
13116 process image...
7832 show image...
5768 grab image...
13116 process image...
7832 show image...
5768 grab image...
13116 process image...
7832 show image...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给算法爸爸上香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值