当前位置: 代码迷 >> 综合 >> Qt5.7 + OpenCV3.2开启多线程调用系统摄像头并实现视频录制与回放、图片截取与保存(三)多线程实现
  详细解决方案

Qt5.7 + OpenCV3.2开启多线程调用系统摄像头并实现视频录制与回放、图片截取与保存(三)多线程实现

热度:288   发布时间:2023-10-08 17:05:16.0

在上一篇博客中,我们介绍了如何使用OpenCV在主线程中实现实时画面显示以及视频的存储与回放,本文主要介绍如何将摄像头的画面获取放到子线程中


关于线程的创建本文采用继承于QObject+MoveToThread的方法,具体创建方法可以移步Qt多线程的创建详解,本文不做赘述


一、项目创建

首先还是创建一个主窗口项目,命名为multiThreadCamera,完成后在项目上右击–>添加新文件–>C++类,类名为CamThread,继承于QObject,源文件和头文件默认为camthread.cppcamthread.h,并在头文件中加入OpenCV的头文件

#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui/highgui.hpp>

声明相关的私有变量

private:cv::VideoCapture capture;cv::VideoWriter writer;cv::Mat src_image;bool stopFlag=false;int camera_num = 0;

然后在mainwindow.h中添加

#include <QThread>
#include <QTimer>
#include <QDebug>
#include <QThread>
#include <QCameraInfo> 
#include <QList>
#include "camthread.h"

UI界面如图所示
Qt5.7 + OpenCV3.2开启多线程调用系统摄像头并实现视频录制与回放、图片截取与保存(三)多线程实现
控件说明:

控件名 作用
label_videoviewer 画面显示
camera_name 显示摄像设备
pushbutton_searchcamera 查找摄像设备
pushbutton_opencamera 打开摄像头
pushbutton_closecamera 关闭摄像头
pushbutton_savevideo 保存视频
pushbutton_savecomplete 结束保存
pushbutton_videoreview 视频回放

二、功能实现

在编写代码前 ,脑中还是要有一个思路,那就是主线程是干嘛的,子线程是干嘛的
本项目中显而易见——主线程负责画面显示及指令响应,子线程负责调用视频设备与获取画面,线程之间通信主要采用信号与槽机制。搞清楚这些,接下来一步步实现——


