在上一篇博客中,我们介绍了OpenCV中常用的类,并且实现了使用OpenCV加载本地的一张图片,本篇文章将讲解如何使用OpenCV调用系统摄像头,实现实时画面显示以及视频的存储与回放
事实上,视频的显示与图片显示原理一样,只不过视频是N多张图片叠放在一起的结果——显示摄像头画面时,使用VideoCapture
捕捉摄像头画面,借助定时器每隔相同的时间在窗口中显示一帧;视频存储是将图片按照一定的频率压入*.avi
视频文件中;回放则是按照写入的频率,将图片一帧一帧读出来并显示
接下来为大家详细介绍——
首先还是创建一个主窗口项目,命名为multiThreadCamera,添加OpenCV头文件及变量声明:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
using namespace cv;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
VideoCapture myCapture;
VideoWriter writer;
Mat picture;
QTimer fps_timer;
};
#endif // MAINWINDOW_H
变量的声明中,QTimer
是为了设置视频的帧频fps
,其定时时间(ms)为1000 ms / fps
VideoCapture
与Videowriter
介绍可以参照上一篇博客,VideoCapture
读取的是视频的每一个原始帧;这些原始帧转换为Mat
类型后,通过Videowriter
写入视频文件。
控件名 | 作用 |
---|---|
label_videoviewer |
画面显示 |
pushbutton_opencamera |
打开摄像头 |
pushbutton_closecamera |
关闭摄像头 |
pushbutton_savevideo |
保存视频 |
pushbutton_savecomplete |
结束保存 |
pushbutton_videoreview |
视频回放 |
然后创建槽函数:(我是为了方便直接右击——转到槽函数)
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();
接下来是cpp文件编写:
在构造函数中添加定时器的初始化与槽函数:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
fps_timer.setInterval(40);//定时器设定时间40ms,即每秒钟显示25张画面
connect(&fps_timer,SIGNAL(timeout()),this,SLOT(display_frame()));
}
接下来是几个按钮的槽函数:
on_pushButton_opencamera_clicked():
此按钮需要实现的功能是:打开系统摄像头、获取并显示画面。事实上,显示画面需要在定时器的槽函数display_frame()
中实现,故在此我们只需打开定时器。
void MainWindow::on_pushButton_opencamera_clicked()
{
myCapture.open(0);
fps_timer.start();
}
其中需要说明的是virtual bool open(int index);
是一个虚函数,一个参数int index表示摄像头的标号,从0开始,如果计算机有n个摄像头,那么那么参数可以选择0到n-1,如果超过n-1编译器不会报错,但是程序会直接崩溃);
我们看到该函数还有一个bool
类型的返回值,用来返回摄像头是否成功打开,所以槽函数可以重新完善一下:
void MainWindow::on_pushButton_opencamera_clicked()
{
myCapture.open(0);
if(!myCapture.open(0))
{
qDebug()<<"Camera Open Failed.";
return;
}
fps_timer.start();
}
display_frame():
摄像头与定时器成功开启之后,接下来就要在定时器的槽函数中实现画面的显示:
void MainWindow::display_frame()
{
myCapture >> picture;
QImage img1 = QImage((const unsigned char*)picture.data, picture.cols, picture.rows, QImage::Format_RGB888).rgbSwapped();
ui->label_videoViewer->setPixmap(QPixmap::fromImage(img1));
}
几点说明:
- 我们要在定时器槽函数中实现的就是两个功能——画面的抓取与UI界面的显示,代码的第一和第三句则分别实现了这两个功能:
myCapture >> picture;
采用了C++中数据流的方式将摄像头的当前帧读回来,实际上该语句可以替换为myCapture.read(picture);
,可能更好理解,读者可以尝试;
- 第三句ui->label_videoViewer->setPixmap(QPixmap::fromImage(img1));
就更好理解了,将QImage
形式的图片转为QPixmap
并显示在QLabel
上。画面虽然是一帧一帧的,但是当一秒钟显示25帧时就变成了视频。
- 关键是第二句话,它提供了一种将Mat
转为QImage
的方法,我们这里默认的是彩色图片的转换,关于其他类型的转换方法可以参考这篇博客https://blog.csdn.net/liyuanbhu/article/details/86307283
另外再补充一句,这里我们选择的是在主窗口中采用QLabel
控件显示画面,如果要像上一篇博客那样采用OpenCV
中的窗口显示也应在定时器槽函数中实现:
void MainWindow::display_frame()
{
myCapture >> picture;
QImage img1 = QImage((const unsigned char*)picture.data, picture.cols, picture.rows, QImage::Format_RGB888).rgbSwapped();
ui->label_videoViewer->setPixmap(QPixmap::fromImage(img1));
namedWindow("VideoPlay", WINDOW_NORMAL);
imshow("VideoPlay", picture);
waitKey(40);
}
这里要加一句waitkey(40)
,让程序停在这里等待外部键盘操作40ms,否则画面会卡死。
on_pushButton_closecamera_clicked():
停止的话则更好实现,目前来说只需关闭定时器和释放摄像头资源,如果需要的话,再清空显示区。
void MainWindow::on_pushButton_closecamera_clicked()
{
fps_timer.stop();
myCapture.release();
ui->label_videoViewer->clear();
}
如果使用了imshow()
函数,这里则要再加一句destroyWindow("VideoPlay");
关闭该窗口。
写到这里程序其实已经可以运行,在Debug模式下点击打开/关闭摄像头,即可成功实现画面的显示与停止。
on_pushButton_savevideo_clicked():
保存按钮首先判断摄像头是否打开,如果没有则直接返回;然后打开(新建)一个视频文件,在while循环里面写入视频帧。
void MainWindow::on_pushButton_savevideo_clicked()
{
if(!myCapture.isOpened())
{
qDebug()<<"Camera Is Not Open.";
return;
}
writer.open("D:\\test.avi",VideoWriter::fourcc('M', 'J', 'P', 'G'),25, Size(640, 480), true);
while (!complete_flag)
{
myCapture >> picture;
writer.write(picture);
namedWindow("VideoPlay", WINDOW_NORMAL);
imshow("VideoPlay", picture);
waitKey(40);
}
}
其中,write.open()
函数和第一篇文章加载图片的基本相同,不做赘述,制定了视频文件之后,通过write()
函数不断向里面写入Mat
类型的数据,即实现了保存。
在这里,我们在私有变量中设置了一个保存停止标志位,
bool complete_flag = false;
然后在on_pushButton_savecomplete_clicked()
中将该变量置位即可实现停止保存。
on_pushButton_savecomplete_clicked():
void MainWindow::on_pushButton_savecomplete_clicked()
{
complete_flag = true;
}
on_pushButton_videoreview_clicked():
视频回放与从摄像头中获取方法是一致的,只不过回放视频时是从视频文件中获取画面帧。
void MainWindow::on_pushButton_videoreview_clicked()
{
myCapture.open("D:\\test.avi");
while (myCapture.isOpened())
{
myCapture >> picture;
if(picture.empty())
break;
imshow("VideoPlay", picture);
if (waitKey(40) == 27) // ESC键的ASCII码为27,如果按下ESC键就推出
break;
}
destroyWindow("VideoPlay");
}
这里面不过把我们打开摄像头的代码myCapture.open(0)
换为了myCapture.open("D:\\test.avi");
接下来都一样,要注意的是当文件读完时,读回来的画面是空的,所以要加一句画面是否为空的判断,如果为空则表示文件读完,直接跳出。
写到这里,我们的基本功能都已经实现,但是这离实际应用还差好多,一方面是前后功能逻辑不太完善,另一方面将全部功能发到主线程中过多的占用了UI资源致使其他功能难以实现,如何进一步完善和开辟新的线程,我们下一篇文章再讲。
前面代码过于零散,重新沾一下便于查看:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
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();
private:
Ui::MainWindow *ui;
bool complete_flag = false;
VideoCapture myCapture;
VideoWriter writer;
Mat picture;
QTimer fps_timer;
};
#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);
fps_timer.setInterval(40);
connect(&fps_timer,SIGNAL(timeout()),this,SLOT(display_frame()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_opencamera_clicked()
{
myCapture.open(0);
if(!myCapture.open(0))
{
qDebug()<<"Camera Open Failed.";
return;
}
fps_timer.start();
}
void MainWindow::on_pushButton_closecamera_clicked()
{
fps_timer.stop();
myCapture.release();
ui->label_videoViewer->clear();
}
void MainWindow::on_pushButton_savevideo_clicked()
{
if(!myCapture.isOpened())
{
qDebug()<<"Camera Is Not Open.";
return;
}
writer.open("D:\\test.avi",VideoWriter::fourcc('M', 'J', 'P', 'G'),25, Size(640, 480), true);
while (!complete_flag)
{
myCapture >> picture;
writer.write(picture);
namedWindow("VideoPlay", WINDOW_NORMAL);
imshow("VideoPlay", picture);
waitKey(40);
}
}
void MainWindow::on_pushButton_savecomplete_clicked()
{
complete_flag = true;
}
void MainWindow::on_pushButton_videoreview_clicked()
{
myCapture.open("D:\\test.avi");
while (myCapture.isOpened())
{
myCapture >> picture;
if(picture.empty())
break;
imshow("VideoPlay", picture);
if (waitKey(40) == 27) // ESC键的ASCII码为27,如果按下ESC键就推出
break;
}
destroyWindow("VideoPlay");
}
void MainWindow::display_frame()
{
myCapture >> picture;
QImage img1 = QImage((const unsigned char*)picture.data, picture.cols, picture.rows, QImage::Format_RGB888).rgbSwapped();
ui->label_videoViewer->setPixmap(QPixmap::fromImage(img1));
namedWindow("VideoPlay", WINDOW_NORMAL);
imshow("VideoPlay", picture);
waitKey(40);
}