野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 27|回复: 0

[emXGUI] 在RGB565屏上实现RGB888屏的显示质量

[复制链接]
发表于 昨天 19:00 | 显示全部楼层 |阅读模式
本帖最后由 huiguang 于 2026-9-16 23:33 编辑

在RGB565屏上实现RGB888屏的显示质量
RGB888 → RGB565 量化抖动:一种基于视觉感知的实现

在现代 UI 设计中,渐变色早已不是点缀,而是塑造品牌质感、传递产品调性的核心语言。从系统灵动背景到车载 HMI 沉浸式界面,渐变让冷冰冰的屏幕拥有了温度与层次。
但硬件选型的现实,让不少嵌入式与 UI 开发者陷入两难:选用RGB888 屏,色彩细腻过渡自然,却意味着更高的成本、更大的功耗开销、更慢的刷新帧率;选用 RGB565 屏,成本与功耗更低、交互更流畅——可一旦用上渐变色,量化伪影立刻暴露无遗。

基于视觉感知的实现方案并非简单的位深截断或传统抖动算法,而是基于人眼视觉感知模型与色彩空间特性,重新设计量化误差的扩散与分布策略,从而抑制 RGB888 到 RGB565 有损量化过程中不可避免的视觉伪影。
实际效果:在 RGB565 屏上,渐变过渡不再出现明显的色阶断层,低饱和区不再偏绿,色彩基调与整体观感更接近原始 RGB888 图像。

一、问题背景:为什么RGB565 会出现伪影
RGB565 用 16 bit表示一个像素:R 占 5 bit、G 占 6 bit、B 占 5 bit。相比 RGB888 每通道 8 bit,量化到 RGB565 相当于对每个通道做低位截断。最直接的做法是右移舍入(truncation / 四舍五入),但这会引入确定性的量化误差:
量化误差 = 原始值 − 量化后重建值
在色彩平缓过渡的区域(渐变、阴影、低饱和度肤色等),这个误差在空间上高度相关,人眼会把相邻的同色误差“连成一片”,就看到了所谓的色阶断层(banding)。而 G 通道多 1 bit、R/B 少 1 bit 的不对称,又会让低亮度区域整体偏向绿色——这正是 RGB565 渐变最常见的两类伪影。

二、传统方案及其局限
截断 / 四舍五入
实现最简单,误差全部“丢弃”,在渐变区必出断层。

Floyd–Steinberg 误差扩散
把当前像素的量化误差按比例扩散到右侧、下方邻近像素,用空间噪声“打散”色块,是印刷/图像领域经典的做法。
伪代码:
for each pixel (x, y):
    old = image[x, y]
    new = quantize(old)          # 量化为 RGB565 可表示的最近色
    error = old - new
    image[x+1, y  ] += error * 7/16
    image[x-1,y+1] += error * 3/16
    image[x , y+1] += error * 5/16
    image[x+1, y+1] += error * 1/16

FS 抖动确实能消除大面积断层,但直接搬到 RGB565 上仍有不足:
① 它在线性 RGB 空间均匀扩散误差,而人眼对亮度(尤其是暗部)的敏感度是非线性的;
② R/B 与 G 的位深不同,统一扩散系数会放大 G 通道的视觉残留;
③ 对低饱和度、接近灰阶的区域不够敏感,容易残留偏色。

三、改进思路:面向视觉感知的量化抖动

核心思想:不追求“数值误差最小”,而追求“视觉误差最小”。具体从四个方向改进。
1. 转换到感知更均匀的色彩空间
先把 RGB 转到 CIE Lab 或 YUV/YCoCg 空间再量化、扩散。Lab 的 L* 通道近似与人眼亮度感知成线性,在 L* 上做误差扩散,可以避免暗部被过度量化、亮部浪费精度的问题。

2. 按通道视觉权重分配误差
利用人眼对绿通道最敏感、对蓝最不敏感的特性,为 R/G/B 的量化误差设置不同权重。例如对亮度(Luma)误差赋予更高优先级,让有限的 16 bit 位宽优先保证明暗过渡平滑。

3. 误差扩散系数的自适应化
根据局部梯度与饱和度动态调整扩散强度:在平缓渐变区加强扩散以打碎色块,在纹理/边缘区减弱扩散以保护细节、避免“脏噪点”。

4. 误差能量再分配
根据CSF(Contrast Sensitivity Function,对比敏感度函数)模型, 将误差能量从敏感频段搬迁到不敏感频段, 改善人眼视觉质量。

四、实现参考(C 语言,适用于嵌入式 GUI / LVGL 离线转换)
#defineR5(v)  (((v) >> 3) & 0x1F)   // 8bit -> 5bit
#defineG6(v)  (((v) >> 2) & 0x3F)   // 8bit -> 6bit
#defineB5(v)  (((v) >> 3) & 0x1F)

// 单像素量化:对误差按感知权重扩散(示意,非完整实现)
voidquantize_pixel(uint8_t *r, uint8_t *g, uint8_t *b, float err[3]) {
    float er = (float)*r * 0.299f, eg =(float)*g * 0.587f, eb = (float)*b * 0.114f;
    *r = (R5(*r + (int)err[0]) << 3) |0x07;  // 重建并补低位
    *g = (G6(*g + (int)err[1]) << 2) |0x03;
    *b = (B5(*b + (int)err[2]) << 3) |0x07;
    // 将剩余误差按7/16、3/16、5/16、1/16 传播给邻域像素
}

五、参数与适配要点

抖动效果与目标屏的特性强相关,实际落地建议关注几点:
• Gamma 校正:面板的实际灰阶响应通常是非线性的,量化前应先做 Gamma 解码、量化后再编码回,否则暗部色阶会明显加重;
• 观察距离与分辨率:小尺寸、高 PPI 或观看距离较远的屏,人眼对细粒度误差不敏感,可适当降低扩散强度以节省算力;
• 色温 / 白平衡:若面板本身偏色,可在量化阶段对 R/B 通道做轻微偏移补偿,缓解偏绿;
• 离线 vs 实时:静态资源(图标、背景)建议离线预量化,运行时零开销;动态 UI 若需实时量化,可采用查表(LUT)+ 有序抖动以降低 CPU/GPU 占用。

六、效果验证:五组典型场景对比

下面用五组对 RGB565 最“不友好”的场景做对比:
左为 RGB888 原图,中为采用上述感知抖动方法后的 RGB565 结果,右为 LVGL 默认的 RGB565 量化抖动,用以观察不同方法在色阶、偏色、跨色过渡上的差异。
(图片可点击放大查看细节)
compare_page_2x.JPG




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2026-9-17 17:10 , Processed in 0.114086 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表