🎬 第六章 FFmpeg命令行实现图片水印及文字跑马灯
📄 一句话概括

overlay 的全部数学是一行乘加:输出 = 前景×alpha + 背景×(1-alpha)。 yuva420p 里色度减半、alpha 却是全分辨率,这一点很多人搞反。

📝 本节和前面几节的关系

前面讲的都是「一条链」。overlay 是本章第一次真正把两路画面合起来。 图片水印、文字水印,最终都落到这个公式上。

一、一行乘加公式

五档 alpha,公式算一遍、PIL 独立算一遍:

alpha 公式算 PIL 算 差


0 200.0 200 0 64 159.8 160 0 128 119.7 120 0 192 79.5 80 0 255 40.0 40 0

两条路一个都不差。前景取 40、背景取 200,alpha 从 0 到 255 走一遍, 输出从 200 平滑走到 40——这就是 overlay 干的所有事。

alpha 为 0 时输出全是背景,为 255 时输出全是前景。 所谓「半透明」,不过是这一段的中间取值。

二、yuva420p 的四个平面

平面 尺寸 是否全分辨率


Y 亮度 160×90 是 U 色度 80×45 否,减半 V 色度 80×45 否,减半 A 透明度 160×90 是

最容易搞反的就是这一行:色度减半了,alpha 没有。 所以做半透明羽化边缘时,alpha 的精度是够的。

真正会丢精度的是颜色:U、V 只有四分之一的分辨率。 半透明的彩色水印在边缘处发灰,根子就在这里。

三、四种常见格式,谁带 alpha

像素格式 带 alpha 什么时候用


yuv420p 不带 普通视频 yuva420p 带 叠水印的主流选择 rgba 带 中间处理、读 PNG argb 带 少数软件要这个顺序

不带 alpha 的格式也能叠,但那就没有「透」了: 公式里 alpha 恒等于 1,输出等于前景,也就是一块实心方块。

这也是为什么「水印加了但四周有黑框」—— 多半是图被读成 rgb24,透明区变成黑色,公式里 alpha 就恒等于 1 了。

四、动手 Demo:两条路算同一个公式

先用手写公式算五档 alpha 的输出值,再用 Pillow 的 alpha_composite 独立算一遍。 最后打印 yuva420p 四个平面的真实尺寸。

import numpy as np
from PIL import Image
import av

fgv, bgv = 40, 200
for a8 in (0, 64, 128, 192, 255):
    a = a8 / 255.0
    theo = fgv * a + bgv * (1 - a)
    fg = Image.new(u"RGBA", (8, 8), (fgv, fgv, fgv, a8))
    bg = Image.new(u"RGBA", (8, 8), (bgv, bgv, bgv, 255))
    got = np.array(Image.alpha_composite(bg, fg))[0, 0, 0]
    print(u"alpha %3d  公式 %6.1f  PIL %3d  差 %d"
          % (a8, theo, got, abs(got - round(theo))))

fr = av.VideoFrame(160, 90, format=u"yuva420p")
for i, p in enumerate(fr.planes):
    print(u"平面 %d  %dx%d" % (i, p.width, p.height))

实跑输出:

--------------------------------------------------------------------
6.5.1 overlay 技术简介——一个乘加公式
--------------------------------------------------------------------

**overlay 的全部数学就是这一行:**
**`输出 = 前景 × alpha + 背景 × (1 - alpha)`**

**① 五档 alpha,两条独立的路各算一遍**

  alpha     公式算     PIL 算     差
  --------------------------------------
  0         200.0      200        0
  64        159.8      160        0
  128       119.7      120        0
  192       79.5       80         0
  255       40.0       40         0

**两条路的结果一个都不差,说明这个公式就是 overlay 的真身。**
**alpha 为 0 时输出全是背景,为 255 时输出全是前景,中间是线性过渡。**

**② yuva420p 的四个平面:alpha 是全分辨率的**

  平面         尺寸       是否和画面一样大
  --------------------------------------------
  Y 亮度       160×90     是
  U 色度       80×45      否(减半)
  V 色度       80×45      否(减半)
  A 透明度     160×90     是

**第二、第三个平面才是 80×45 的半分辨率——**
**色度 U、V 按 4:2:0 减半了,但 alpha 没有跟着减半。**
**所以做半透明的圆角、羽化边缘时,alpha 的精度是够的;**
**真正会丢精度的是颜色,因为 U、V 只有四分之一的分辨率。**

**③ 四种常见格式哪个带 alpha**

  像素格式     带 alpha     什么时候用
  ------------------------------------------------
  yuv420p      不带         普通视频
  yuva420p     带           叠水印的主流选择
  rgba         带           中间处理、读 PNG
  argb         带           少数软件要这个顺序

**不带 alpha 的格式也能叠,但那就没有「透」了:**
**公式里 alpha 恒等于 1,输出等于前景,也就是一块实心方块。**

Image.alpha_composite(bg, fg) 的参数顺序是「底下、上面」,别写反。 写反了 alpha 就作用在背景上,结果完全不同。

fr.planes 只有真的分配了内存才能取到尺寸,所以要 VideoFrame(160, 90, ...)。 空构造拿不到平面信息。

这段里没有 overlay 滤镜——因为本机建不起双输入的图(下一节细说)。 但公式与平面结构都是真的,换到能跑的环境,结果一致。

30 秒实验

  1. 换通道:前景改成只改一个通道,看另外两个通道是不是保持背景值。
  2. 量平面:把 yuva420p 换成 yuv420p,看平面数变成几个。
  3. 真跑一次:有 overlay 的机器上,overlay=10:10:format=auto 对比 format=yuv420。

常见误区

  1. 以为 alpha 也跟着 4:2:0 减半。它是全分辨率的。
  2. 把 alpha_composite 的参数写反。作用对象会变成背景。
  3. 用 rgb24 读水印图。透明区变黑,alpha 恒等于 1,四周挂黑框。
自测三问(点开对答案) 1. overlay 的公式是什么? —— 输出 = 前景×alpha + 背景×(1-alpha),逐像素、逐通道。 2. yuva420p 里哪个平面是全分辨率的? —— Y 与 A。U、V 按 4:2:0 各减半。 3. 为什么半透明水印的边缘会发灰? —— 色度 U、V 只有四分之一分辨率,颜色精度不够。

相关笔记