在上一篇博客中,我们介绍了如何使用OpenCV在主线程中实现实时画面显示以及视频的存储与回放,本文主要介绍如何将摄像头的画面获取放到子线程中
关于线程的创建本文采用继承于QObject
+MoveToThread
的方法,具体创建方法可以移步Qt多线程的创建详解,本文不做赘述
一、项目创建
首先还是创建一个主窗口项目,命名为multiThreadCamera,完成后在项目上右击–>添加新文件–>C++类,类名为CamThread
,继承于QObject
,源文件和头文件默认为camthread.cpp
和camthread.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
界面如图所示
控件说明:
控件名 | 作用 |
---|---|
label_videoviewer | 画面显示 |
camera_name | 显示摄像设备 |
pushbutton_searchcamera | 查找摄像设备 |
pushbutton_opencamera | 打开摄像头 |
pushbutton_closecamera | 关闭摄像头 |
pushbutton_savevideo | 保存视频 |
pushbutton_savecomplete | 结束保存 |
pushbutton_videoreview | 视频回放 |
二、功能实现
在编写代码前 ,脑中还是要有一个思路,那就是主线程是干嘛的,子线程是干嘛的
本项目中显而易见——主线程负责画面显示及指令响应,子线程负责调用视频设备与获取画面,线程之间通信主要采用信号与槽机制。搞清楚这些,接下来一步步实现——
这里代码虽然零散,但体现了我编写代码时的一个思路历程,初学者可以尝试阅读 找找思路 文章最后附有完整代码
-
线程的创建
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.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_OBJECT
public:
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();
}
else
QMessageBox::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