OpenCV实现一个视频播放器

发布于:2025-05-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

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。


网站公告

今日签到

点亮在社区的每一天
去签到