关于opencv加载视频以及保存视频,处理视频文件,要用到ffmpeg组件,ffmpeg组件有两个,一个是二进制版一个是源码版。
一. ffmpeg下载与环境配置
1. ffmpeg下载
首先进入ffmpeg官网。
之后点击windows图标之后
会进入一个github界面,点击展示更多。
然后选择下载,我这边选的是lpgl版而不是pgl。是共享版不是静态版
下面是静态版和共享版区别
-
ffmpeg-n7.0-latest-win64-lgpl-7.0.zip
(116 MB)- 说明:这是 FFmpeg 的静态版本。
- 内容:包含所有的 FFmpeg 可执行文件和静态链接的库文件。这意味着所有功能和库都被编译到了一个可执行文件中。使用这个版本时,所有依赖项都包含在同一个可执行文件中,独立运行,不依赖外部的动态链接库(DLL)。
- 适用场景:当你希望独立运行 FFmpeg,而不需要任何外部依赖时使用。这种版本通常体积较大,但不需要额外的 DLL 文件,使用更方便。
-
ffmpeg-n7.0-latest-win64-lgpl-shared-7.0.zip
(50.7 MB)- 说明:这是 FFmpeg 的共享版本。
- 内容:包含了 FFmpeg 的动态链接库(DLL 文件)和少量的可执行文件。这种版本更小巧,因为它通过动态链接来引用库文件,而不是将所有库文件静态编译到可执行文件中。
- 适用场景:当你希望将 FFmpeg 的功能整合到你的应用程序中,并希望以动态链接的方式进行集成时使用。动态链接库的好处是更新更灵活,你可以替换 DLL 文件而不需要重新编译整个应用程序。此版本更适合需要频繁更新或需要动态库管理的场景。
LGPL版 允许你在闭源或商业软件中动态链接使用 FFmpeg,而无需公开你的源代码,前提是你不修改 FFmpeg 的源代码。
GPL版 要求如果你将 FFmpeg 作为一部分集成到你的软件中,无论是静态链接还是动态链接,都必须公开你的软件源代码,并允许它在同样的 GPL 许可证下分发。
然后这边下载的是lgpl版而不是gpl版,lgpl版允许商业化闭源,所以就直接用lgpl版。
2. ffmpeg环境配置
点击进入高级系统设置
之后进入你解压好的文件夹下的bin目录下,复制bin目录的绝对路径,想必到了学opencv了,应该不会是不懂啥是绝对路径了吧,这里面的dll文件就是动态库,以后打包程序会用。
接下来按照我的步骤添加环境变量,把地址粘贴到5号框里。
之后控制台输入命令
ffmpeg -version
大功告成。
二. 视频加载与保存
先粘整个模块的代码,然后慢慢说。我这边是没有给他新添按钮,函数直接构造函数调用了,想加的话可以自己加,包括保存视频时候需要设置的参数,都可以自己写一个输入框来设置。
void MainWindow::setVideo(cv::Mat &image) {
QString inputFilePath = "C:\\Users\\wangy\\Desktop\\VID1.mp4";
QString tempOutputFilePath = "C:\\Users\\wangy\\Desktop\\temp_output.mp4";
QString finalOutputFilePath = "C:\\Users\\wangy\\Desktop\\VID2.mp4";
int bitrate = 3500; // 期望的比特率为 2500 kbps
// 打开原视频
cv::VideoCapture capture(inputFilePath.toStdString());
if (!capture.isOpened()) {
qDebug() << "Error: Unable to open video! Check the video format and path.";
return;
}
int frame_width = capture.get(cv::CAP_PROP_FRAME_WIDTH);
int frame_height = capture.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = capture.get(cv::CAP_PROP_FPS);
// 使用四字符编码器
int fourcc = static_cast<int>(capture.get(cv::CAP_PROP_FOURCC));
// 初始化 VideoWriter,保存临时文件
cv::VideoWriter writer;
writer.open(tempOutputFilePath.toStdString(),
fourcc,
fps,
cv::Size(frame_width, frame_height),
true);
if (!writer.isOpened()) {
qDebug() << "Error: Unable to open video writer!";
return;
}
// 使用 QTimer 进行定时更新
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=]() mutable {
cv::Mat frame;
capture >> frame; // 逐帧读取
if (frame.empty()) {
qDebug() << "End of video!";
timer->stop();
// 检查并删除现有的最终输出文件
if (QFile::exists(finalOutputFilePath)) {
QFile::remove(finalOutputFilePath);
qDebug() << "Existing final output file deleted.";
}
capture.release();
writer.release();
// 使用 FFmpeg 将临时文件转换为最终文件,并设置比特率
QProcess ffmpegProcess;
ffmpegProcess.start("ffmpeg", QStringList()
<< "-i" << tempOutputFilePath // 输入临时文件
<< "-c:v" << "libx264" // 强制使用 H.264 编码器
<< "-b:v" << QString::number(bitrate) + "k" // 设置比特率
<< "-bufsize" << QString::number(bitrate * 2) + "k" // 设置缓冲区大小
<< "-maxrate" << QString::number(bitrate) + "k" // 强制最大比特率
<< "-minrate" << QString::number(bitrate) + "k" // 强制最小比特率
<< finalOutputFilePath); // 输出最终文件
ffmpegProcess.waitForFinished();
qDebug() << "Video conversion complete with target bitrate!";
// 删除临时文件
QFile::remove(tempOutputFilePath);
qDebug() << "Temporary file deleted.";
return;
}
writer.write(frame); // 写入帧到临时文件
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
QImage qimg(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
QImage pixmap = setImageSize(qimg, ui->dstImage);
ui->dstImage->setPixmap(QPixmap::fromImage(pixmap));
});
// 设置帧率,假设 33fps
timer->start(33); // 每33ms触发一次
}
接下来进行模块分析。首先,来看路径的选择:第一个参数是原视频的存放路径,第二个是中间转换视频的存放路径,第三个则是输出视频的存放路径。需要注意的是,中间转换视频和输出视频的文件可以不存在,程序会根据你提供的命名和路径,在对应的位置自动创建这些文件。
QString inputFilePath = "C:\\Users\\wangy\\Desktop\\VID1.mp4";
QString tempOutputFilePath = "C:\\Users\\wangy\\Desktop\\temp_output.mp4";
QString finalOutputFilePath = "C:\\Users\\wangy\\Desktop\\VID2.mp4";
下面的部分
int frame_width = capture.get(cv::CAP_PROP_FRAME_WIDTH);
int frame_height = capture.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = capture.get(cv::CAP_PROP_FPS);
// 使用四字符编码器
int fourcc = static_cast<int>(capture.get(cv::CAP_PROP_FOURCC));
前三个是获取视频帧的宽度,高度和帧率。这些值来自视频捕获对象 capture,cv::CAP_PROP_FOURCC
用于获取 FourCC 编码。FourCC 是一个四字节的编码,表示视频的编码格式(压缩/解压缩器)。FourCC 编码代表视频压缩所使用的编解码器,如 'XVID'、'MJPG' 或 'H264'。
if (frame.empty())
如果视频帧已经读取完,删除保存路径下的内容,然后先输出中间视频,最后将中间视频按照想要的格式转换成最终视频,再删除中间视频。需要注意的是视频比特率,视频比特率的处理是存在误差的,根据处理方法,比特率的误差不同,如果需要更精确的比特率,需要更复杂的处理方式。例如两次编码,因为视频中的比特率是会变化的,所以在第一次编码时先对视频进行初步压缩,再根据目标比特率和第一次编码的结果进行调整,以保证最终视频的比特率尽可能接近设定值。如下代码:
// 第一阶段:分析视频并生成统计文件
QProcess ffmpegPass1;
ffmpegPass1.start("ffmpeg", QStringList()
<< "-y" // 强制覆盖
<< "-i" << tempOutputFilePath // 输入临时文件
<< "-c:v" << "libx264" // 使用 H.264 编码器
<< "-b:v" << QString::number(bitrate) + "k" // 目标比特率
<< "-pass" << "1" // 第一次分析
<< "-f" << "mp4" // 强制输出格式
<< "NUL"); // Windows 下不生成输出文件
ffmpegPass1.waitForFinished();
// 第二阶段:使用第一次的统计数据,进行实际编码
QProcess ffmpegPass2;
ffmpegPass2.start("ffmpeg", QStringList()
<< "-y" // 强制覆盖
<< "-i" << tempOutputFilePath // 输入临时文件
<< "-c:v" << "libx264" // 使用 H.264 编码器
<< "-b:v" << QString::number(bitrate) + "k" // 目标比特率
<< "-pass" << "2" // 第二次编码
<< finalOutputFilePath); // 输出最终文件
ffmpegPass2.waitForFinished();
这样就可以有效的降低比特率误差,但是也需要更长的视频处理时间,如果你想像pr一样要求视频输出更加准确,就需要更多处理步骤,比如进行两次或多次编码,调整关键帧间距,优化压缩算法,或者使用自适应比特率控制(ABR)和恒定质量(CQ)等高级编码设置。此外,可以引入分析工具对第一遍编码的结果进行详细评估,根据帧间的复杂性、运动场景和图像细节对比特率分布进行优化调整,从而在后续编码过程中进行更精准的调整。如果对比特率的精确控制要求非常高,还可以结合统计复查、帧级调整等方式,逐步细化编码过程,最终输出精度更高的视频。
capture.release() 和writer.release() 为什么要在输出前面?
- 在调用 FFmpeg 进行文件转换之前,临时文件应已完全写入并关闭,否则
ffmpeg
可能会读取不完整或锁定的文件,导致转换失败或产生错误输出。 - 通过提前
release()
确保临时文件处于关闭状态,ffmpeg
可以顺利进行处理,确保最终输出的文件完整且符合预期。