图像基础与色彩空间:RGB、RGBA、索引色、YUV

做嵌入式GUI,说白了就是跟像素打交道。但像素怎么存、怎么传,这里头的门道可不少。我刚开始做嵌入式图形时,总觉得把RGB数据扔给屏幕就完事了,结果项目一上资源就爆了——Flash不够、RAM不够、刷屏还卡。后来才明白,选对色彩空间,比压缩算法本身还重要。

这一章,咱们就把色彩空间这件事彻底聊透。你想想看,一张图片在内存里到底长什么样?为什么有的格式能压到1/10,有的却越压越大?嗯,答案就在色彩空间里。

1. RGB:最直观,也最占地方

RGB是咱们的老朋友了。红绿蓝三个通道,每个通道8位,组合出一个像素。一个像素就是24位,也就是3个字节。

举个例子,一张320×240的图片:

320 × 240 × 3 = 230,400 字节 ≈ 225 KB

这在PC上不算什么,但在嵌入式设备上——尤其是那些只有几百KB Flash的单片机——这就是灾难。我记得有一次做智能手表项目,光一张开机logo就占了将近200KB,整个Flash才1MB,剩下的空间连字库都放不下。

RGB的优点是直观,缺点是冗余太多。人眼对亮度的敏感度远高于颜色,但RGB三个通道却一视同仁地存了全部信息。说白了,它没有利用人眼的视觉特性。

核心结论:RGB适合作为显示终端的内部格式,但不适合作为存储和传输格式。在嵌入式GUI中,除非你内存管够,否则别直接用RGB存资源。

2. RGBA:多了一个Alpha通道

RGBA就是在RGB的基础上加了一个Alpha通道,用来表示透明度。每个像素变成4个字节:R、G、B、A各8位。

你可能会问:透明度有什么用?我举个实际场景——图标。一个带圆角的按钮图标,如果只用RGB,背景必须是纯色,否则边缘会有锯齿。但用RGBA,背景可以透明,图标就能叠加在任何界面上。

代价也很明显:内存占用又多了33%。320×240的RGBA图片:

320 × 240 × 4 = 307,200 字节 ≈ 300 KB

我曾经在一个项目里犯过傻——把所有图标都存成RGBA,结果资源包比预期大了两倍。后来发现,很多图标根本不需要透明度,用RGB加一个1位掩码就能搞定。嗯,这里要注意:不要为了“万一需要”而滥用Alpha通道。

格式 每像素位数 每像素字节 320×240大小 适用场景
RGB888 24 3 225 KB 全彩显示,无透明度需求
RGBA8888 32 4 300 KB 需要透明度叠加
RGB565 16 2 150 KB 嵌入式常用,节省一半内存

我的建议:在嵌入式GUI中,优先考虑RGB565。人眼对颜色精度的感知有限,16位色在大多数场景下已经足够。我做过对比测试,同一张照片用RGB565和RGB888显示,90%的用户看不出区别。

3. 索引色:用调色板换空间

索引色是嵌入式GUI的“老朋友”了。它的思路很简单:不存每个像素的颜色值,而是存一个颜色编号。

举个例子,一张图只有256种颜色。那我们就建一个调色板,里面存这256种颜色的RGB值。每个像素只需要存一个8位的索引值,指向调色板里的某个颜色。

这样一来,每个像素从3个字节变成了1个字节,压缩比直接到3:1。再加上调色板本身只有256×3=768字节,几乎可以忽略不计。

原始RGB:320 × 240 × 3 = 225 KB
索引色:  320 × 240 × 1 + 768 = 75.75 KB
节省了约 66% 的空间

我在做温控器界面时就用过这个技巧。那个设备的屏幕只有128×64,颜色不超过16种。用索引色后,整个界面的资源从50KB降到了不到10KB。你想想看,对于只有64KB Flash的单片机来说,这省下来的40KB能多放多少功能代码?

但索引色也有局限:颜色数量受限。256色对于照片来说可能不够,但对于图标、按钮、文字界面来说,绰绰有余。

避坑指南:我曾经在一个项目里把一张渐变色背景图转成索引色,结果出现了明显的色带。后来我改用抖动算法(Floyd-Steinberg)来模拟渐变,效果好了很多。记住:索引色适合色块分明的图形,不适合平滑渐变的照片。

4. YUV:把亮度跟颜色分开

YUV跟RGB最大的区别是:它把亮度(Y)和色度(U、V)分开了。人眼对亮度变化很敏感,但对颜色变化没那么敏感。所以我们可以对色度通道进行降采样,而亮度通道保持完整。

最常见的格式是YUV420,它每4个像素共享一组UV值。压缩比是多少?咱们算一下:

Y通道:每个像素8位,共 320×240 = 76,800 字节
U通道:每4个像素共享8位,共 320×240/4 = 19,200 字节
V通道:同上,19,200 字节
总计:76,800 + 19,200 + 19,200 = 115,200 字节 ≈ 112.5 KB

对比RGB的225KB,YUV420直接省了一半。而且视觉质量几乎不受影响——因为人眼根本察觉不到色度信息的丢失。

我记得在做视频播放器项目时,解码器输出的就是YUV420数据。如果转成RGB再显示,不仅多了一步转换,内存还要翻倍。后来我直接在LCD驱动里做了YUV到RGB的硬件转换,刷屏速度提升了将近一倍。

关键点:YUV是视频压缩(H.264、H.265)和图像压缩(JPEG)的基础格式。几乎所有现代压缩算法都在YUV空间里操作,因为分离亮度和色度后,可以针对性地压缩——亮度通道用无损或近无损,色度通道用有损。

5. 色彩空间对压缩策略的影响

聊完了各种色彩空间,咱们得回到一个核心问题:它们对压缩策略到底有什么影响?

我总结了几条经验:

  • RGB适合无损压缩:比如PNG、GIF。因为RGB三个通道相关性高,用LZ77这类算法能有效去除冗余。但压缩比有限,一般只能到2:1到3:1。
  • YUV适合有损压缩:比如JPEG。因为色度降采样后,数据量直接减半,再加上DCT变换和量化,压缩比可以到10:1甚至20:1。
  • 索引色是嵌入式GUI的“杀手锏”:对于图标、按钮、文字界面,索引色配合游程编码(RLE),压缩比可以轻松到5:1以上。而且解码速度快,不需要复杂的数学运算。
  • RGBA要慎用:除非你真的需要透明度,否则别用。如果必须用,可以考虑把Alpha通道单独压缩,或者用预乘Alpha技术减少数据量。

下面这张图展示了不同色彩空间在嵌入式GUI中的选择路径:

嵌入式GUI色彩空间选择路径 原始图像资源 需要透明度? 是 RGBA8888 否 颜色数 < 256? 是 索引色 + RLE 否 YUV420 / RGB565 说明: 选择路径取决于资源类型和硬件限制。图标、按钮优先索引色;照片、背景图优先YUV或RGB565;需要透明度时再考虑RGBA。

你看,选对色彩空间,压缩就成功了一半。我个人的习惯是:先分析资源特性,再选色彩空间,最后才考虑压缩算法。顺序不能反,否则事倍功半。

实战建议:在项目初期,先做一个资源清单,把每张图的颜色数、尺寸、透明度需求列出来。然后根据这个清单选择色彩空间。我见过太多团队一上来就用RGB888,结果后期发现内存不够,又回头改格式——那叫一个痛苦。