~/blog/filtergraph-guide
滤镜2026-07-28 · 8 min

滤镜图 filter_complex 入门:从 scale 到多路合成

一条命令同时缩放、加水印、拼画面。用图解的方式讲清滤镜链与滤镜图的区别,看完就能写出自己的复合命令。

一条 FFmpeg 命令同时完成缩放、加水印、拼画面——这不是三个命令的拼接,而是滤镜图(filter graph)在起作用。filter_complex 是 FFmpeg 最强大也最劝退的部分:方括号、垫、链,符号一堆。这篇用图解的方式把它拆开,看完你就能写出自己的复合命令。

先分清:滤镜链 vs 滤镜图

**滤镜链(filter chain)**是滤镜的串行流水线,前一个的输出是后一个的输入,用逗号连接:

scale=1280:-1,format=yuv420p,vflip

数据像流水线一样依次流过:先缩放 → 再转像素格式 → 再垂直翻转。

**滤镜图(filter graph)**允许多条链存在,链之间可以分叉、汇合、交叉连接,用分号分隔链、方括号标记垫(pad):

[0:v]scale=640:480[a];[1:v]scale=640:480[b];[a][b]hstack

一句话总结:单路串行用 -vf(滤镜链),多路或需要拆分合流用 -filter_complex(滤镜图)-filter_complex 是超集——滤镜链只是没有分叉的滤镜图。

理解"垫":滤镜图的连线

垫(pad)是理解滤镜图的钥匙。方括号里的标签就是一条虚拟的连线,把上游输出接到下游输入:

输入0 ──[0:v]──> scale ──[a]──┐
                              ├──> hstack ──> 输出
输入1 ──[1:v]──> scale ──[b]──┘

对应命令:

ffmpeg -i left.mp4 -i right.mp4 -filter_complex \
  "[0:v]scale=640:480[left];[1:v]scale=640:480[right];[left][right]hstack" \
  out.mp4

逐段拆解:

  • [0:v]:第 0 个输入的视频流(0-i 的顺序,v 表示 video;音频是 a
  • scale=640:480[left]:处理后打上 left 标签,供下游引用
  • [left][right]hstackhstack 滤镜吃两个输入垫,横向拼接

没打标签的输出会自动接到滤镜图的最终输出;hstack 的输出没有标签,所以它就是结果。

-vf-filter_complex 的关系

-vf-filter_complex
输入流固定单路任意多路
能否引用第 2 个输入不能(-i 只当数据源)能,[1:v] 直接引用
输出自动映射到输出文件可用 -map 精确选择
音频不经手可一并处理

-vf 本质是 -filter_complex 的单路语法糖。但注意:同一个命令里 -vf-filter_complex 不要混用,容易产生两个互不知情的滤镜管线。

实战一:缩放 + 水印(最常见的复合需求)

给视频右下角加图片水印,同时限制到 720p:

ffmpeg -i input.mp4 -i logo.png -filter_complex \
  "[1:v]scale=120:-1[wm];[0:v][wm]overlay=W-w-20:H-h-20,scale=-2:720" \
  out.mp4

拆开看:

  1. [1:v]scale=120:-1[wm]:水印图缩到 120 宽(-1 表示按比例自动算高)
  2. [0:v][wm]overlay=...:把水印叠到主画面上。W/H 是主画面尺寸,w/h 是水印尺寸,所以 W-w-20:H-h-20 = 右下角留 20px 边距
  3. ,scale=-2:720:接在 overlay 后面的链尾缩放,-2 让宽度自动对齐到偶数(H.264 要求宽高为偶数,用 -1 会报错)

顺带记住 overlay 的位置速记:0:0 左上、(W-w)/2:(H-h)/2 居中、0:H-h 左下。

实战二:多路合成与画面布局

四宫格拼画面——每个输入缩到半宽半高,再用 xstack 摆位:

ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -i d.mp4 -filter_complex \
  "[0:v]scale=iw/2:ih/2[a];[1:v]scale=iw/2:ih/2[b];\
   [2:v]scale=iw/2:ih/2[c];[3:v]scale=iw/2:ih/2[d];\
   [a][b][c][d]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0" \
  out.mp4

layoutw0 表示"第 0 个输入的宽度"、h0 同理——垫与垫之间的相对位置关系直接写进布局表达式。

分叉也很自然:一个源同时出预览图和正式输出:

ffmpeg -i input.mp4 -filter_complex \
  "[0:v]split=2[v1][v2];[v1]scale=320:-1[thumb];[v2]scale=1280:-2[main]" \
  -map "[thumb]" thumb.jpg -map "[main]" main.mp4

split=2 把一路复制成两路,各自走不同的链,最后用 -map 指定哪个垫写入哪个输出文件。这是滤镜图相对 -vf 的真正威力所在。

调试技巧

滤镜图写长了必然出错,几个救命习惯:

  1. 逐段验证。先跑通 [0:v]scale=640:480[v],确认无误后再加第二段——一口气写完再调试是效率最低的方式。
  2. -v error 减少日志噪音。FFmpeg 默认日志很吵,只看 error 能立刻定位是哪个滤镜在抱怨。
  3. 报错会指名滤镜和垫Invalid argument 后面跟的垫标签就是断点,顺着它检查上游输出参数。
  4. 分辨率不匹配是头号错误源hstack/vstack 要求输入同高/同宽,overlay 像素格式不一致会静默变慢——拼接前先统一 scale + format=yuv420p

写在最后

滤镜图的思路其实是数据流编程:每个滤镜是一个节点,垫是连线,你设计的是图而不是命令。掌握 [x] 标签的连接逻辑后,剩下的只是查滤镜参数表。

不想手写的话,视频加水印视频转 GIF把本文用到的 overlay、scale 滤镜做成了即拿即用的界面;想自由组合参数,去 FFmpeg 命令实验室直接试跑——都在浏览器本地完成。

FAQ

Q:滤镜链的顺序影响结果吗? A:影响,而且影响体积。先 scale 缩小再做后续滤镜,处理的数据量更小;但去噪类滤镜通常要放在缩放前,顺序要按需求定。

Q:-1-2 在 scale 里有什么区别? A:-1 表示保持比例自动计算,-2 是自动计算且对齐到偶数。H.264/H.265 编码要求宽高为偶数,实际项目里基本都应该用 -2

Q:为什么我的 overlay 是逐帧处理但速度极慢? A:通常是主视频与水印的像素格式不一致,FFmpeg 在每一帧做隐式格式转换。给两路显式加 format=yuv420p 可以消除。

RELATED TOOLS · 把文章里的东西用起来