OpenCV计算机视觉开发入门与实践<三十五>:开发视频播放器

摘要 :本文基于 Qt 与 OpenCV 实现了一个功能完整的视频播放器,支持播放、暂停、快进、后退、重新播放、停止以及进度条拖动与单击跳转等操作。文章首先介绍播放器的整体实现思路------在子线程中通过 VideoCapture 循环读取视频帧并转换为 QImage 显示;随后给出完整的源码实现,涵盖 main.cpp 入口、Widget 主界面逻辑、自定义 newqslider 进度条类(支持点击跳转)以及 videoThread 视频处理线程类,并附有详细代码注释,便于读者直接参考与复用。

1.实现视频播放器

  播放器的基本功能包括播放、 暂停、 快进、 后退、 重新播放、 停止、 拖动进度条等。通过OpenCV实现视频播放器, 其思路大致就是在线程中使用OpenCVVideoCapture循环读取本地视频的每一帧Mat, 然后将其发送到界面转换成QImage进行显示; 而进度条拖动则用到了VideoCapture中的set函数; 进度条则使用QSlider, 并且通过自定义新的进度条类实现单击跳转功能。

2.源码实现

2.1 main.cpp
cpp 复制代码
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;    
    w.show();
    return a.exec();
}
2.2 Widget 类
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QSlider>
#include "videothread.h"
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void receiveFrame(int currentFrame,Mat frame);
private slots:

    void sliderClickedSlot();//点击进度条信号槽

    void on_horizontalSlider_sliderReleased();

    void on_horizontalSlider_sliderMoved(int position);

    void on_btn_startPlay_clicked();

    void on_btn_backward_clicked();

    void on_btn_forward_clicked();

    void on_btn_stopPlay_clicked();

    void on_btn_replay_clicked();

    void on_btn_openFile_clicked();

private:
    Ui::Widget *ui;
    videothread *pthread = nullptr;
    //接收到视频帧数
    int receiveCurrentFramecount;
    //当前显示图片
    QImage img1;
    bool isend;
    //加载视频文件路径
    QString videoFilePath = "";
};

#endif // WIDGET_H
cpp 复制代码
#pragma execution_character_set("utf-8")
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{

    ui->setupUi(this);
    setWindowTitle("视频播放器");
    setWindowIcon(QIcon(":/img/video_icon.png"));
    receiveCurrentFramecount=0;
    isend=false;
    connect(ui->horizontalSlider,SIGNAL(costomSliderClicked()),this,SLOT(sliderClickedSlot()));//点击进度条信号槽
    ui->horizontalSlider->setSingleStep(1);//设置单步长为1
    qRegisterMetaType<Mat>("Mat");

    ui->btn_backward->setEnabled(false);
    ui->btn_forward->setEnabled(false);
    ui->btn_replay->setEnabled(false);
    ui->btn_stopPlay->setEnabled(false);
    ui->horizontalSlider->setEnabled(false);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::receiveFrame(int currentFrame, Mat frame)
{
    receiveCurrentFramecount=currentFrame;
    img1 = QImage(frame.data,frame.cols,frame.rows,QImage::Format_RGB888);
    img1 = img1.scaled(ui->label->width(),ui->label->height());
    ui->label->setPixmap(QPixmap::fromImage(img1));

    ui->horizontalSlider->setValue(receiveCurrentFramecount);//设置当前进度条取值
    ui->label_2->setText(QString::number(receiveCurrentFramecount));//设置当前帧数
    update();
}

void Widget::sliderClickedSlot()//点击进度条 信号槽
{
    //自定义鼠标点击信号,可以实现点哪 跳转到哪
    qDebug()<<"点击了进度条";
    pthread->pauseThread();//播放暂停
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
}

void Widget::on_horizontalSlider_sliderReleased()//释放进度条
{
    //ui->horizontalSlider->value();获取当前进度条值
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
    //此处一定要先设置进度条再开启线程,否则线程开启时再设置会冲突
}

void Widget::on_horizontalSlider_sliderMoved(int position)//进度条拖动
{
    pthread->pauseThread();//播放暂停
}

void Widget::on_btn_startPlay_clicked()
{
    if(videoFilePath == "")
    {
        QMessageBox::warning(this,"警告","请选择要加载的视频文件!");
        return;
    }
    if(pthread == nullptr)
    {
        QByteArray ba = videoFilePath.toLocal8Bit();
        char* ch = ba.data();
        pthread=new videothread(ch);
        //第五个参数采用Qt::BlockingQueuedConnection   信号发出后发送线程阻塞,直到槽函数执行完毕,继续发送下一条信号
        connect(pthread,SIGNAL(sendFrame(int,Mat)),this,SLOT(receiveFrame(int,Mat)));//接收每一帧Mat
        //connect(pthread,SIGNAL(endPlayVideo()),this,SLOT(setPlayEndSlot()),Qt::BlockingQueuedConnection);
        ui->horizontalSlider->setRange(0,pthread->getVideoAllFramecount());//设置进度条取值范围
        ui->label_4->setText(QString::number(pthread->getVideoAllFramecount()));//设置总帧数
    }
    if(pthread->isRunning())
    {
        if(pthread->getIsrun()==false)
        {
            pthread->resumeThread();//修改为播放状态
            ui->btn_startPlay->setIcon(QIcon(":/img/24gf-pause.png"));
        }
        else
        {
            pthread->pauseThread();;//视频暂停
            ui->btn_startPlay->setIcon(QIcon(":/img/24gf-play.png"));
        }
    }
    else
    {
        pthread->start();
        pthread->setIsrun(true);//视频开始
        ui->btn_startPlay->setIcon(QIcon(":/img/24gf-pause.png"));

        ui->btn_backward->setEnabled(true);
        ui->btn_forward->setEnabled(true);
        ui->btn_replay->setEnabled(true);
        ui->btn_stopPlay->setEnabled(true);
    }
}


void Widget::on_btn_backward_clicked()
{
    pthread->pauseThread();//播放暂停
    int curFrameIndex = ui->horizontalSlider->value();
    if(curFrameIndex < 100)
    {
        ui->horizontalSlider->setValue(0);
    }
    else
    {
        ui->btn_forward->setEnabled(true);
        ui->horizontalSlider->setValue(curFrameIndex-100);
    }
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
    ui->btn_startPlay->setIcon(QIcon(":/img/24gf-pause.png"));
}


void Widget::on_btn_forward_clicked()
{
    pthread->pauseThread();//播放暂停
    int curFrameIndex = ui->horizontalSlider->value();
    if(curFrameIndex > (pthread->getVideoAllFramecount() -100))
    {
        ui->btn_forward->setEnabled(false);
        ui->horizontalSlider->setValue(pthread->getVideoAllFramecount()-1);
    }
    else
    {
        ui->btn_backward->setEnabled(true);
        ui->horizontalSlider->setValue(curFrameIndex+100);
    }
    pthread->setCurrentFrame(ui->horizontalSlider->value());
    pthread->resumeThread();//播放继续 设置为true
    ui->btn_startPlay->setIcon(QIcon(":/img/24gf-pause.png"));
}


void Widget::on_btn_stopPlay_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;
        }
    }
    ui->btn_startPlay->setIcon(QIcon(":/img/24gf-play.png"));
    ui->btn_backward->setEnabled(false);
    ui->btn_forward->setEnabled(false);
    ui->btn_replay->setEnabled(false);
    ui->btn_stopPlay->setEnabled(false);
    ui->label_2->setText("0");
    ui->label_4->setText("0");
    ui->label->setText("视频播放器");
    ui->widget_FileOpen->show();
    videoFilePath = "";
    ui->horizontalSlider->setValue(0);
    ui->horizontalSlider->setEnabled(false);
}


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);
    }

}