这里代码虽然零散,但体现了我编写代码时的一个思路历程,初学者可以尝试阅读 找找思路 文章最后附有完整代码


  1. 线程的创建
    mainwindow.h:

    private:Ui::MainWindow *ui;QThread *firstThread;CamThread *MyCamThread;QList<QCameraInfo> camera_list;QTimer fps_timer;
    

    mainwindow.cpp:

     ui->setupUi(this);firstThread = new QThread;MyCamThread = new CamThread;MyCamThread->moveToThread(firstThread);
    
  2. 各按钮槽函数(直接右击–>转到槽)
    2.1 查找摄像头时先清空下拉框,然后将查到的摄像头信息依次加入到下拉框中:

    void MainWindow::on_pushButton_searchcamera_clicked()
    {
          ui->camera_name->clear();camera_list = QCameraInfo::availableCameras();for(auto i =0;i<camera_list.size();i++){
          ui->camera_name->addItem(camera_list.at(i).description());}
    }
    

    2.2 打开摄像头时应先获取摄像头标号,在子线程中打开:

    //获取摄像头标号
    void CamThread::camNumber(const int &n)
    {
          camera_num = n; //camera_num 是全局变量,在camthread.h中
    }
    //打开摄像头
    void CamThread::openCamera()
    {
          capture.open(camera_num);if(!capture.isOpened()){
          return;}
    }
    

    主线程中开启子线程、发送标号、启动定时器、打开摄像头:

    void MainWindow::on_pushButton_opencamera_clicked()
    {
          if(ui->camera_name->currentIndex() >= 0){
          firstThread->start();MyCamThread->camNumber(ui->camera_name->currentIndex());fps_timer.start();MyCamThread->openCamera();}else //没有找到视频设备QMessageBox::information(this,tr("Error"),tr("Have No Camera Device!"),QMessageBox::Ok);
    }
    

    2.3 视频在label上显示
    这里主要是通过让子线程每隔50ms向主线程发送一帧画面,然后主线程接受并显示。
    主线程:

     connect(&fps_timer,    SIGNAL(timeout()), MyCamThread, SLOT(mainwindowDisplay()));connect(MyCamThread,SIGNAL(sendPicture(QImage)),this,SLOT(recivePicture(QImage)));fps_timer.setInterval(50);void MainWindow::recivePicture(QImage img)
    {
          ui->label_videoViewer->setPixmap(QPixmap::fromImage(img));
    }
    

    子线程:

    void CamThread::mainwindowDisplay()
    {
          capture >> src_image;QImage img1 = QImage((const unsigned char*)src_image.data,src_image.cols, src_image.rows, QImage::Format_RGB888).rgbSwapped();emit sendPicture(img1);
    }
    

    2.4 关闭摄像头:

    void CamThread::closeCamera()
    {
          capture.release();writer.release();
    }
    

    主线程中:

    void MainWindow::on_pushButton_closecamera_clicked()
    {
          fps_timer.stop();ui->label_videoViewer->clear();MyCamThread->closeCamera();firstThread->quit();firstThread->wait();
    }
    

    2.5保存视频:

    void CamThread::startsave()
    {
          QString path = QCoreApplication::applicationDirPath().append("/Video/").append(QDateTime::currentDateTime().toString("yyyyMMddhhmmss")).append(".avi");cv::String file_path = path.toStdString() ;writer.open(file_path,cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), 20.0, cv::Size(640, 480));while(!stopFlag){
          capture >> src_image;writer.write(src_image);cv::namedWindow("video", cv::WINDOW_NORMAL);cv::imshow("video", src_image);cv::waitKey(50);}
    }
    

    主线程中:

    	void MainWindow::on_pushButton_savevideo_clicked(){
          MyCamThread->setFlag(false);MyCamThread->startsave();}
    

    2.6 保存完成

    void CamThread::closeImshow()
    {
          cv::destroyWindow("video");
    }
    
    void MainWindow::on_pushButton_savecomplete_clicked()
    {
          MyCamThread->setFlag(true);MyCamThread->closeImshow();
    }
    

    2.7 视频回放
    和在主线程的操作一样,只是最后回访结束之后发送结束信号,主线程再进行相应操作。

    void CamThread::reviewVideo()
    {
          cv::VideoCapture video;cv::Mat video_src;QString path = QFileDialog::getOpenFileName(0,"打开","../","");cv::String openpath = path.toStdString();video.open(openpath);while(video.isOpened()){
          video>>video_src;if(video_src.empty())break;cv::imshow("video_review",video_src);if(cv::waitKey(50)==27){
          cv::destroyWindow("video_review");break;}}emit reviewComplete();
    }
    

    主线程:
    因为回放是在摄像头关闭的情况下进行的,所以回放时先开启线程,回访结束后关闭线程。

    void MainWindow::on_pushButton_videoreview_clicked()
    {
          firstThread->start();MyCamThread->reviewVideo();
    }connect(MyCamThread,SIGNAL(reviewComplete()),this,SLOT(reviewVideo_complete()));void MainWindow::reviewVideo_complete()
    {
          firstThread->quit();firstThread->wait();
    }

三、总结

从上面的代码看下来或许可以加深对于主线程、子线程的理解——主线程主要负责UI显示、信号与槽函数的映射、函数的调用,子线程才是真正的实现这些功能的地方。
另外,为了防止窗口关闭时视频设备资源并未被完全释放,所以修改析构函数:

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

使直接关闭窗口时资源也能被回收


附上完整代码:
camthread.h:

#ifndef CAMTHREAD_H
#define CAMTHREAD_H#include <QObject>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui/highgui.hpp>class CamThread :public QObject
{
    Q_OBJECT
public:explicit CamThread(QObject *parent = 0);
signals:void reviewComplete();void sendPicture(const QImage &img);
public slots:void setFlag(bool flag = false);void openCamera();void closeCamera();void startsave();void camNumber(const int &n);void reviewVideo();void closeImshow();void mainwindowDisplay();
private slots:private:cv::VideoCapture capture;cv::VideoWriter writer;cv::Mat src_image;bool stopFlag=false;int camera_num = 0;
};#endif // CAMTHREAD_H

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QTimer>
#include <QDebug>
#include <QThread>
#include <QCameraInfo>
#include <QList>
#include "camthread.h"
namespace Ui {
    
class MainWindow;
}class MainWindow : public QMainWindow
{
    Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);~MainWindow();private slots:void on_pushButton_opencamera_clicked();void on_pushButton_closecamera_clicked();void on_pushButton_savevideo_clicked();void on_pushButton_savecomplete_clicked();void on_pushButton_videoreview_clicked();void display_frame();void on_pushButton_searchcamera_clicked();void recivePicture(QImage img);void reviewVideo_complete();
private:Ui::MainWindow *ui;QThread *firstThread;CamThread *MyCamThread;QList<QCameraInfo> camera_list;QTimer fps_timer;
};#endif // MAINWINDOW_H

