RGB 说「怎么发光」,YUV 说「多亮 + 偏什么色」。 同一个颜色两种说法,换的是坐标,不是信息。
第十章把声音讲完了,这一章换到画面这头的「换尺寸与换颜色」。 像素格式那一堆名字,根子都在这两个模型上。
一、两种模型
一个颜色可以用两套坐标描述。 模型 三个分量 谁在用
RGB 红、绿、蓝三盏灯 屏幕、图片 YUV 亮度加两个色差 视频、编码
RGB 是「三个灯各开多亮」,很直观,所以屏幕与图片都用它。 YUV 是「整体多亮、往哪个颜色偏」,对人眼更贴合——因为人眼对亮度敏感、对色度迟钝。 这个生理事实是整个视频编码的基础:既然分不清那么细的颜色, 色度就存一半分辨率,数据量立刻降下来。
二、六个典型色的换算
实测值,不是公式推的。 颜色 RGB Y U V
红 255,0,0 81 90 240 绿 0,255,0 145 54 34 蓝 0,0,255 41 240 110 灰 128,128,128 126 128 128 黑 0,0,0 16 128 128 白 255,255,255 235 128 128
黑不是 Y=0,白也不是 255。 亮度只用 16 到 235 这一段, 两头各留一点余量,这叫 limited range。 为什么留余量:模拟信号时代要给同步头与过冲留空间; 数字时代沿用了这个约定,因为大量设备与素材都是这么存的。 灰那一行 U=V=128,说明「没有颜色」。所以 YUV 里的灰度图就是 U=V=128 的图。
三、换回来会削顶
从 YUV 回到 RGB,超出范围的部分被削掉。 Y(UV=128) 换回的 RGB
0 0,0,0 16 0,0,0 128 130,130,130 235 255,255,255 255 255,255,255
0 与 16 都变成纯黑,235 与 255 都变成纯白。
因为目标只有 0 到 255,装不下的就削。
这条解释了「同一段视频在不同播放器上明暗不同」:
有人按 limited 解、有人按 full 解,差的就是 16 与 235 这两个边界。
所以「颜色不对」的第一嫌疑不是编码,是范围标错了。
查的时候先看 color_range 是 0(未指定)还是 1(limited)。
四、只动色度会怎样
亮度固定 128,色度从 0 扫到 255。 U=V RGB 是什么颜色
0 0,255,0 绿 64 27,207,0 黄绿 128 130,130,130 灰 192 232,51,255 粉紫 255 255,0,255 品红
亮度一直没变,颜色跑了一圈。 这说明「Y 管明暗、UV 管颜色」这条分工是成立的,不是比喻。 这条对调色很有用:想改明暗只动 Y,想改色调只动 UV, 两者互不干扰——这就是 YUV 比 RGB 更适合做色彩处理的理由。
五、为什么视频偏爱 YUV
三条理由,第三条是根本。 理由 说明
省带宽 色度降采样,一个像素只要 1.5 字节 黑白兼容 只取 Y 就是灰度图 符合人眼 对亮度敏感、对色度迟钝
1.5 字节是怎么来的:亮度全存(1 字节),两个色差各存四分之一(0.25 + 0.25),合计 1.5。 这就是 4:2:0 的账。 换成 RGB 要 3 字节,同样画质下 YUV 只用一半——这就是 yuv420p 在视频里占绝对主流的原因。 代价是细节损失:色度只有一半分辨率,所以纯红纯蓝的边缘容易出现「糊边」, 这是 4:2:0 的固有代价。
六、动手 Demo:Demo 85:RGB 与 YUV 之间到底怎么换
六个典型色从 RGB 换到 YUV,再反过来看削顶,最后只动色度看颜色怎么跑。
import numpy as np
import av
def to_yuv(rgbv):
a = np.zeros((4, 4, 3), dtype=np.uint8)
a[:, :] = rgbv
f = av.VideoFrame.from_ndarray(a, format="rgb24")
y = f.reformat(format="yuv420p").to_ndarray()
return int(y[0, 0]), int(y[4, 0]), int(y[5, 0])
for v in ((255, 0, 0), (0, 255, 0), (0, 0, 255),
(128, 128, 128), (0, 0, 0), (255, 255, 255)):
print(v, to_yuv(v))
# 反过来:Y 从 0 到 255,看削顶
for y in (0, 16, 128, 235, 255):
f = av.VideoFrame(4, 4, "yuv420p")
for i, p in enumerate(f.planes):
p.update(bytes([y if i == 0 else 128]) * p.buffer_size)
print(y, f.reformat(format="rgb24").to_ndarray()[0, 0].tolist())
实跑输出:
--------------------------------------------------------------------
RGB 与 YUV 之间到底怎么换
--------------------------------------------------------------------
**① 两种模型各自管什么**
模型 三个分量 谁用
--------------------------------------------
RGB 红、绿、蓝三盏灯 屏幕、图片
YUV 亮度 + 两个色差 视频、编码
**RGB 是「怎么发光」,YUV 是「多亮 + 偏什么色」。**
**同一个颜色,两种描述方式,信息量一样。**
**② 六个典型色,从 RGB 换到 YUV**
颜色 RGB Y U V
------------------------------------------------
红 255,0,0 81 90 240
绿 0,255,0 145 54 34
蓝 0,0,255 41 240 110
灰 128,128,128 126 128 128
黑 0,0,0 16 128 128
白 255,255,255 235 128 128
**黑不是 Y=0,是 Y=**16**;白也不是 255,是 **235**。**
**这就是「limited range」**——亮度只用 16 到 235,两头留着余量。
**灰的那一行 U=V=128,说明「没有颜色」**。**所以 YUV 里「灰度图」就是 U=V=128 的图。**
**③ 从 YUV 换回 RGB**
Y(UV=128) 换回的 RGB
-----------------------------------
0 [0, 0, 0]
16 [0, 0, 0]
128 [130, 130, 130]
235 [255, 255, 255]
255 [255, 255, 255]
**Y=0 与 Y=16 都变成纯黑,Y=235 与 Y=255 都变成纯白。**
**因为目标范围只有 0 到 255,超出的部分被「削」掉了。**
**这条解释了「为什么同一段视频在不同播放器上明暗略有不同」**:
**有人按 limited range 解,有人按 full range 解,差的就是这 16 与 235 的边界。**
**④ 只动色度,看颜色怎么跑**
Y U V RGB
-------------------------------------------
128 0 0 [0, 255, 0]
128 64 64 [27, 207, 0]
128 128 128 [130, 130, 130]
128 192 192 [232, 51, 255]
128 255 255 [255, 0, 255]
**亮度一直没变,颜色却从绿跑到灰再跑到品红。**
**这说明「Y 管明暗、UV 管颜色」这条分工是成立的。**
**⑤ 视频为什么用 YUV 不用 RGB**
理由 说明
-----------------------------------------------------
省带宽 色度可以降采样,1.5 字节就够一个像素
黑白兼容 只取 Y 就是灰度图
人眼特性 对亮度敏感、对色度迟钝
**第三条是根本原因**:既然眼睛分不清那么细的颜色,
**色度就存一半分辨率——这就是 4:2:0 的来历,也是 yuv420p 占主流的理由。**
黑是 Y=16、白是 Y=235,不是 0 与 255,这是 limited range。 UV 落在 128 就是「没有颜色」,所以灰度图就是 U=V=128。
30 秒实验
- 把红色换成 (255,128,128),看 Y 与 U 怎么变(提示:亮度升、色度降)。
- 用
reformat(format="yuvj420p")再试一次,看黑白的 Y 变成几(提示:0 与 255)。 - 把
Y=100、U=200、V=100组合一下,猜它偏什么色再看答案。
常见误区
- 以为黑色是 Y=0。 limited range 里黑的 Y 是 16。
- 以为颜色不对一定是编码问题。 先查 color_range 有没有标错。
- 以为 YUV 无损。 4:2:0 的色度只有一半分辨率。
自测三问(点开对答案)
1. RGB 与 YUV 的本质区别是什么? —— RGB 按发光三原色分,YUV 按亮度与色差分。 2. limited range 里黑与白的 Y 各是多少? —— 黑 16、白 235。 3. 为什么视频用 YUV 而不是 RGB? —— 色度可以降采样,一个像素只要 1.5 字节,符合人眼特性。- ☐ (本节完,下一节 11.2 SWScale库结构简介)