Qt QML实现视频帧提取

前言

视频帧率(Frame Rate)是指视频播放时每秒显示的画面帧数,通常用fps(Frames Per Second)来表示。视频是由一系列静止的图像帧组成的,而视频帧率则决定了这些图像帧在单位时间内播放的速度。较高的视频帧率可以提供更流畅的视频画面,而较低的视频帧率则可能导致画面卡顿和不连贯的情况

在实际的应用开发中,经常会遇到需要处理视频的情况,例如提取视频帧用于图像处理、分析等应用。本文将介绍如何利用Qt QML实现视频帧的提取,通过简单的代码示例将图片提取保存到本地中。

效果

先看运行效果:

正文

本示例通过QML实现UI,Qt5.15 cmake编译,使用多线程处理提取,保证UI主线程不会卡顿,将提取的图片保存到本地。

提取部分,关键代码:

复制代码
void FrameExtractor::stopProcessing()
{
    qDebug() << "停止帧提取处理";
    m_running.store(0);
    
    // 清空队列,避免处理不必要的帧
    QMutexLocker locker(&m_mutex);
    if (!m_frameQueue.isEmpty()) {
        qDebug() << "清空帧队列,当前队列长度: " << m_frameQueue.size();
        m_frameQueue.clear();
    }
    
    // 唤醒等待中的线程
    m_condition.wakeOne();
}

void FrameExtractor::processFrames()
{
    qDebug() << "开始处理视频帧";
    
    while (m_running.load()) {
        QVideoFrame frame;
        
        // 获取下一帧
        {
            QMutexLocker locker(&m_mutex);
            
            // 如果队列为空且没有更多帧,则结束处理
            if (m_frameQueue.isEmpty() && m_noMoreFrames.load()) {
                qDebug() << "队列为空且没有更多帧,结束处理";
                break;
            }
            
            // 如果队列为空,等待新帧
            if (m_frameQueue.isEmpty()) {
                qDebug() << "队列为空,等待新帧...";
                m_condition.wait(&m_mutex);
                qDebug() << "等待结束,继续处理";
                continue;
            }
            
            frame = m_frameQueue.dequeue();
            qDebug() << "从队列中获取一帧,当前队列长度: " << m_frameQueue.size();
        }
        
        // 处理帧
        if (frame.isValid()) {
            QVideoFrame cloneFrame(frame);
            if (cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) {
                // 将视频帧转换为QImage
                QImage image;
                
                switch (cloneFrame.pixelFormat()) {
                case QVideoFrame::Format_RGB32:
                case QVideoFrame::Format_ARGB32:
                case QVideoFrame::Format_ARGB32_Premultiplied:
                    image = QImage(cloneFrame.bits(),
                                  cloneFrame.width(),
                                  cloneFrame.height(),
                                  cloneFrame.bytesPerLine(),
                                  QImage::Format_RGB32);
                    break;
                case QVideoFrame::Format_RGB24:
                    image = QImage(cloneFrame.bits(),
                                  cloneFrame.width(),
                                  cloneFrame.height(),
                                  cloneFrame.bytesPerLine(),
                                  QImage::Format_RGB888);
                    break;
                case QVideoFrame::Format_YUYV:
                case QVideoFrame::Format_UYVY:
                case QVideoFrame::Format_YUV420P:
                case QVideoFrame::Format_YV12:
                case QVideoFrame::Format_NV12:
                case QVideoFrame::Format_NV21:
                {
                    // 对于YUV格式,需要进行颜色空间转换
                    // 这里简化处理,将其转换为灰度图像
                    qDebug() << "处理YUV格式视频帧: " << cloneFrame.pixelFormat() 
                             << "宽度: " << cloneFrame.width() 
                             << "高度: " << cloneFrame.height();
                    
                    // 安全地创建灰度图像
                    image = QImage(cloneFrame.width(), cloneFrame.height(), QImage::Format_Grayscale8);
                    
                    // 只处理Y平面数据,避免访问UV平面导致的问题
                    const uchar *bits = cloneFrame.bits();
                    int bytesPerLine = cloneFrame.bytesPerLine();
                    
                    // 限制处理范围,避免越界访问
                    int maxHeight = qMin(cloneFrame.height(), image.height());
                    int maxWidth = qMin(cloneFrame.width(), image.width());
                    
                    try {
                        for (int y = 0; y < maxHeight; ++y) {
                            for (int x = 0; x < maxWidth; ++x) {
                                // 只取Y分量作为灰度值
                                uchar value = bits[y * bytesPerLine + x];
                                image.setPixelColor(x, y, QColor(value, value, value));
                            }
                        }
                        qDebug() << "YUV帧处理完成";
                    } catch (const std::exception &e) {
                        qDebug() << "处理YUV帧时发生异常: " << e.what();
                        // 如果发生异常,创建一个空白图像
                        image = QImage(cloneFrame.width(), cloneFrame.height(), QImage::Format_Grayscale8);
                        image.fill(Qt::black);
                    }
                    break;
                }
                default:
                    // 对于其他格式,尝试转换为RGB32
                    image = QImage(cloneFrame.bits(),
                                  cloneFrame.width(),
                                  cloneFrame.height(),
                                  cloneFrame.bytesPerLine(),
                                  QImage::Format_RGB32).copy();
                    break;
                }
                
                // 保存图像
                if (!image.isNull()) {
                    QString fileName = QString("%1/frame_%2.jpg")
                            .arg(m_outputDir)
                            .arg(m_extractedCount, 6, 10, QChar('0'));
                    
                    qDebug() << "正在保存图像到: " << fileName;
                    
                    if (image.save(fileName, "JPG")) {
                        m_extractedCount++;
                        qDebug() << "图像保存成功,已提取: " << m_extractedCount << "/" << m_frameCount;
                        emit progressUpdated(m_extractedCount, m_frameCount);
                    } else {
                        qDebug() << "图像保存失败: " << fileName;
                    }
                } else {
                    qDebug() << "无法保存空图像,跳过当前帧";
                }
                
                cloneFrame.unmap();
            }
        }
    }
    
    // 处理完成
    emit finished();
}

本文代码下载

相关推荐
写一本我们家的书10 分钟前
Qt MainWindow简单例子(文本编辑)
开发语言·qt
JCBP_38 分钟前
C语言经典代码题
c语言·开发语言·汇编·vscode·算法
萧萧玉树43 分钟前
设计模式-单一职责
开发语言·c++·设计模式
java1234_小锋44 分钟前
一周学会Flask3 Python Web开发-SQLAlchemy添加数据操作-班级模块
开发语言·前端·python·flask·flask3
forestsea1 小时前
Java 8 Stream API:传统实现和流式编程的范式对比
java·开发语言
多多*1 小时前
Spring设计模式 八股速记 高层模块底层模块 依赖倒置原则 开闭原则 接口隔离原则
java·开发语言·数据库·macos·github·objective-c·cocoa
小林熬夜学编程1 小时前
【高并发内存池】第一弹---深入解析内存池:项目介绍、原理及设计定长内存池全攻略
linux·服务器·c语言·开发语言·c++·算法
郭涤生1 小时前
Chapter 2:auto_《Effective Modern C++》notes
开发语言·c++·笔记
沙滩捡贝壳的小男孩1 小时前
告别硬编码:优雅管理状态常量与响应码
java·开发语言
前端wchen2 小时前
在 macOS 上轻松部署 Docker:详细安装与配置步骤
开发语言·网络·程序人生·macos·docker·mac·工具