color=c=black:s=1280x720:d=5 才是真黑底,它的 Y 平面是 16、色度是 128。 颜色能写名字、写 0xRRGGBB、写 random,转回 RGB 会有一两个数的偏差。
上一节的 nullsrc 造出来的底带绿味,这一节给出正解。 7.4 到这里把「裁剪」与「造画面」两件事收完。
一、命令与参数
跟 nullsrc 的写法几乎一样:
ffmpeg -f lavfi -i color=c=black:s=1280x720:d=5 -c:v libx264 -pix_fmt yuv420p black.mp4
c 是颜色,可以写颜色名、0xRRGGBB,甚至写 random。
其余参数跟 nullsrc 完全一致:s 尺寸、r 帧率、d 时长。
两者唯一的区别就是「内容」:nullsrc 给空白,color 给一个具体的颜色。 所以命令里只换一个名字,别的都不用动。
二、三种写法实测
把首像素打出来,看实际是什么颜色:
写法 首像素 RGB 说明
c=black [0, 0, 0] 真黑 c=red [253, 0, 0] 红不是 255 c=0x336699 [49, 100, 151] 十六进制
c=red 出来是 [253, 0, 0] 而不是 [255, 0, 0],
因为 yuv420p 是有范围压缩的(16 到 235),转回 RGB 时会有个位数的偏差。
写 0x336699 出来是 [49, 100, 151],同样差一两个数。
这不是错,是 RGB 到 YUV 再回 RGB 的来回转换,永远不可能完全无损。
做 UI 底色、品牌色的时候要把这个偏差算进去。 差两三个数肉眼无所谓,但如果要跟另一张图做像素比对,就得接受它。
三、和 nullsrc 的对比
四行说清两者分工:
项 nullsrc color=c=black
Y 平面 0 16 U/V 平面 0 128 看着像 带绿味的黑 真正的黑 适合 画布、占位 黑幕、黑边
要「黑幕」就用 color,要「空地」就用 nullsrc,别混。 黑边补出来带绿味,在深色场景里会很明显——这是最容易返工的一处。
四、三种颜色写法
记三种就够日常用:
写法 例子
颜色名 black、white、red、blue 十六进制 0x336699 随机 random
c=random 每帧换一个颜色,用来做闪色测试很方便。
但压出来几乎不省字节——每一帧都完全不同,编码器找不到可参考的前一帧。
另外还能带透明度:[email protected],一半透明。
但输出要是 yuv420p,透明度会丢掉,得配 yuva420p 与支持 alpha 的容器。
五、收个尾
7.4 这一节把「裁剪」和「凭空造画面」两件事讲完了。 crop 负责从有到有,nullsrc 与 color 负责从无到有。
再往后 7.5 起的倒放、翻转、填充,全是在这两件事上做叠加。 换句话说:一整套滤镜特效,底层都是「取一块、造一块、换一块」。
这一节最该带走的一句话:造黑底用 color,别用 nullsrc。 那一层绿味在浅色场景里看不出来,一到暗场就露馅。
六、动手 Demo:三种颜色,三个首像素
下面这段代码把三种颜色各出一帧,打首像素与 Y 平面最小值。
最后一行是 0x336699,看它转回来是多少。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
fr = next(c0.decode(video=0))
base = fr.to_ndarray(format=u"yuv420p")
full = fr.to_ndarray(format=u"rgb24")
W, H = fr.width, fr.height
c0.close()
def probe(args, cap=1):
g = av.filter.Graph()
s0 = g.add(u"color", args)
sk = g.add(u"buffersink")
s0.link_to(sk)
g.configure()
n = 0
info = None
while n < cap:
try:
o = sk.pull()
except Exception:
break
if info is None:
a = o.to_ndarray(format=u"rgb24")
y = min(bytes(o.planes[0])[:o.planes[0].line_size])
info = (a[0, 0].tolist(), y)
n += 1
return n, info
for c, tag in ((u"black", u"c=black"), (u"red", u"c=red"),
(u"0x336699", u"c=0x336699")):
n, info = probe(u"c=%s:s=160x90:r=25:d=2" % c)
print(u"%-12s %d 帧 首像素 %s Y=%d" % (tag, n, info[0], info[1]))
实跑输出:
--------------------------------------------------------------------
7.4.6 使用 color 滤镜生成黑色背景的视频
--------------------------------------------------------------------
**① 命令与参数**
`ffmpeg -f lavfi -i color=c=black:s=1280x720:d=5 -c:v libx264 -pix_fmt yuv420p black.mp4`
**`c` 是颜色,可以写颜色名、`0xRRGGBB`,甚至写 `random`。**
**② 三种写法实测出来是什么颜色**
写法 首像素 RGB 说明
------------------------------------------------
c=black [0, 0, 0] 真黑
c=red [253, 0, 0] 红不是 255
c=0x336699 [49, 100, 151] 十六进制
**`c=red` 出来是 `[253, 0, 0]` 而不是 `[255, 0, 0]`,**
**因为 yuv420p 是有范围压缩的(16 到 235),转回 RGB 时会有个位数的偏差。**
**写 `0x336699` 出来是 `[49, 100, 151]`,同样差一两个数,这不是错,是色彩空间的来回转换。**
**③ 和 nullsrc 的对比**
项 nullsrc color=c=black
---------------------------------------------
Y 平面 0 16
U/V 平面 0 128
看着像 带绿味的黑 真正的黑
适合 画布、占位 黑幕、黑边
**要「黑幕」就用 color,要「空地」就用 nullsrc,别混。**
**④ 常见的三种颜色写法**
写法 例子
----------------------------------------
颜色名 black、white、red、blue
十六进制 0x336699
随机 random
**`c=random` 每帧换一个颜色,用来做闪色测试很方便,但压出来几乎不省字节——**
**每一帧都完全不同,编码器找不到可参考的前一帧。**
**⑤ 收个尾**
**7.4 这一节把「裁剪」和「凭空造画面」两件事讲完了。**
**crop 负责从有到有,nullsrc 与 color 负责从无到有。**
**再往后 7.5 起的倒放、翻转、填充,全是在这两件事上做叠加。**
三行输出对上本节那张表:[0, 0, 0]、[253, 0, 0]、[49, 100, 151]。
注意 c=black 的 Y 是 16,c=red 的 Y 是 81,都不是直觉里的数。
c=red 的 Y=81,是因为红色在 YUV 里亮度很低——
人眼对红不敏感,这一套系数延续了几十年,从黑白电视时代就定下来了。
代码里取 Y 用的是 planes[0] 的第一行最小值,因为纯色帧每一行都一样。
换成有内容的画面就不能这么取样了,得按行号乘 line_size 去切。
30 秒实验
- 试白色:把
c=black换成c=white,看 Y 是 235 还是 255。 - 试透明:写
[email protected],看 pix_fmt 会不会被自动改成 yuva420p。 - 试 random:写
c=random:d=2,数一下出来多少种颜色。
常见误区
- 拿 nullsrc 当黑幕。它带绿味,暗场能看出来。
- 以为 c=red 就是 255。实测 253,范围压缩的必然结果。
- 以为颜色写错会报错。写个不存在的名字,它可能悄悄当成别的色。
自测三问(点开对答案)
1. 为什么 c=red 出来是 253 而不是 255? —— yuv420p 是 16 到 235 的有限范围,来回转换会有几个数的偏差。 2. c=black 的 Y 平面值是多少? —— 16,这是有限范围里的黑。nullsrc 是 0,那是非法值。 3. c=random 压出来为什么省不了字节? —— 每帧颜色都不同,编码器找不到可参考的前一帧。- ☐ 7.4 完,下一节 7.5 视频倒放