## 前言
视频帧率(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();
}
-----------------
本文代码下载