OpenCV计算机视觉开发实践:基于Qt C++ - 商品搜索 - 京东
播放器的基本功能包括播放、暂停、快进、后退、重新播放、停止、拖动进度条等。通过OpenCV实现视频播放器,其思路大致就是在线程中使用OpenCV的VideoCapture循环读取本地视频的每一帧Mat,然后将其发送到界面转换成QImage进行显示,而进度条拖动则用到了VideoCapture中的set函数,进度条则使用QSlider,并且通过自定义新的进度条类实现单击跳转功能。
由于本书不是专门讲述Qt编程的书,因此一些Qt界面设计和线程编程的基础知识这里就不展开讲解了。
【例12.5】实现一个视频播放器
新建一个Qt Widgets应用工程,工程名是viPlayer。在“Class Information”对话框上设置“Base Class”为QWidget,其他保持默认。
设计界面。在Qt Creator中双击打开widget.ui,从控件盒子中拖放6个Tool Button、3个Horizontal Spacer以及1个Horizontal Slider到窗口上,并设置每个控件的objectName以及布局等,最终设计界面如图12-8所示。
图12-8
这些按钮的图片都在项目文件夹的img目录下,可以作为资源添加到IDE中。
实现滑块功能。在项目中添加newqslider.h和newqslider.cpp。这里需要对horizontalSlider的单击事件函数进行重写,实现单击进度条后获取单击处的进度值并更新进度条的功能。
#include "newqslider.h"
newqslider::newqslider(QWidget *parent) : QSlider(parent)
{
}
/*****************************************************************
* 函数名称:mousePressEvent(QMouseEvent *ev)
* 功能描述:重写鼠标单击事件,实现进度条单击哪儿就跳到哪儿
* 参数说明: 无
* 返回值: 无
******************************************************************/
void newqslider::mousePressEvent(QMouseEvent *ev)
{
// 先调用父类的鼠标单击处理事件,这样可以不影响拖动的情况
QSlider::mousePressEvent(ev);
// 获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始单击的位置就没有意义了)
double pos = ev->pos().x() / (double)width();
setValue(pos * (maximum() - minimum()) + minimum());
// 发送自定义的鼠标单击信号
emit costomSliderClicked();
}
实现OpenCV采集线程。在项目中添加videothread.h,代码如下:
#include <QObject>
#include <QThread>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <QDebug>
#include <QDateTime>
using namespace std;
using namespace cv;
class videothread : public QThread
{
Q_OBJECT
public:
videothread(const char* filename);
void run();
// 释放视频采集对象
void releaseCap();
// 获取视频总帧数
int getVideoAllFramecount();
// 设置当前进度条
void setCurrentFrame(int value);
bool getStop() const;
// 设置视频结束标识
void setStop(bool value);
bool getIsrun() const;
void setIsrun(bool value);
// 暂停
void pauseThread();
// 继续
void resumeThread();
// 停止
void stopThread();
signals:
// 发送当前帧和帧数
void sendFrame(int currentFrame,Mat frame);
private:
// 视频对象
VideoCapture cap;
Mat frame;
// 视频当前帧数
int currentFramecount;
// 总帧数
int allFramecount;
// 视频帧率
int fps;
// 录制视频帧
int videoWriterFrame;
// 线程结束标识位
bool stop;
// 视频暂停标识位
bool isrun;
};
在项目中添加videothread.cpp,代码如下:
#include "videothread.h"
videothread::videothread(const char* filename)
{
this->stop = false;
this->isrun =false;
this->currentFramecount=0;
this->videoWriterFrame=0;
if(cap.open(filename));// 创建视频对象
{
this->allFramecount=cap.get(CAP_PROP_FRAME_COUNT);// 获取视频文件中的总帧数
this->fps=int(round(cap.get(CAP_PROP_FPS)));// 获取视频帧率
}
}
void videothread::run()
{
while(stop==false)// 线程运行和停止,卡住线程,暂停时不退出线程
{
while(isrun==true)// 视频运行和暂停
{
if(cap.read(frame))// 捕获视频帧
{
this->currentFramecount++;
cvtColor(frame, frame, COLOR_BGR2RGB);// 将opencvBGR格式转换成Image用到的RGB
emit sendFrame(currentFramecount,frame);// 发送帧数据
}
msleep(40);// 延时
}
}
cap.release();// 释放打开的视频
}
int videothread::getVideoAllFramecount()
{
return allFramecount;
}
void videothread::setStop(bool value)
{
stop = value;
}
void videothread::setCurrentFrame(int value)
{
this->currentFramecount=value;// 当前帧数
cap.set(CAP_PROP_POS_FRAMES,currentFramecount);// 进度条跳转对应帧
}
bool videothread::getIsrun() const
{
return isrun;
}
void videothread::setIsrun(bool value)
{
isrun = value;
}
void videothread::pauseThread()// 这两个函数用于确保在运行情况下才能切换状态
{
if(this->isRunning()&&this->isrun==true)// 当前线程运行且视频运行
{
this->isrun=false;
}
}
void videothread::resumeThread()
{
if(this->isRunning()&&this->isrun==false)// 当前线程运行且视频暂停
{
this->isrun=true;
}
}
void videothread::stopThread()
{
if(this->isRunning())// 当前线程运行
{
this->stop=true;// 结束线程
// msleep(10);
releaseCap();
this->terminate();
}
}
bool videothread::getStop() const
{
return stop;
}
void videothread::releaseCap()
{
if(cap.isOpened()){
cap.release();
}
}
采集线程中设置了暂停、继续播放、停止功能,并可以获取帧率和帧数量。这个线程实现了核心功能。界面上的按钮事件处理函数和滑块事件处理函数都和线程有关,比如重播按钮的事件处理函数代码如下:
void Widget::on_btn_replay_clicked()
{
pthread->stopThread(); // 结束线程
qDebug()<<"重新播放";
if(!pthread->isRunning()) // 线程没有运行
{
if(isend!=true) // 此时线程结束并已释放,就不再释放
{
// 断开连接
disconnect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)));// 接收每一帧Mat
delete pthread;
pthread = nullptr;
}
// 创建新线程
QByteArray ba = videoFilePath.toLocal8Bit();
char* ch = ba.data();
pthread=new videothread(ch);
pthread->start();
pthread->setIsrun(true);// 视频开始
connect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)));
// 接收每一帧Mat
isend=false;// 表明此时线程还未结束
ui->btn_startPlay->setIcon(QIcon(":/img/24gf-pause.png"));
ui->btn_backward->setEnabled(true);
ui->btn_forward->setEnabled(true);
}
}
限于篇幅,其他控件的事件处理函数不在这里给出,具体可以查看本书配套资源中的源码工程中的widget.cpp。
运行程序,/root/下的hd.mp4就可以播放了,按暂停键后的界面如图12-9所示。
图12-9
此视频播放器只涉及OpenCV加载视频和对视频帧的处理,以及进度条管理等部分功能。在使用中需要注意线程指针的创建和释放以及OpenCV采集对象的新建和释放,否则频繁停止和加载会出现野指针和内存泄漏的bug。