97 个 OpenCV 实例(十九):视频写入,VideoWriter 与 FourCC 编码

上一篇把「画面接进来」打通了------VideoCapture 三种方式读帧、采集循环、测帧率。这一篇把另一半补上:画面「写出去」。采集到的帧只是内存里的 Mat,要变成能回放、能归档、能交付的视频文件,靠的是 VideoWriter。官方 samples 里的 videowriter_basic 就干这一件事:把摄像头帧实时编码成 .avi 文件,同时窗口预览。麻雀虽小,五脏俱全,编码器选择、帧率、尺寸、写入循环一次讲透。

一、效果先行

先看结果。videowriter_basic 打开默认摄像头,一边在「Live」窗口预览,一边把帧写进 live.avi,按任意键结束:

退出后盘上多出一个 live.avi,用 ffprobe 一验,编码、尺寸、帧率全对上了:

复制代码
codec_name=mjpeg       # Motion JPEG 编码
width=768  height=576  # 尺寸跟摄像头一致
r_frame_rate=25/1      # 25 帧每秒
pix_fmt=yuvj420p

记住两件事:VideoWriter 一个类就能把帧编码成视频文件;fourcc 决定用什么编码器,fps 和尺寸要在写入前定死。

二、FourCC:四个字符选一个编码器

视频编码的入口是一个叫 fourcc 的东西------Four Character Code,四个 ASCII 字符唯一标识一种编码器。fourcc('M','J','P','G') 把 M、J、P、G 四个字符打包成一个整数,交给底层编码库选编码器。

FourCC 编码器 特点 容器
MJPG Motion JPEG 逐帧独立压缩,兼容性最好 .avi
XVID / DIVX MPEG-4 Part 2 有帧间压缩,体积小 .avi
X264 / H264 H.264 压缩率最高,工业主流 .mp4
MP4V MPEG-4 通用 .mp4

一个关键点:编码器必须运行时可用,否则 open 会失败。示例选 MJPG 就是因为它在几乎所有平台都内置,最不容易翻车。真要用 H264,得先确认系统装了对应编码库。

三、VideoWriter 的五个参数

初始化一个写入器,官方给的姿势是默认构造再 open,一共五个参数:

cpp 复制代码
VideoWriter writer;
int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  // 编码器 ID
double fps = 25.0;                                     // 目标帧率
string filename = "./live.avi";                        // 输出文件
writer.open(filename, codec, fps, src.size(), isColor);
if (!writer.isOpened()) {                              // 打开失败要查
    cerr << "Could not open the output video file for write\n";
    return -1;
}

五个参数分别是:文件名、编码器 ID、帧率、帧尺寸、是否彩色。前两个决定「存成什么样」,后三个决定「存的数据长什么样」。尺寸和色彩必须跟实际写入的帧严格一致,不一致编码就出问题。

四、先读一帧,再初始化写入器

尺寸和色彩这两个参数,摄像头没交出一帧之前谁也不知道。所以官方用了「先读一帧探测」的范式:

cpp 复制代码
cap >> src;                                   // ① 先抓一帧
if (src.empty()) return -1;
bool isColor = (src.type() == CV_8UC3);       // ② 从帧里问出类型
writer.open(filename, codec, fps, src.size(), isColor);  // ③ 用它初始化

src.size() 问出尺寸,src.type() == CV_8UC3 判断是不是 BGR 彩色。这套「探测 → 初始化 → 循环写入」的顺序,是所有采集类程序的固定套路------不先拿到一帧,你根本没法告诉写入器帧长什么样。

五、采集-写入循环

初始化完之后就是主循环,在上一篇的采集循环中间加一步 write:

cpp 复制代码
for (;;) {
    if (!cap.read(src)) break;   // 读一帧,读失败或流结束就退出
    writer.write(src);           // 把这一帧编码进视频流
    imshow("Live", src);         // 同时实时预览
    if (waitKey(5) >= 0) break;  // 任意键终止
}

注意 cap.read(src) 和上一篇的 cap >> src 是同一个动作,差别只在返回值------read 返回 bool,适合放进 if 里判断流是否结束。循环结束后没有显式 writer.release(),因为 VideoWriter 的析构函数会自动关闭文件、刷新缓冲,这是 RAII 的体现。

六、实测

这个示例硬编码 VideoCapture(0),VM 里没有物理摄像头。我用 v4l2loopback 造一个虚拟相机、再用 ffmpeg 把测试视频灌进去,成功跑通:

