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

四宫格就是四个坐标:0_0、w0_0、0_h0、w0_h0。 空白处默认是绿的,fill=black 基本是必写的一句。

📝 本节和前面几节的关系

上一节把三条参数讲清了,这一节落到最小的完整例子:四格。 四格能通,九格只是多写五条坐标。

一、四个坐标

四宫格是两行两列,四条坐标一句话说完:

格子 坐标 含义


第 1 格 0_0 左上,原点 第 2 格 w0_0 右上,x 挪一格宽 第 3 格 0_h0 左下,y 挪一格高 第 4 格 w0_h0 右下,x、y 各挪一格

四条坐标里的 w0、h0 都是「第 0 路」的尺寸。 四格等大,所以 w0 = w1 = w2 = w3,用哪个都一样。

写 W_0 不行——W 是 overlay 的变量,xstack 里没有这个符号。 两个滤镜的表达式变量表不一样,混着记最容易出错。

二、四色各占一格

四路纯色拼起来,量每格中心色:

格子 输入 中心 RGB 什么色


第 1 格 red (253, 0, 0) 红 第 2 格 green (0, 127, 0) 绿 第 3 格 blue (0, 0, 254) 蓝 第 4 格 white (255, 255, 255) 白

输出 160×90,四格的中心色正好按输入顺序排下来。 这就是 xstack 的默认行为:第几路输入就摆在第几格。

green 量出来是 (0, 127, 0),不是 (0, 255, 0)—— ffmpeg 里的 green 是网页标准色 0x008000,比纯绿暗一半。要纯绿得写 lime。

蓝为什么是 254 不是 255,红为什么是 253:还是 YUV 范围压缩那个零头, 7.4.6 讲 color 滤镜时算过一遍,换个滤镜名字结论不变。

三、fill 不给就是绿的

四格等大时看不出 fill 的作用,所以故意给两路不同尺寸:

fill 写什么 空白处 RGB 是不是黑色


不给 (0, 136, 0) 不是 black (0, 0, 0) 是 red (252, 0, 0) 不是 0x336699 (48, 100, 151) 不是 white (253, 253, 253) 不是

不给 fill 时空白处是 (0, 136, 0)——不是黑,是个绿。 原因很简单:画布一开始是 YUV 全 0,Y=0、U=0、V=0 转成 RGB 就带绿味。

这不是 bug,是「没初始化」。 所以拼格子时 fill=black 基本是必写的一句,少写就会露出绿色空当。

顺带一个尺寸规律:两路 80×45 与 40×22 拼横排,输出是 120×45。 宽取两者相加,高取两者最大——矮的那格下方就被 fill 补上了。

四、动手 Demo:四色四格、fill 五档

下面这段代码先拼一张四色四宫格,量四格的中心色; 再给两路不同尺寸,试 fill 的五种写法。

import av
import numpy as np

L4 = u"0_0|w0_0|0_h0|w0_h0"
COL = [u"red", u"green", u"blue", u"white"]

def quad(fill=None, sizes=None):
    g = av.filter.Graph()
    sizes = sizes or [u"80x45"] * 4
    srcs = [g.add(u"color", u"c=%s:s=%s:r=25" % (COL[i], sizes[i]))
            for i in range(4)]
    args = u"inputs=4:layout=%s" % L4
    if fill:
        args += u":fill=%s" % fill
    xs = g.add(u"xstack", args)
    sk = g.add(u"buffersink")
    for i, p in enumerate(srcs):
        p.link_to(xs, 0, i)
    xs.link_to(sk)
    g.configure()
    return sk.pull().to_ndarray(format=u"rgb24")

