这是第三阶段「图像读写与视频 I/O」的收官,也是本阶段最大的官方示例------videocapture_gstreamer_pipeline,380 行。前面几课 VideoCapture 都是「传个文件路径让它自己开」,这一课要亲手拼 GStreamer 管道字符串,把「读文件、解封装、解码、转格式」每一步都拆开自己指定,还顺带测了不同后端的编解码性能。工业上 RTSP/RTMP 拉流、自定义采集管线,靠的就是这套管道拼装。
一、效果先行
先看实测。用 FFmpeg 后端解码测试视频,程序自己测出平均帧率:
Mode: decode, Backend: ffmpeg
795 frames in 1.28 sec ~ 621.6 FPS
795 帧只用 1.28 秒解完,每秒 620 多帧,远超实时播放(25 帧)的需求。再换一个文件对比,270 帧 0.43 秒,629 FPS,结果稳定在 620+。这就是「解码性能测量」------同一份代码,换文件、换后端、换编码器,就能摸清你的机器到底能跑多快。
二、GStreamer 管道:用 ! 串起来的一条流水线
GStreamer 的核心是一个个处理单元(element),用感叹号串成流水线。OpenCV 的 VideoCapture 和 VideoWriter 能直接接收这条管道字符串:
filesrc location="vtest.avi" ! avidemux ! decodebin ! videoconvert ! appsink
从左到右:filesrc 读文件、avidemux 解封装(把 avi 拆成音视频流)、decodebin 解码、videoconvert 转格式、appsink 把帧交给 OpenCV。写入方向反过来:
appsrc ! videoconvert ! jpegenc ! avimux ! filesink location="out.avi"
appsrc 接 OpenCV 的帧、jpegenc 编码、avimux 封装、filesink 落盘。看懂了这两条,就理解了「自定义采集管道」的全部------每一步都能换元素、加参数。

三、六种后端,一个后端字符串分发
官方示例支持 6 种后端,靠命令行参数切换:
| 后端 | 说明 |
|---|---|
| gst-default | OpenCV 默认 GStreamer 管道 |
| gst-basic | 手动拼软件管道 |
| gst-vaapi | Intel VAAPI 硬解硬编 |
| gst-libav | libav 解码器 |
| gst-mfx | Intel Media SDK |
| ffmpeg | FFmpeg 后端 |
代码里用工厂函数按后端字符串分发,返回不同构造的采集器。调用方只关心「给我一个能用的 VideoCapture」,不关心底层是哪个后端。这套工厂模式是处理「多后端可选」的经典写法。
四、decode 与 encode 两种模式
这个示例有 decode(解码测速)和 encode(编码测速)两种模式。decode 读视频文件测解码 FPS;encode 用 videotestsrc 合成测试图案源,再编码写文件测编码 FPS。encode 的合成源长这样:
videotestsrc pattern=smpte ! video/x-raw,width=1280,height=720,framerate=30/1 ! appsink
videotestsrc 是 GStreamer 自带的测试图案源,smpte 是彩条图,专门用来测性能------不依赖任何输入文件,想压多少帧压多少帧。
五、语法精华:模板、查表、计时
这个示例是 C++ 语法的大杂烩,几处值得细看。安全查 map 的模板函数:
cpp
template<typename M>
inline typename M::mapped_type getValue(const M &dict, const typename M::key_type &key,
const string &errorMessage) {
typename M::const_iterator it = dict.find(key);
if (it == dict.end()) CV_Error(Error::StsBadArg, errorMessage);
return it->second;
}
template<typename M> 泛化到任意 map,M::mapped_type 是值类型,M::key_type 是键类型,找不到就抛异常。还有一堆 inline 查表函数,把「字符串 → 具体配置」集中管理------分辨率表、fourcc 表、解码元素表、封装元素表,配置表驱动。
计时用 TickMeter 而不是手写 getTickCount:
cpp
TickMeter tick;
tick.start();
cap->grab(); cap->retrieve(frame);
tick.stop();
// 最后:tick.getCounter() / tick.getTimeSec() = 平均 FPS
TickMeter 把起停、计数、累计秒数都封装好了,比手写 tick 计数清爽得多。
六、实测
本机只跑通了 FFmpeg 后端:decode 模式 vtest.avi 621.6 FPS、Megamind.avi 629.3 FPS。GStreamer 后端全军覆没------原因很有意思:我本机的 OpenCV 编译时虽然开了 WITH_GSTREAMER,但当时系统缺 GStreamer 的开发头文件,导致 GStreamer 后端根本没编译进去(检查 libopencv_videoio 的链接,只有 FFmpeg 库,没有 GStreamer 库)。
encode 模式也跑不了,因为合成源固定用 GStreamer 的 videotestsrc,同样依赖 GStreamer 后端。所以要真正跑通 gst 那几路,得先补装 gstreamer 开发包再重编 OpenCV。这也正好说明:一个功能「开关开着」不等于「真的编译进去了」,得看最终链接。
七、踩坑记录
| 坑 | 现象 | 正确姿势 |
|---|---|---|
| 开关开了却没编进去 | gst 后端全 not opened | 看 ldd 有没有 gstreamer 库,别只看 CMake 开关 |
| 测试视频编码没查 | GStreamer 解不出 | vtest.avi 是 DivX MPEG-4,装 gstreamer1.0-libav |
| 短名参数解析错 | -m decode 变 true | 用 --mode=decode 长名最稳 |
| pipeline 引号漏转义 | 管道解析失败 | C++ 里 location="..." 要转义 |
| encode 依赖合成源 | encode 跑不了 | 合成源用 videotestsrc,依赖 GStreamer |
八、AI 与 LLM Wiki:这一课沉淀了什么
这一课新增「GStreamer 管道」概念页,两条硬要求一个不少------API 汇总表列了 Ptr/makePtr、TickMeter、CommandLineParser 完整用法、getNumThreads、findFile 等二十多个接口;demo 语法分析把 380 行源码拆成五块,模板函数 getValue、查表函数、工厂函数、pipeline 拼装、TickMeter 计时逐个讲透。

更值钱的是把「开关开了但没编译进去」这个坑固化下来------用 ldd 验证后端是否真的链接,而不是信 CMake 的开关。这种「配置 ≠ 生效」的坑,AI 沉淀一次,以后每次环境排查都能少走弯路。
进度表勾选完成,从 43 个变成 44 个,进度 45%。第三阶段「图像读写与视频 I/O」16 个实例至此全部走完。
写在最后
97 个实例,今天完成第 44 个,进度 45%。第三阶段收官:图片读写、视频采集、视频写入、图像序列、音频、GStreamer 管道,六站走完,VideoCapture 和 VideoWriter 从「能用」到「会定制」。下一篇开启第四阶段「特征检测与匹配」,从 lkdemo 的 Lucas-Kanade 光流讲起,进入真正的视觉算法核心。
本文示例代码均出自 OpenCV 官方 samples,遵循 Apache 2.0 协议。