去色有三条路:hue=s=0 最快、format=gray 最彻底、colorchannelmixer 最可控。 三条都能出灰,但灰的数值不一样。
7.11 动的是时间轴上的亮度,这一节动的是颜色。 去色本身很简单,难的是「灰得对不对」。
一、三条路
三个滤镜都能去色,但它们做的事不一样:
写法 做了什么 输出格式
hue=s=0 饱和度归零 还是 yuv420p format=gray 只留亮度平面 gray colorchannelmixer=… 按权重混三通道 看输入格式
hue=s=0 是最常用的一条:只改饱和度,其它一概不动,格式也不变,
接在别的滤镜后面毫无负担。
format=gray 更彻底——它把两个色度平面直接扔掉,只留亮度。
后面接只吃灰度输入的滤镜时用它,否则会因为多转一道而变慢。
二、实测三通道均值
去色后 R、G、B 三个均值应该相等,量一下就知道成没成:
写法 R G B 三通道相等
原图 145.5 140.2 140.2 不相等 hue=s=0 141.8 141.8 141.8 是 format=gray 142.2 142.2 142.2 是 colorchannelmixer 141.6 141.6 141.6 是
三条都出灰,但灰的数值差零点几:141.8、142.2、141.6。 差别来自算法:前者在 YUV 域把色度清零,中间那个扔掉色度再按 bt601 还原, 后者在 RGB 域按 0.3 / 0.4 / 0.3 的权重混。
0.3 / 0.4 / 0.3 是一组「看着舒服」的经验权重,不是标准的亮度公式。 标准公式是 0.299 / 0.587 / 0.114(bt601),用它出来的黑白更接近「真实明暗」。
三、去色不等于黑白照片
「黑白」在日常语境里包含两层:没有颜色、只剩明暗。
hue=s=0 只做前者:亮度曲线一点没动,原来亮的地方还是那么亮。
想做「老照片」那种质感,得在去色之后再压对比、降亮度、加颗粒:
ffmpeg -i s7.mp4 -vf "hue=s=0,lutyuv=y=val*0.85,noise=alls=12" old.mp4
lutyuv=y=val*0.85 把亮度整体压到 85%,noise=alls=12 加一层颗粒。
这两步合起来就有「胶片感」了,光去色只会得到一段灰扑扑的视频。
再加一步暗角(vignette)就更像:hue=s=0,vignette,lutyuv=y=val*0.85。
四、什么时候用哪条
按需求挑,别一律用 hue=s=0:
想要 用哪条
最快去色、格式不变 hue=s=0 后面接只吃灰度的滤镜 format=gray 要自己定权重(比如偏红) colorchannelmixer
第三条的实际用途是「模拟特定胶片的感光」: 把红的权重调高,天空会变暗、皮肤会变亮,出来的是「红滤镜黑白」的味道。 拍黑白风景时在镜头前加黄镜、橙镜,就是这个原理。
一个容易忽略的点:去色之后就没法再「只调某一个颜色」了。
想保留一点红色(比如只让红色物体有颜色),得用 colorchannelmixer 或者
hue=s=0 之后再拿 selectivecolor 补回来,顺序不能反。
五、动手 Demo:三条路的灰值对比
下面这段代码先量原图的三通道均值,再用三种写法各去一次色, 逐个量 R、G、B 是不是真的相等。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
BASE = [fr.to_ndarray(format=u"yuv420p") for fr in c0.decode(video=0)]
c0.close()
def run(filters, n=50):
g = av.filter.Graph()
b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
p = b
for name, args in filters:
f = g.add(name, args) if args else g.add(name)
p.link_to(f)
p = f
sk = g.add(u"buffersink")
p.link_to(sk)
g.configure()
out = []
for i in range(n):
vf = av.VideoFrame.from_ndarray(BASE[i % len(BASE)], format=u"yuv420p")
vf.pts = i
vf.time_base = TB
b.push(vf)
while True:
try:
out.append(sk.pull().to_ndarray(format=u"rgb24"))
except Exception:
break
b.push(None)
while True:
try:
out.append(sk.pull().to_ndarray(format=u"rgb24"))
except Exception:
break
return out
def mean3(a):
return [a[:, :, i].mean() for i in range(3)]
pure = av.VideoFrame.from_ndarray(BASE[0], format=u"yuv420p")
pure = pure.to_ndarray(format=u"rgb24")
print(u"--- 原图三通道 %s" % [u"%.1f" % v for v in mean3(pure)])
ways = ((u"hue=s=0", [(u"hue", u"s=0")]),
(u"format=gray", [(u"format", u"gray")]),
(u"colorchannelmixer",
[(u"colorchannelmixer", u".3:.4:.3:0:.3:.4:.3:0:.3:.4:.3")]))
for tag, fl in ways:
a = run(fl, 1)[0]
v = mean3(a)
same = max(v) - min(v) < 1.0
print(u" %-18s %s 三通道相等=%s"
% (tag, [u"%.1f" % x for x in v], same))
实跑输出:
--------------------------------------------------------------------
7.12 黑白效果
--------------------------------------------------------------------
**① 三条路都能去色,结果不完全一样**
**`hue=s=0`、`format=gray`、`colorchannelmixer` 三条写法都常见:**
写法 做了什么 输出格式
-----------------------------------------------------------
hue=s=0 饱和度归零 还是 yuv420p
format=gray 只留亮度平面 gray
colorchannelmixer=… 按权重混三通道 看输入格式
**② 实测:三通道均值是不是一样**
写法 R 均值 G 均值 B 均值 三通道相等
---------------------------------------------------------------------
原图 145.5 140.2 140.2 不相等
hue=s=0 141.8 141.8 141.8 是
format=gray 142.2 142.2 142.2 是
colorchannelmixer 141.6 141.6 141.6 是
**三种写法都出灰,但灰的数值不一样:`hue=s=0` 落 141.8,`format=gray` 落 142.2。**
**差这一点是因为前者在 YUV 里把色度清零,后者直接扔掉色度平面再按 bt601 还原。**
**③ 只去色不压亮,和真灰度是两件事**
**「黑白」在日常语境里包含两层:没有颜色、只剩明暗。**
**`hue=s=0` 只做前者:亮度曲线一点没动,原来亮的地方还是那么亮。**
**想做「老照片」那种,还得在去色之后再压对比、降亮度、加噪点:**
`ffmpeg -i s7.mp4 -vf "hue=s=0,lutyuv=y=val*0.85,noise=alls=12" old.mp4`
**`lutyuv` 改亮度、`noise` 加颗粒,这两步在 7.12 之后还会碰到。**
**④ 什么时候用哪条**
想要 用哪条
--------------------------------------------------
最快去色、格式不变 hue=s=0
后面还要接只吃灰度的滤镜 format=gray
要自己定权重(比如偏红) colorchannelmixer
**第三条的实际用途是「模拟特定胶片的感光」:**
**人眼对绿最敏感,权重写 `.3:.59:.11` 最接近真实明暗;**
**把红的权重调高,出来的黑白就偏「红滤镜」那种感觉。**
第一行是原图:R 145.5、G 140.2、B 140.2。G 与 B 相等不是巧合, 这片素材的主色是灰底加红条,除了红条之外三通道本来就接近。
后面三行三个数都相等,说明三种写法确实都去干净了色。 数值差零点几,就是三种算法在 YUV / RGB 之间来回转的细微差别。
判断写成 max(v) - min(v) < 1.0,不是写 ==——
整数相除的取整会让三个通道差 1,写死相等会误判成「没去干净」。
30 秒实验
- 换权重:把 mixer 改成
.299:.587:.114,看灰值往哪边偏。 - 只留红:把 mixer 写成
1:0:0:0:0:0:0:0:0,看是什么效果。 - 加质感:接上
lutyuv=y=val*0.85与noise=alls=12,量均值掉多少。
常见误区
- 以为去色就是黑白照片。亮度曲线没动,只是没了颜色。
- 用
==判断三通道相等。取整误差会差 1,要用容差。 - 先去色再想单独调某个颜色。颜色信息已经没了,补不回来。
自测三问(点开对答案)
1. 三条去色写法里哪条不改变格式? —— `hue=s=0`,输出还是 yuv420p。 2. 标准亮度权重是多少? —— 0.299 / 0.587 / 0.114(bt601),不是 0.3 / 0.4 / 0.3。 3. 为什么 `format=gray` 后面不能再调颜色? —— 色度平面已经被扔掉,颜色信息不存在了。- ☐ (本节完,下一节 7.13 模糊处理)