def mid(a, i, cw=80, ch=45):
    j, k = i // 2, i % 2
    blk = a[j * ch + ch // 2 - 5:j * ch + ch // 2 + 5,
            k * cw + cw // 2 - 5:k * cw + cw // 2 + 5]
    return tuple(int(v) for v in blk.reshape(-1, 3).mean(axis=0))

a = quad()
print(u"--- 四色各占一格(输出 %dx%d)" % (a.shape[1], a.shape[0]))
for i in range(4):
    print(u"  第 %d 格 %-6s %s" % (i + 1, COL[i], mid(a, i)))

print(u"--- fill 五档(两路不同尺寸,看空白处)")
for f in (None, u"black", u"red", u"0x336699", u"white"):
    g = av.filter.Graph()
    s1 = g.add(u"color", u"c=red:s=80x45:r=25")
    s2 = g.add(u"color", u"c=blue:s=40x22:r=25")
    args = u"inputs=2:layout=0_0|w0_0"
    if f:
        args += u":fill=%s" % f
    xs = g.add(u"xstack", args)
    sk = g.add(u"buffersink")
    s1.link_to(xs, 0, 0)
    s2.link_to(xs, 0, 1)
    xs.link_to(sk)
    g.configure()
    o = sk.pull().to_ndarray(format=u"rgb24")
    print(u"  fill=%-10s %s" % (f, tuple(int(v) for v in o[38, 100])))

实跑输出:

--------------------------------------------------------------------
7.10.2 使用 FFmpeg 实现【四宫格】
--------------------------------------------------------------------

**① 先看 layout 怎么写**

**四宫格就是两行两列,四个坐标:**

  格子        坐标      含义
  ---------------------------------------------
  第 1 格     0_0       左上,原点
  第 2 格     w0_0      右上,x 挪一格宽
  第 3 格     0_h0      左下,y 挪一格高
  第 4 格     w0_h0     右下,x、y 各挪一格

**注意 `w0` 里的 0 指的是「第 0 路输入的宽」,不是「第 1 格」。**
**四格等大时 `w0 = w1 = w2 = w3`,随便用哪个都一样;不等大时就要挑对了。**

**② 四色各占一格,量出来**

  格子        输入      中心 RGB            什么色
  ----------------------------------------------------
  第 1 格     red       (253, 0, 0)         红
  第 2 格     green     (0, 127, 0)         绿
  第 3 格     blue      (0, 0, 254)         蓝
  第 4 格     white     (255, 255, 255)     白

**输出 `160×90`,四格的中心色正好是红、绿、蓝、白,顺序一路对下来。**
**这就是 xstack 的默认行为:第几路输入就摆在第几格。**

**`green` 量出来是 `(0, 127, 0)`,不是 `(0, 255, 0)`——**
**ffmpeg 里的 `green` 是网页标准的 0x008000,比纯绿暗一半。要纯绿得写 `lime`。**

**③ fill 不给就是绿色,不是黑色**

**四格等大时看不出这一条,所以故意给两路不同尺寸:**

  fill 写什么     空白处 RGB          是不是黑色
  --------------------------------------------------
  不给            (0, 136, 0)         不是
  black           (0, 0, 0)           是
  red             (252, 0, 0)         不是
  0x336699        (48, 100, 151)      不是
  white           (253, 253, 253)     不是

**不给 fill 时空白处是 `(0, 136, 0)`——因为画布一开始是 YUV 全 0,**
**Y=0、U=0、V=0 转成 RGB 就是个带绿味的色。这不是 bug,是没初始化。**

**所以拼格子时 `fill=black` 基本是必写的。**
**少写这一句,露出来的空当是绿的,一眼就能看出不对。**

第一种量法与本节那张表完全一致,红绿蓝白一路对下来。 量的是每格中心 10×10,避开格与格交界处的混色。

第二种五档 fill 里,只有 black 是 (0, 0, 0), 不给的那一档是 (0, 136, 0)——绿得挺明显,拼图时一眼就能看出来。

注意 green 与 lime 的区别在真实项目里会咬人: 想做「绿幕抠像」的底,写 green 会抠不干净,因为它是 0x008000 的暗绿。

30 秒实验

  1. 改颜色:把 COL 换成 [red, yellow, cyan, magenta],看四格量出来是什么。
  2. 改尺寸:把四格改成 160x90,看输出是不是变成 320×180。
  3. 删 fill:把两路不同尺寸那段的 fill 去掉,确认空白处变绿。

常见误区

  1. 以为空白处默认是黑色。默认是 YUV 全 0 的绿。
  2. 把 green 当纯绿。它是 0x008000,要纯绿写 lime。
  3. 用 W、H 写 xstack 的坐标。那是 overlay 的变量,xstack 只认 w0、h0。
自测三问(点开对答案) 1. 四宫格的四条坐标怎么写? —— `0_0|w0_0|0_h0|w0_h0`。 2. 不给 fill,空白处是什么色? —— `(0, 136, 0)`,YUV 全 0 转出来的绿。 3. 两路 80×45 与 40×22 横排,输出多大? —— 120×45,宽相加、高取最大。

相关笔记