名字后面的数字是每像素位数:16 位省一半空间,24 位是真彩色,32 位多一个字节。 位数少的代价不是「模糊」,是「色带」。
上一节只用了 rgb24 一种。这一节把 555、565、32 位都试一遍,看字节与颜色数怎么变。 这几个格式在采集卡、老显卡、嵌入式屏上很常见。
一、名字里的数字指的是位深
四种格式各裸存一份,字节与位深如下:
像素格式 总字节 每像素位数
rgb555 1440000 16 rgb565 1440000 16 rgb24 2160000 24 rgb32 2880000 32
第三列不是照名字抄的,是把总字节除以「像素数×帧数」再乘 8 算出来的。 所以能看出一个反直觉的地方:555 的名字里三个 5 加起来只有 15,实际占 16 位。
多出来的那一位不用来存颜色,只用来凑齐两个字节。 同理 rgb32 的第 4 个字节多数时候是占位的,少数格式才拿它存透明度。
二、位数少了,丢的是颜色档数
每通道几位,就决定了每一档能分多细:
格式 每通道档数 能表示的颜色数
rgb555 5/5/5 位 32768 rgb565 5/6/5 位 65536 rgb24 8/8/8 位 16777216
第二行是 565 的特殊设计:绿色多给一位。 因为人眼对绿最敏感,同样少一位,蓝红掉了不太看得出来,绿掉了就很明显。
第三行的 16777216 就是常说的「1677 万色」,也就是 2 的 24 次方。 这个数字到今天还是「真彩色」的标准说法,虽然它已经是三十年前的定义了。
三、什么时候该用哪个
按场景选,不按「越大越好」选:
场景 建议 理由
存档、后期处理 rgb24 不差这一倍空间 嵌入式屏、老设备 rgb565 显存按位算,省一半 要透明度 rgba 第 4 个字节存 alpha 给 GPU 上传纹理 rgb32 四字节对齐最快
第三行提醒一件事:真正的透明度要用 rgba,不是 rgb32。
名字像,但 rgb32 的第 4 字节在多数实现里是不参与显示的。
低色深最典型的毛病是「色带」:平滑渐变会变成一级一级的台阶。 在天空、人脸高光这些大面积渐变上最容易看出来。
四、动手 Demo:四种 RGB 各存一份,反算每像素位数
把同一批帧分别按 rgb555、rgb565、rgb24、rgb32 裸存,比较字节。 然后用「总字节 ÷ 像素总数 × 8」反算位深,看与名字里的数字对不对得上。
import av, os
for pf in (u"rgb555", u"rgb565", u"rgb24", u"rgb32"):
fn = pf + u".rgb"
c1 = av.open(fn, u"w", format=u"rawvideo")
s1 = c1.add_stream(u"rawvideo", rate=25)
s1.width, s1.height, s1.pix_fmt = 160, 90, pf
c0 = av.open(u"s5.mp4")
n = 0
for f in c0.decode(video=0):
n += 1
for o in s1.encode(f.reformat(width=160, height=90, format=pf)):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
sz = os.path.getsize(fn)
print(u"%s %8d 字节 %.0f 位/像素" % (pf, sz, sz * 8.0 / (160 * 90 * n)))
实跑输出:
--------------------------------------------------------------------
5.2.4 RGB16/RGB24/RGB32——位深与排列
--------------------------------------------------------------------
**① 四种 RGB 各存一份**
像素格式 总字节 每像素位数
------------------------------------
rgb555 1440000 16
rgb565 1440000 16
rgb24 2160000 24
rgb32 2880000 32
**第三列是把总字节除以「像素数×帧数」再乘 8 算出来的,不是照名字抄的。**
**555 与 565 都是 16 位(2 字节)——名字里的三位数字加起来只有 15,**
**多出来的那一位是留给透明的,四个格式的位深其实是 16/16/24/32。**
**② 位数少了,丢的是「颜色的档数」**
格式 每通道档数 能表示的颜色数
-----------------------------------------
rgb555 5/5/5 位 32768
rgb565 5/6/5 位 65536
rgb24 8/8/8 位 16777216
**第二行是 565 的特殊之处:绿色多给一位,因为人眼对绿最敏感。**
**第三行 16777216 色就是「真彩色」的由来——2 的 24 次方。**
**所以低色深格式的代价不是「模糊」而是「色带」:渐变会变成一条条台阶。**
rgb555 在 FFmpeg 里是 16 位存的,高 1 位不用。
所以它的位深与 rgb565 一样,都是 16,字节数也就完全一样。
rgb32 是 4 字节但只有 24 位有效,第 4 字节多半是 0。
它换来的好处是对齐:四字节一个像素,GPU 与 SIMD 处理起来更顺。
最后那个除法一定要除以帧数。 忘了除,算出来的位深会变成几百,一眼就知道算错了。
30 秒实验
- 看色带:把一张渐变图转成 rgb565 再转回来,放大看台阶。
- 看对齐:
-pix_fmt rgb0与rgb32各转一遍,字节一样,第 4 字节含义不同。 - 命令行指定:
-pix_fmt rgb565le注意字节序,小端与大端的区别在这时就显出来了。
常见误区
- 以为 555 是 15 位。它占 16 位,多的一位不用,字节数按 2 字节算。
- 把 rgb32 当 rgba 用。第 4 字节多数实现不参与显示,要透明度得用 rgba。
- 以为降位深只会「稍微差一点」。大面积渐变上的色带非常显眼。
自测三问(点开对答案)
1. rgb555 与 rgb565 的字节数为什么一样? —— 因为两者都按 16 位存储,差别只在 16 位怎么分配给三个通道。 2. 为什么 565 给绿色多一位? —— 人眼对绿色最敏感。同样少一位,绿通道掉档比红蓝更容易被看出来。 3. rgb32 的第 4 个字节通常存什么? —— 多数情况什么都不存,只为了四字节对齐;要透明度应该用 rgba。- ☐ (本节完,下一节 5.2.5 利用 FFmpeg 提取音频的 PCM)