🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

拿真视频拼四宫格,每格挂一个改亮度的滤镜就能验证顺序。 工程上是四步:统一尺寸、补比例、按顺序接、写 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 秒实验

  1. 换滤镜:把第 2 格从 negate 换成 hue=s=0,看均值还是不是 125.5。
  2. 换顺序:把 PRE 的四项倒过来,看均值顺序是不是也倒过来。
  3. 加 fill:给四路不同尺寸,看空白处是不是绿的。

常见误区

  1. 四路尺寸不统一。xstack 会歪,而且不报错。
  2. 以为反色是精确的 255 减。YUV 转回来有范围压缩,差几格。
  3. 先拼再缩。每格要多糊一次,画质与速度都吃亏。
自测三问(点开对答案) 1. 怎么验证四宫格的位置对不对? —— 给每路挂不同的亮度处理,量四格中心均值,看是否跟预期位置对上。 2. 先缩再拼和先拼再缩,哪个好? —— 先缩再拼。每格只重采样一次,xstack 处理的画布也更小。 3. 四路时长不一致会怎样? —— 默认按最短的收尾,整段跟着变短。

相关笔记