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 秒实验
- 换通道:前景改成只改一个通道,看另外两个通道是不是保持背景值。
- 量平面:把
yuva420p换成yuv420p,看平面数变成几个。 - 真跑一次:有 overlay 的机器上,
overlay=10:10:format=auto对比format=yuv420。
常见误区
- 以为 alpha 也跟着 4:2:0 减半。它是全分辨率的。
- 把 alpha_composite 的参数写反。作用对象会变成背景。
- 用 rgb24 读水印图。透明区变黑,alpha 恒等于 1,四周挂黑框。
自测三问(点开对答案)
1. overlay 的公式是什么? —— 输出 = 前景×alpha + 背景×(1-alpha),逐像素、逐通道。 2. yuva420p 里哪个平面是全分辨率的? —— Y 与 A。U、V 按 4:2:0 各减半。 3. 为什么半透明水印的边缘会发灰? —— 色度 U、V 只有四分之一分辨率,颜色精度不够。- ☐ (本节完,下一节 6.5.2 -filter_complex overlay 添加水印)