OpenCV实现一个视频播放器
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用到的RGBemit 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)));// 接收每一帧Matdelete 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)));
// 接收每一帧Matisend=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。