void Widget::on_btn_openFile_clicked()
{
    QSettings setting("./Setting.ini", QSettings::IniFormat);          //为了能记住上次打开的路径
    QString lastPath = setting.value("LastFilePath").toString();
    QString FilePath = QFileDialog::getOpenFileName(this,"加载视频文件",lastPath,QObject::tr("所有文件(*.mp4)"));
    if(FilePath.isEmpty())
    {
        QMessageBox::warning(this,"警告","未选择文件,请重新选择!");
        return;
    }
    videoFilePath = FilePath;
    ui->widget_FileOpen->hide();
    ui->horizontalSlider->setEnabled(true);
    on_btn_startPlay_clicked();
}
2.3 newQslider类
cpp 复制代码
#ifndef NEWQSLIDER_H
#define NEWQSLIDER_H

#include <QWidget>
#include <QSlider>
#include <QMouseEvent>
class newqslider : public QSlider
{
    Q_OBJECT
public:
    explicit newqslider(QWidget *parent = 0);
    void mousePressEvent(QMouseEvent *ev);
signals:
    void costomSliderClicked();//自定义的鼠标单击信号,用于捕获并处理
public slots:
};

#endif // NEWQSLIDER_H
cpp 复制代码
#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();
}
2.4 videoThread类
cpp 复制代码
#ifndef VIDEOTHREAD_H
#define 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;

};

#endif // VIDEOTHREAD_H
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();
    }
}
相关推荐
leoZ2311 小时前
2026-09-08-mysql-57-init-walkthrough
前端·javascript·数据库·vue.js·opencv·mysql·adb
hansang_IR2 小时前
【题解】P9753 [CSP-S 2023] 消消乐
数据结构·c++·算法
竞赛考级题库2 小时前
202606 青少年等级考试C/C++真题一级 建议答题时长:60min
java·c语言·c++
大熊背2 小时前
冯‑克里斯映射估计算法逻辑
深度学习·机器学习·计算机视觉·白平衡·色调映射·冯-克里斯映射估计
linx2952 小时前
阶段五讲义:STL 全貌——扔掉手写循环,学“动词“
c++
2301_768103492 小时前
AI视频创作Agent实战05:Wan场景融合与VideoRetalk口型驱动
人工智能·音视频
差不多的周周2 小时前
《MoChat:面向多转弯动作理解和描述的关节分组时空接地多通道大语言模型》论文核心部分详解
人工智能·深度学习·机器学习·计算机视觉·语言模型·自然语言处理
handler012 小时前
【Linux】动态库、静态库使用与原理
linux·运维·服务器·c++·c·动态库·静态库
raindayinrain2 小时前
c++并发--同步
c++·同步