滤镜是最小单位,链是逗号串起来的几个滤镜,图是分号隔开的一堆链。 三层包含,一层比一层大。
上一节说「复杂滤镜是一张网」,这一节把这张网拆成三个层级。 概念不难,难的是把概念对应到写法上。
一、三层包含,一层比一层大
三个词,三种粒度:
中文 英文 怎么分隔 例子
滤镜 filter 一个名字 scale 滤镜链 filterchain 逗号 scale,hflip 滤镜图 filtergraph 分号 A;B
三个层级是包含关系:图里有若干条链,链里有若干个滤镜。 理解这一点,读别人写的长命令就不会晕——先按分号切,再按逗号切。
二、一条链,逐环看尺寸
把四个滤镜串成一条链,看每一环交出什么尺寸:
第几环 参数 输出尺寸
0 源 — 160×90 1 scale 320:180 320×180 2 crop 200:121:40:20 200×120 3 hflip (无参数) 200×120 4 pad 240:160:20:20:black 240×160
每一环只看上一环交过来的东西,不管前面发生过什么。 这就是链的本质:四次独立的变换,串在一起。
第二环那行值得停一下:crop 要的是 200×121,出来却是 200×120。 因为 yuv420p 的宽高必须是偶数,121 被就近取成了 120。
这件事不报错、也不警告,只是默默改数。 查「输出尺寸对不上」的时候,先怀疑这里,别急着怀疑命令写错了。
三、图就是链加标签
把上面那条链和另一条链并排放,用标签连起来,就成了图:
split=2[a][b]; [a]scale=320:180,crop=200:121:40:20[v1]; [b]hflip[v2]
按分号切,得到三条链:split=2[a][b]、[a]scale=...、[b]hflip。
三条链之间用 [a]、[b] 两个标签交接,整体才是图。
标签就是「管子」的名字。 一条链的出口起个名字,另一条链的入口喊这个名字,数据就从那儿流过去。
所以写图的步骤是固定的三步:先想清楚要几条链, 每条链里放哪几个滤镜、按什么顺序,最后用标签把它们接起来。
四、从命令行读回三层结构
拿一条稍长的命令练手:
-vf "scale=80:45,hflip,drawbox=x=10:y=10:w=20:h=10"
这一整串是一条链,三个滤镜,没有分号——所以它还只是链,不是图。
-vf 的参数位置上永远只能放一条链,这是它名字里那个 v 的由来。
换成 -filter_complex 之后,位置放的就是图了:
可以一条链,也可以十条链,全看你要画多复杂。
五、动手 Demo:四环链逐级量尺寸,抓出取偶那一环
下面这段代码把四环链逐级跑一遍,每一步都量一次真实尺寸。 重点看第二环:参数写的 121,出来的是 120。
import av
from fractions import Fraction
TB = Fraction(1, 25)
CHAIN = [(u"scale", u"320:180"), (u"crop", u"200:121:40:20"),
(u"hflip", u""), (u"pad", u"240:160:20:20:black")]
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray()
c0.close()
for n in range(len(CHAIN) + 1):
g = av.filter.Graph()
prev = g.add_buffer(width=160, height=90,
format=u"yuv420p", time_base=TB)
for name, args in CHAIN[:n]:
f = g.add(name, args) if args else g.add(name)
prev.link_to(f)
prev = f
sk = g.add(u"buffersink")
prev.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
prev.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
tag = u"源" if n == 0 else CHAIN[n - 1][0]
print(u"%-8s %d×%d" % (tag, o.shape[1], o.shape[0]))
实跑输出:
--------------------------------------------------------------------
7.1.2 滤镜图、滤镜链、滤镜的关系
--------------------------------------------------------------------
**① 这三个词是层层包含的:滤镜 < 链 < 图。**
中文 英文 怎么分隔 例子
-------------------------------------------------------
滤镜 filter 一个名字 scale
滤镜链 filterchain 逗号 scale,hflip
滤镜图 filtergraph 分号 A;B
**② 一条四环的链,逐环看尺寸怎么变**
第几环 参数 输出尺寸
------------------------------------------------
0 源 — 160×90
1 scale 320:180 320×180
2 crop 200:121:40:20 200×120
3 hflip (无参数) 200×120
4 pad 240:160:20:20:black 240×160
**每一环只看上一环交过来的东西,不管前面发生过什么。**
**这就是链的本质:四个滤镜,四次独立的变换,串在一起。**
**③ 中间那两环有个细节值得停一下**
**crop 要的是 200×121,实际输出是 200×120。**
**因为 yuv420p 的宽高必须是偶数,121 被就近取成了 120——**
**这件事不报错、也不警告,只是默默改数。查画面尺寸对不上时,先怀疑这里。**
**④ 图就是「几条链 + 几个连接点」**
**把刚才那条链和另一条链并排放,用标签连起来,就是图:**
`split=2[a][b]; [a]scale=320:180,crop=200:120:40:20[v1]; [b]hflip[v2]`
**这里有三条链:split 那条、[a] 开头那条、[b] 开头那条。**
**三条链之间用标签 [a]、[b] 交接,整体才是图。**
**⑤ 关系可以一句话说完**
层级 数量关系
-----------------------------------
滤镜图 由若干条链组成
滤镜链 由若干个滤镜组成
滤镜 最小单位,只做一件事
**所以「写一个滤镜图」的活儿,拆开就是:先想清楚要几条链,**
**每条链里放哪几个滤镜、按什么顺序,最后用标签把它们接起来。**
每一轮都重新建图,只挂前 n 个滤镜——所以每行都是「走到这一环」的真实尺寸。 这个方法比算一遍公式可靠,尤其是遇到取偶、取整这类隐藏规则时。
crop 那一行的 121 变成 120,是 yuv420p 带来的: 色度平面是两两合并的,奇数高最后一行没有配对,只能砍掉。 换成 rgb24 就不会砍——同一句 crop,换个像素格式结果就不同。
30 秒实验
- 换格式:把
format=u"yuv420p"换成rgb24,看 crop 还砍不砍。 - 加一环:在 pad 后面再加
scale=120:80,看尺寸回到哪里。 - 改奇数:把 crop 写成
200:119,看输出是 118 还是 120。
常见误区
- 把滤镜链叫成滤镜图。只有一条链时它就是链,没有分号就不成图。
- 以为标签可以随便起。同名标签会被当成同一根管子,数据流就串了。
- 以为 crop 写什么就出什么。yuv420p 下宽高会被取偶,不报错。
自测三问(点开对答案)
1. 滤镜链用什么分隔,滤镜图用什么分隔? —— 链用逗号,图用分号。 2. crop 要 200×121 为什么得到 200×120? —— yuv420p 要求宽高为偶数,121 被就近取成 120。 3. 标签的作用是什么? —— 给管子起名字,让一条链的出口能接到另一条链的入口。- ☐ (本节完,下一节 7.1.3 简单滤镜和复杂滤镜的区别)