[Qt] Use QWidget to display camera images collected by opencv

written in front

This case uses the QWidget container to rewrite the paintEvent function to display the camera image collected by OpenCv. The image can also adapt to the size of the QWidget, and can also detect the disconnection of the camera (perhaps due to power outage or unplugged waiting). Pause the image display when changing the window size to prevent inexplicable lag and crash errors! (There are many ways to display images. You can use QLabel to display images. Although it is simple, QLabel is not recommended for this kind of video screen; you can also use QGraphicsView to display the image. The next article will be updated)

Please add image description

1. New construction projects

1. Create a new Qt project, add a Widget container and two buttons to the UI, and add a suitable layout. Set the ObjectName of the Widget to CameraWidget"Open Camera openCamera" and "Close Camera closeCamera"
Insert image description here
. 2. Create a new task class for Opencv to collect camera images and send signals. Use moveToThread to add the task class to the child thread.
3. Create a new class CameraWidget in Qt, inherit QWidget, and promote the Widget control in the UI to this class.

2. Collect images

1. Configure the OpenCv environment in the Qt project (this article assumes that you have already configured it, otherwise see my other article: QT configuration opencv346 environment (MinGw compiler) .
2. Create a new task class. The main task of this class is: collection Image, send signal, close the camera and exit the thread.
3. The code is as follows

Camera.h

#ifndef CAMERA_H
#define CAMERA_H

#include <QObject>
#include <QTimer>
#include <QPixmap>

#include <opencv2/opencv.hpp>
using namespace cv;

class Camera : public QObject
{
    
    
    Q_OBJECT
public:
    explicit Camera(QObject *parent = nullptr);

    void openCamera();      // 打开相机
    void closeCamera();     // 关闭相机

private:
    VideoCapture *cap;     // 相机
    QTimer *capTimer;       // 采集图像定时器
    bool cameraIsOpened = false;        // 相机打开标志位

signals:
    void cameraShowImage(QPixmap);      // 发送图像信号
    void cameraIsOpen(bool);            // 相机打开信号
};

#endif // CAMERA_H

Camera.cpp

#pragma execution_character_set("utf-8")
#include "camera.h"
#include <QDebug>
#include <QThread>

Camera::Camera(QObject *parent) : QObject(parent)
{
    
    

}

void Camera::openCamera()
{
    
    
    qDebug() << "子线程:" << QThread::currentThreadId();
    // 打开相机
    int cameraDevId = 0;
    this->cap = new VideoCapture;
    this->cap->open(cameraDevId, cv::CAP_DSHOW);     // 必须带上 CAP_DSHOW ,否则会报错 [ WARN:1] terminating async callback,然后再次打开就打不开了
    if(!this->cap->isOpened()){
    
    
        this->cap->release();		// 释放相机
        qDebug() << "相机打开失败!";
        return;
    }
    this->cameraIsOpened = true;
    emit this->cameraIsOpen(true);     // 相机打开

    qDebug() << QString("相机参数: (%1, %2), %3 FPS").arg(cap->get(3)).arg(cap->get(4)).arg(cap->get(5));

    this->capTimer = new QTimer;
    connect(this->capTimer, &QTimer::timeout, [=]() {
    
    
//        qDebug() << "子线程:" << QThread::currentThreadId();
        Mat frame;
        // 采集图像
        this->cap->read(frame);
        if(frame.empty()){
    
    
            qDebug() << "相机可能断开";
            this->closeCamera();        // 关闭相机
            return ;
        }
        // 将OpenCv的BGR图像转换成正常的RGB图像
        cvtColor(frame, frame, cv::COLOR_BGR2RGB);
        // 将OpenCv的图像转换成Qt的QImage
        QPixmap showImage = QPixmap::fromImage(QImage((const uchar*)(frame.data),
                                                      frame.cols,
                                                      frame.rows,
                                                      frame.step,
                                                      QImage::Format_RGB888));
        // 将QPixmap图像通过信号发送出去
        emit this->cameraShowImage(showImage);
    });
    // 采样时间可以根据相机的FPS修改
    this->capTimer->start(40);
}

void Camera::closeCamera()
{
    
    
    qDebug() << "子线程:" << QThread::currentThreadId();
    if(!this->cameraIsOpened) return;

    if(this->capTimer->isActive()){
    
    
        this->capTimer->stop();
        this->capTimer->deleteLater();

        this->cameraIsOpened = false;
        emit this->cameraIsOpen(false);     // 相机关闭

        this->cap->release();		// 释放相机
        cv::destroyAllWindows();	// 销毁OpenCv的窗口
    }
}

3. Display image

1. Create a new class that inherits QWidget, and promote the Widget control in the UI to this class.
Insert image description here

2. Rewrite the paintEvent function to scale and draw the image according to the size of the widget; design a variable showCameraImagethat will trueonly display the drawn image if it is.
3. Create a new timer and rewrite the resizeEvent function; when the window size changes, it will showCameraImagebe set to false, and then the timer will be refreshed continuously. showCameraImageWhen the window size no longer changes, the timer does not refresh and will be set to trueredraw the image after the time is up .
4. Create several member functions to update the image.
5. The code is as follows:

CameraWidget.h

#ifndef CAMERAWIDGET_H
#define CAMERAWIDGET_H

#include <QWidget>
#include <QTimer>
#include <QPixmap>

class CameraWidget : public QWidget
{
    
    
    Q_OBJECT
public:
    explicit CameraWidget(QWidget *parent = nullptr);

    void showImage(bool b);      // 显示图像
    void setImage(QPixmap img);     // 更新图像

protected:
    void resizeEvent(QResizeEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private:
    QPixmap cvImage;                // 相机图像
    QTimer *noShowCameraImage;      // 不显示图像定时器
    bool showCameraImage = true;    // 显示图像
    
    bool camerIsOpened = false;     // 相机打开

signals:

};

#endif // CAMERAWIDGET_H

CameraWidget.cpp

#pragma execution_character_set("utf-8")
#include "camerawidget.h"
#include <QDebug>
#include <QPainter>

#define CAMERA_IMAGE_WIDTH 640
#define CAMERA_IMAGE_HEIGHT 480

CameraWidget::CameraWidget(QWidget *parent) : QWidget(parent)
{
    
    
    this->noShowCameraImage = new QTimer;
    connect(this->noShowCameraImage, &QTimer::timeout, this, [=](){
    
    
        this->showCameraImage = true;
        this->noShowCameraImage->stop();
        this->update();
    }); 

    this->update();
}

void CameraWidget::showImage(bool b)
{
    
    
    this->camerIsOpened = b;
    update();
}

void CameraWidget::setImage(QPixmap img)
{
    
    
    this->cvImage = img;
    update();
}

/**********************************************************
 * @brief label大小变化事件
**********************************************************/
void CameraWidget::resizeEvent(QResizeEvent *event)
{
    
    
    this->showCameraImage = false;
    // 定时器刷新时间,当窗口大小不变后多长时间恢复图像
    // 这涉及到用户体验,设置适当的值即可
    this->noShowCameraImage->setInterval(200);
    this->noShowCameraImage->start();

    QWidget::resizeEvent(event);
}

void CameraWidget::paintEvent(QPaintEvent *event)
{
    
    
    QPainter painter(this);

    // 背景颜色
    painter.setPen(QColor(192, 192, 192));
    painter.setBrush(QColor(192, 192, 192));
    painter.drawRect(0, 0, this->width(), this->height());

    // 等比缩放,绘制图像
    if(this->showCameraImage && this->camerIsOpened){
    
    
        // 寻找图像大小和widget大小的缩放关系
        QRectF imgRect = this->cvImage.rect();
        QRectF widgetRect = this->rect();
        double factor = qMin(widgetRect.width() / imgRect.width(), widgetRect.height() / imgRect.height());
        // 计算新的Rect
        int imgWidth = imgRect.width() * factor;
        int imgHeight = imgRect.height() * factor;
        int startX = (this->width() - imgWidth) / 2;
        int startY = (this->height() - imgHeight) / 2;
        // 显示图像
        painter.drawPixmap(startX, startY, imgWidth, imgHeight, this->cvImage);
    }

    QWidget::paintEvent(event);
}

4. Main window

In the main thread, the main task is to create sub-threads and bind signal slots.

#pragma execution_character_set("utf-8")
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDebug>

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

    qDebug() << "主线程:" << QThread::currentThreadId();

    // 相机子线程
    this->camera = new Camera;
    this->cameraThread = new QThread;
    this->camera->moveToThread(this->cameraThread);
    connect(this->cameraThread, &QThread::finished, this->cameraThread, &QThread::deleteLater);
    connect(this->cameraThread, &QThread::finished, this->camera, &Camera::deleteLater);
    connect(this->camera, &Camera::cameraShowImage, ui->cameraWidget, &CameraWidget::setImage);		// 更新图像
    this->cameraThread->start();

    connect(ui->openCamera, &QPushButton::clicked, this->camera, &Camera::openCamera);      // 打开相机
    connect(ui->closeCamera, &QPushButton::clicked, this->camera, &Camera::closeCamera);    // 关闭相机

    connect(this->camera, &Camera::cameraIsOpen, ui->cameraWidget, &CameraWidget::showImage);
}

MainWindow::~MainWindow()
{
    
    
    if(this->cameraThread->isRunning()){
    
    
        this->cameraThread->quit();
        this->cameraThread->wait();
    }
    delete ui;
}

Guess you like

Origin blog.csdn.net/iiinoname/article/details/126976718