23、视频滤镜链:多个滤镜串联使用、滤镜图(filter graph)的概念与构建

说实话,滤镜链这个概念,我第一次接触的时候也觉得有点绕。但说白了,它就是「把多个滤镜像串糖葫芦一样串起来」。你想想看,一个滤镜处理完,输出给下一个滤镜,下一个再处理,再传给下一个……这就是滤镜链。

但光有链还不够。有时候我们需要更复杂的结构——比如一个画面拆成两路,一路做模糊,一路做锐化,最后再合并。这时候就需要滤镜图(filter graph)了。嗯,咱们今天就把这两个东西彻底讲明白。

滤镜链:最简单的串联方式

滤镜链,就是滤镜一个接一个地处理。每个滤镜的输出,自动成为下一个滤镜的输入。在 FFmpeg 里,我们用逗号 , 来连接它们。

举个例子:我想先缩放视频到 640x480,再旋转 90 度,最后加一个模糊效果。命令是这样的:

ffmpeg -i input.mp4 -vf "scale=640:480,transpose=1,boxblur=2:1" output.mp4

这里 scaletransposeboxblur 三个滤镜用逗号串起来。数据流就是:

  • 原始帧 → scale → 缩放后的帧 → transpose → 旋转后的帧 → boxblur → 模糊后的帧 → 输出

我在项目中遇到过一个问题:滤镜顺序搞反了。比如先旋转再缩放,和先缩放再旋转,结果完全不一样。你想想看,先旋转再缩放,旋转后的画面尺寸变了,缩放参数就得重新算。所以顺序很重要,千万别搞混。

小技巧:我个人习惯在写长滤镜链时,先在脑子里过一遍数据流。或者用 -vf "filter1,filter2,filter3" 这种写法,一目了然。

滤镜图:更复杂的处理结构

滤镜链只能处理「一条路走到黑」的情况。但实际工作中,我们经常需要分路、合并、交叉处理。这时候就要用到滤镜图了。

滤镜图用分号 ; 来分隔不同的滤镜「段落」,用方括号 [label] 来标记输入输出节点。说白了,就是给每个滤镜的输入输出起个名字,然后手动连接它们。

来看一个经典例子:把视频分成两路,一路做边缘检测,另一路做颜色反转,最后叠加在一起。

ffmpeg -i input.mp4 -vf "
  [0:v]split[base][edge];
  [edge]edgedetect[edge_out];
  [base]negate[neg_out];
  [edge_out][neg_out]overlay
" output.mp4

这个图的结构是这样的:

  • [0:v] 是输入视频流
  • split 把画面复制成两路:[base][edge]
  • [edge]edgedetect 滤镜,输出 [edge_out]
  • [base]negate 滤镜,输出 [neg_out]
  • 最后 [edge_out][neg_out] 通过 overlay 叠加

你看,这就是一个典型的滤镜图。有分叉,有合并,比单纯的链灵活多了。

注意:滤镜图里的标签名要唯一,不能重复。我曾经因为两个滤镜用了同一个标签名 [out],结果 FFmpeg 报错说「标签冲突」。排查了半天才发现是命名重复了。

滤镜图的构建规则

构建滤镜图其实有套路。我总结了几条核心规则:

  1. 每个滤镜都要有明确的输入和输出——用 [输入标签]滤镜名[输出标签] 的格式
  2. 标签名可以任意起——但建议用有意义的名称,比如 [scaled][rotated]
  3. 分号分隔不同的滤镜段落——每个段落可以是一个滤镜,也可以是一个滤镜链
  4. 最后一个滤镜的输出就是最终输出——不需要显式指定标签

举个例子,一个稍微复杂点的图:先缩放,然后分两路,一路做模糊,一路做锐化,最后叠加。

ffmpeg -i input.mp4 -vf "
  [0:v]scale=640:480[scaled];
  [scaled]split[blur_in][sharpen_in];
  [blur_in]boxblur=2:1[blur_out];
  [sharpen_in]unsharp=5:5:1.0[sharpen_out];
  [blur_out][sharpen_out]overlay=format=auto
" output.mp4

这个图有 5 个滤镜段落,4 个标签。数据流清晰,每个节点都能追踪。嗯,这就是我推荐的做法——标签名起得好,调试起来省一半时间。

用 SVG 画一张滤镜图结构

光说可能不够直观。我画了一张图,展示上面那个「缩放→分路→模糊+锐化→叠加」的滤镜图结构。

输入 [0:v] scale split boxblur unsharp overlay 输出 [blur_in] [sharpen_in] [blur_out] [sharpen_out] [scaled]

这张图里,每个矩形代表一个滤镜,箭头代表数据流向。标签名标在节点旁边。你看,数据从左边进来,经过 scale,到 split 分叉,然后分别走 boxblur 和 unsharp,最后在 overlay 合并输出。结构一目了然。

实际项目中的避坑指南

我在实际项目中用过不少滤镜图,踩过一些坑。分享几个经验:

  • 标签名不要用数字开头——FFmpeg 会把 [1v] 误解为输入流编号。我吃过这个亏,后来统一用字母开头加下划线,比如 [v1][v_out]
  • 滤镜图里不要有「悬空」的输入输出——每个标签要么被定义,要么被使用,不能有未连接的节点。否则 FFmpeg 会报错
  • 复杂滤镜图建议先画草图——我习惯在纸上画一遍结构,再写命令。尤其是分路超过 3 路的时候,脑子容易乱
  • -filter_complex 代替 -vf——滤镜图建议用 -filter_complex,它支持多输入多输出,更灵活。-vf 只能处理单路视频
核心总结:滤镜链用逗号串联,适合线性处理;滤镜图用分号和标签,适合分路合并。构建滤镜图的关键是:明确每个节点的输入输出,标签名唯一,数据流闭环。掌握了这些,你就能随心所欲地组合滤镜了。

好了,滤镜链和滤镜图的概念就讲到这里。说白了,它就是 FFmpeg 里最灵活、最强大的视频处理方式。你想想看,只要你能画出数据流图,FFmpeg 就能帮你实现。这就是它的魅力所在。


公众号:蓝海资料掘金营,微信deep3321