复制代码
Writing videofile: ./live.avi
Press any key to terminate

12 秒后终止,生成 8.9MB 的 live.avi。ffprobe 对账:mjpeg 编码、768×576、25fps、yuvj420p,和源码里 fourcc('M','J','P','G') 加 fps=25 加 src.size() 完全吻合。

但这一趟并不顺利。第一天搭虚拟相机时踩了个硬坑:Ubuntu 24.04 自带的 v4l2loopback 0.12.7 是 2021 年的老版本,在 kernel 7.0.0 上运行时直接内核崩溃------vidioc_reqbufs 里有个空指针访问,内核 OOPS 后把进程强制杀死,连第一行输出都打印不出来。换成 GitHub 最新的 0.15.4 版,一次编译通过、再无崩溃,采集和写入全链路跑通。

七、踩坑记录

坑 现象 正确姿势
编码器不可用 open 返回 false 用 MJPG 兜底,用 H264 先查可用性
把 fps 当节拍器 播放快进或慢放 fps 只是文件头元数据,节奏由采集循环定
尺寸类型不一致 编码失败或视频损坏 write 的帧和 open 声明严格对齐
容器编码器不配 打不开或异常 .avi 配 MJPG/XVID,.mp4 配 H264/MP4V
老版虚拟相机崩溃 进程被 SIGKILL 无输出 v4l2loopback 升到 0.15.4

八、AI 与 LLM Wiki:这一课沉淀了什么

这一课照例新增「视频写入」概念页,两条硬性要求一个不少------「API 汇总表」列了当天用到的 16 个接口(VideoWriter 的构造、fourcc、open、write、isOpened,加上配套的 VideoCapture 和 Mat 接口),功能、参数、用法三列逐条记清;「demo 语法分析」把 67 行源码拆成五块讲透,从头文件到主循环,重点拆了 fourcc 的静态成员调用、五参数 open、先读帧再初始化的范式。

更值钱的是把那个 v4l2loopback 0.12.7 崩溃的坑固化进了技能。AI 学东西不是记在脑子里就算完,而是把「现象 → 根因 → 解法」写进可复用的技能条目,下次再遇到无摄像头环境跑采集代码,直接照着升级到 0.15.4,不用再从头踩一遍。

进度表上 videowriter_basic 勾选完成,从 38 个变成 39 个,进度 40%。第三阶段「图像读写与视频 I/O」已经走过图片、采集、写入三站。

写在最后

97 个实例,今天完成第 39 个,进度 40%。这一站把「写出去」补上了:VideoWriter 一个类管编码,fourcc 选编码器,五个参数定输出,先读帧再初始化,循环里 write 就完事。下一篇往视频的边角料走------videocapture_image_sequence 把图像序列合成视频,videocapture_audio 讲音视频同步,看看视频文件里除了画面还有什么。

本文示例代码均出自 OpenCV 官方 samples,遵循 Apache 2.0 协议。

相关推荐
深蓝AI6 小时前
无头浏览器不必从 Chrome 改:Lightpanda 用 Zig 从零造,100 个页面只占 123MB 内存
人工智能·程序员
奈落248 小时前
AI辅助的另一面:从2026年国际AI安全报告的“故障”清单到国内专家的可靠性短板,自主行动的Agent为什么更难及时干预
人工智能·安全·agent·本地部署·ai 办公
火山引擎开发者社区10 小时前
eMudhra 证书失陷:容器用户最容易忽略的镜像信任链漏洞
人工智能
LabVIEW开发11 小时前
LabVIEW 机载 DIAL:每秒万次甲烷测量
人工智能·labview·labview知识·labview功能·labview程序
YWamy11 小时前
如何降低RTC SDK在复杂网络环境下的运行成本
网络·音视频·实时音视频
飞猫的边缘AI12 小时前
英特尔量子生意
人工智能·量子计算·边缘ai
深蓝AI12 小时前
桌面 Agent 缺的不是视觉:Vercel 开源 agent-desktop,用无障碍树 ref 替代截图坐标
人工智能·程序员
镜子AI12 小时前
镜子AI 技术复盘:三条自动化链路上的“停止条件“设计
运维·人工智能·自动化
向哆哆12 小时前
羊行为目标检测数据集:4类别、2,000张图像 | 目标检测
人工智能·yolo·目标检测·目标跟踪
如若12313 小时前
【论文复现】CoFailMed:从零复现医学图像分割的共享失败审计,含完整代码流程与科研绘图
人工智能·python