拿真视频拼四宫格,每格挂一个改亮度的滤镜就能验证顺序。 工程上是四步:统一尺寸、补比例、按顺序接、写 fill。
7.10.2 用的是纯色,只能验位置;这一节换成真视频,验的是「每格内容不同」。 它也是本章多滤镜合成的收尾案例。
一、为什么还要再做一遍
纯色拼图能验位置,验不出「每格真的是不同的画面」。 因为纯色四格颜色本来就不同,接错位置也看得出;换成同一段视频,就看不出了。
办法是给每路挂一个改亮度的滤镜,让四格亮度差得开: 第 1 格原样、第 2 格反色、第 3 格压暗一半、第 4 格提亮一半。
这样一量中心均值就有四个互不相同的数,位置对不对一目了然。 这个「给每路打个标记再量」的思路,比肉眼看图可靠得多。
二、四格中心均值
同一帧、同一片源,四格各做了什么、量出来多少:
格子 这一格做了什么 中心均值
第 1 格 原样 131.2 第 2 格 negate 反色 125.5 第 3 格 y=val0.5 压暗 57.1 第 4 格 y=val1.5 提亮 155.1
压暗掉到 57.1、提亮升到 155.1,跟「乘 0.5」「乘 1.5」大致对得上 (对不上整倍数是因为均值取的是 RGB 三通道再取平均,不是单看 Y)。
反色那一格是 125.5,不是 255-131.2=123.8。
差一点点是因为反色作用在 YUV 上,转回来有范围压缩——别指望它是精确的 255 减。
三、先缩再拼还是先拼再缩
四格都用原尺寸时输出 320×180;每格先缩一半,输出就是 160×90:
每格尺寸 输出尺寸 宽高是原片几倍
160×90(不缩) 320×180 2 倍 80×45(缩一半) 160×90 1 倍
两条路的画质不一样:先说 scale 再拼,四格各糊一次,拼完就是成品; 先拼成 320×180 再缩到 160×90,等于每格糊两次,细节掉得更多。
计算量也差得开:先缩再拼时 xstack 只处理 160×90 大的画布, 先拼再缩要处理 320×180,最后还得再缩一次。所以「先缩」几乎总是对的。
四、工程上的四步
真做一条四宫格命令,按这四步走不会翻车:
步 做什么 为什么
一 每路先 scale 到统一尺寸 xstack 要求等大 二 必要时 pad 补成同一比例 避免拉伸变形 三 按想要的顺序接进 xstack 顺序就是格子位置 四 写 fill=black 不留绿色空当
还有一条不在表里:四路时长要一致。
xstack 默认按最短的那路收尾,某一路短了整段就跟着短;
要让它按最长的走,加 shortest=0,但那样短的那几格会定格在最后一帧。
最后一句总结:多宫格的所有麻烦都在「进 xstack 之前」。 尺寸、比例、时长、顺序,四样对齐了,xstack 那一句永远只有一行。
五、动手 Demo:真实素材四格、两种缩放顺序
下面这段代码用同一段视频拼四宫格,每格挂一个不同的亮度处理, 量四格中心均值验证顺序;最后再试「每格先缩一半」。
import av
ESC = u"s7.mp4".replace(u"\\", u"/").replace(u":", u"\\:")
L4 = u"0_0|w0_0|0_h0|w0_h0"
def quad(pre=None, scale=None):
u"""pre:每路额外串的滤镜;scale:先缩到多大。"""
g = av.filter.Graph()
srcs = []
for i in range(4):
p = g.add(u"movie", ESC)
if scale:
f = g.add(u"scale", scale)
p.link_to(f)
p = f
for name, args in (pre[i] if pre else []):
f = g.add(name, args) if args else g.add(name)
p.link_to(f)
p = f
srcs.append(p)
xs = g.add(u"xstack", u"inputs=4:layout=%s" % L4)
sk = g.add(u"buffersink")
for i, p in enumerate(srcs):
p.link_to(xs, 0, i)
xs.link_to(sk)
g.configure()
o = sk.pull()
return o.to_ndarray(format=u"rgb24"), o.width, o.height
PRE = [[], [(u"negate", None)], [(u"lutyuv", u"y=val*0.5")],
[(u"lutyuv", u"y=val*1.5")]]
a, w, h = quad(PRE)
print(u"--- 真实素材四宫格 %dx%d" % (w, h))
for i in range(4):
j, k = i // 2, i % 2
blk = a[j * 90 + 30:j * 90 + 60, k * 160 + 40:k * 160 + 120]
print(u" 第 %d 格 中心均值 %.1f" % (i + 1, blk.mean()))
print(u"--- 先缩一半再拼")
_, w2, h2 = quad(None, u"80:45")
print(u" 每格 80x45 -> %dx%d" % (w2, h2))
实跑输出:
--------------------------------------------------------------------
7.10.5 实现的视频【四宫格】
--------------------------------------------------------------------
**① 换成真实素材,每格加一个不同的处理**
**纯色拼图只能验位置,验不出「每格真的是不同的画面」。**
**换成真视频,并给每路挂一个不同的滤镜:**
**第 1 格原样、第 2 格反色、第 3 格压暗一半、第 4 格提亮一半。**
**这样四格的亮度差得开,一量中心均值就能认出来。**
**② 四格中心均值**
格子 这一格做了什么 中心均值
-------------------------------------------
第 1 格 原样 131.2
第 2 格 negate 反色 125.5
第 3 格 y=val*0.5 压暗 57.1
第 4 格 y=val*1.5 提亮 155.1
**原样 131.2,压暗掉到 57.1,提亮升到 155.1,反色变成 125.5。**
**四个数互不相同,说明四格确实是四段不同的画面,没接错位置。**
**输出 320×180——四格各 160×90,正好 2×2。**
**③ 先缩小再拼,输出跟着变小**
每格尺寸 输出尺寸 宽高是原片几倍
---------------------------------------------------
160×90(不缩) 320×180 2 倍
80×45(缩一半) 160×90 1 倍
**拼完再缩是另一回事:先拼成 320×180 再 scale 到 160×90,**
**四格都得跟着糊一遍;先缩再拼,每格只糊一次,总计算量还更小。**
**④ 工程上的四步**
步 做什么 为什么
-------------------------------------------------------
一 每路先 scale 到统一尺寸 xstack 要求等大
二 必要时 pad 补成同一比例 避免拉伸变形
三 按想要的顺序接进 xstack 顺序就是格子位置
四 写 fill=black 不留绿色空当
**还有一条不在表里:四路时长要一致。**
**xstack 默认按最短的那路收尾,某一路短了,整段就跟着短。**
四行均值 131.2 / 125.5 / 57.1 / 155.1,四个数互不相同, 说明四格确实是四段不同处理,没有一格接错位置。
如果顺序接反了,这四个数会跟着换位置,但集合不变—— 所以光看「有四个不同的数」不够,得跟预期位置逐个对。
最后一行是缩放顺序的对比:每格缩一半,输出就从 320×180 变成 160×90。 xstack 本身不改尺寸,它只是把各路按坐标摆好。
30 秒实验
- 换滤镜:把第 2 格从
negate换成hue=s=0,看均值还是不是 125.5。 - 换顺序:把
PRE的四项倒过来,看均值顺序是不是也倒过来。 - 加 fill:给四路不同尺寸,看空白处是不是绿的。
常见误区
- 四路尺寸不统一。xstack 会歪,而且不报错。
- 以为反色是精确的 255 减。YUV 转回来有范围压缩,差几格。
- 先拼再缩。每格要多糊一次,画质与速度都吃亏。
自测三问(点开对答案)
1. 怎么验证四宫格的位置对不对? —— 给每路挂不同的亮度处理,量四格中心均值,看是否跟预期位置对上。 2. 先缩再拼和先拼再缩,哪个好? —— 先缩再拼。每格只重采样一次,xstack 处理的画布也更小。 3. 四路时长不一致会怎样? —— 默认按最短的收尾,整段跟着变短。- ☐ 7.10 完,下一节 7.11.1 fade 滤镜的参数说明