camthread.cpp:

#include "camthread.h"
#include <QMessageBox>
#include <iostream>
#include <QDebug>
#include <QFileDialog>
#include <QDateTime>
#include <QCoreApplication>
CamThread::CamThread(QObject *parent) : QObject(parent)
{
    stopFlag = false;
}
void CamThread::startsave()
{
    QString path = QCoreApplication::applicationDirPath().append("/Video/").append(QDateTime::currentDateTime().toString("yyyyMMddhhmmss")).append(".avi");cv::String file_path = path.toStdString() ;writer.open(file_path,cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), 20.0, cv::Size(640, 480));while(!stopFlag){
    capture >> src_image;writer.write(src_image);cv::namedWindow("video", cv::WINDOW_NORMAL);cv::imshow("video", src_image);cv::waitKey(50);}
}
void CamThread::mainwindowDisplay()
{
    capture >> src_image;QImage img1 = QImage((const unsigned char*)src_image.data,src_image.cols, src_image.rows, QImage::Format_RGB888).rgbSwapped();emit sendPicture(img1);
}
void CamThread::camNumber(const int &n)
{
    camera_num = n;
}
void CamThread::openCamera()
{
    capture.open(camera_num);if(!capture.isOpened()){
    return;}
}
void CamThread::closeCamera()
{
    if(!stopFlag)   // 如果还在保存视频 则关闭cv窗口{
    cv::destroyWindow("video");}capture.release();writer.release();
}
void CamThread::setFlag(bool flag)
{
    stopFlag = flag;
}void CamThread::closeImshow()
{
    cv::destroyWindow("video");
}
void CamThread::reviewVideo()
{
    cv::VideoCapture video;cv::Mat video_src;QString path = QFileDialog::getOpenFileName(0,"打开","../","");cv::String openpath = path.toStdString();video.open(openpath);while(video.isOpened()){
    video>>video_src;if(video_src.empty())break;cv::imshow("video_review",video_src);if(cv::waitKey(50)==27){
    cv::destroyWindow("video_review");break;}}emit reviewComplete();
}

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <Qdir>
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{
    ui->setupUi(this);firstThread = new QThread;MyCamThread = new CamThread;MyCamThread->moveToThread(firstThread);connect(&fps_timer,    SIGNAL(timeout()), MyCamThread, SLOT(mainwindowDisplay()));connect(MyCamThread,SIGNAL(sendPicture(QImage)),this,SLOT(recivePicture(QImage)));fps_timer.setInterval(50);connect(MyCamThread,SIGNAL(reviewComplete()),this,SLOT(reviewVideo_complete()));QString save_picture = QCoreApplication::applicationDirPath();QDir dir;dir.cd(save_picture);if(!dir.exists("video")){
    dir.mkdir("video");}
}MainWindow::~MainWindow()
{
    delete ui;delete MyCamThread;
}void MainWindow::on_pushButton_opencamera_clicked()
{
    if(ui->camera_name->currentIndex() >= 0){
    firstThread->start();MyCamThread->camNumber(ui->camera_name->currentIndex());fps_timer.start();MyCamThread->openCamera();}elseQMessageBox::information(this,tr("Error"),tr("Have No Camera Device!"),QMessageBox::Ok);
}void MainWindow::on_pushButton_closecamera_clicked()
{
    fps_timer.stop();ui->label_videoViewer->clear();MyCamThread->closeCamera();firstThread->quit();firstThread->wait();
}void MainWindow::on_pushButton_savevideo_clicked()
{
    MyCamThread->setFlag(false);MyCamThread->startsave();
}void MainWindow::on_pushButton_savecomplete_clicked()
{
    MyCamThread->setFlag(true);MyCamThread->closeImshow();
}void MainWindow::on_pushButton_videoreview_clicked()
{
    firstThread->start();MyCamThread->reviewVideo();
}void MainWindow::display_frame()
{
    }void MainWindow::on_pushButton_searchcamera_clicked()
{
    ui->camera_name->clear();camera_list = QCameraInfo::availableCameras();for(auto i =0;i<camera_list.size();i++){
    ui->camera_name->addItem(camera_list.at(i).description());}
}
void MainWindow::recivePicture(QImage img)
{
    ui->label_videoViewer->setPixmap(QPixmap::fromImage(img));
}
void MainWindow::reviewVideo_complete()
{
    firstThread->quit();firstThread->wait();
}

项目连接:https://gitee.com/Mr-Yslf/BlogResources/tree/master/MultiThreadCamera

  相关解决方案