RGB 这三个字母,干我们这行的几乎每天都要碰上。做前端的调个按钮颜色,搞嵌入式的点个 RGB 灯,玩图像的读个像素值,甚至调个 PLC 触摸屏都得跟 RGB 打交道。但说实话,很多人对 RGB 的理解就停在"红绿蓝三个数"这个层面,真到用的时候——比如为什么屏幕上的红色跟打印出来的红色不一样、为什么 PWM 调光调着调着颜色就偏了、为什么 FPGA 里 RGB 转 TMDS 老是出不来图——就抓瞎了。这篇东西我打算把 RGB 从最基础的颜色对照,到它在各种硬件和软件场景里的实际玩法,系统地捋一遍。不管你是刚入门的新手,还是已经踩过几个坑的老手,应该都能从里面找到点有用的东西。
1. 先把 RGB 颜色对照表这件事说透
1.1 RGB 到底是什么,为什么是这三个通道
RGB 的本质是加色模型。你可以这么理解:在一个全黑的屋子里,你拿红、绿、蓝三盏灯往白墙上照,三盏灯都开到最亮,墙就是白的;三盏都关掉,就是黑的。这就是加色——光越加越亮。这跟 CMYK 那套减色模型(颜料越混越暗)是反过来的,所以屏幕用 RGB,印刷用 CMYK,这不是随便定的,是物理原理决定的。
每个通道的取值范围通常是 0 到 255,也就是 8 位。为什么是 8 位?因为一个字节就是 8 位,存起来方便,而且 256 级(0-255)的灰阶对人眼来说基本够用了——再多你也分辨不出来。三个通道各 8 位,合起来就是 24 位色,能表示 16777216 种颜色,也就是常说的"真彩色"。
那 RGB 颜色对照表是干嘛的?说白了就是一张颜色和数值的映射表。你告诉别人"我要一个珊瑚红",别人不知道你说的是哪种红,但你说"RGB(255, 127, 80)",那就唯一确定了。对照表的价值就在于消除歧义。
1.2 常用颜色对照表与十六进制换算
实际工作中,我们更常用十六进制来表示 RGB,因为写起来短。换算规则很简单:每个通道的十进制值转成两位十六进制,拼在一起,前面加个#。
比如珊瑚红 RGB(255, 127, 80):
- 255 → FF
- 127 → 7F
- 80 → 50
所以就是#FF7F50。
下面这张表是我平时用得比较多的基础色对照,建议收藏:
| 颜色名 | RGB 十进制 | 十六进制 | 典型用途 |
|---|---|---|---|
| 纯红 | 255, 0, 0 | #FF0000 | 警告、错误提示 |
| 纯绿 | 0, 255, 0 | #00FF00 | 成功、运行状态 |
| 纯蓝 | 0, 0, 255 | #0000FF | 链接、信息提示 |
| 白 | 255, 255, 255 | #FFFFFF | 背景、高亮 |
| 黑 | 0, 0, 0 | #000000 | 文字、边框 |
| 黄 | 255, 255, 0 | #FFFF00 | 注意、待处理 |
| 青 | 0, 255, 255 | #00FFFF | 辅助信息 |
| 品红 | 255, 0, 255 | #FF00FF | 调试标记 |
| 橙 | 255, 165, 0 | #FFA500 | 提醒 |
| 灰 | 128, 128, 128 | #808080 | 禁用状态 |
这里有个细节很多人不注意:十六进制里大小写其实无所谓,#ff7f50和#FF7F50是等价的,但团队协作时最好统一,不然代码 review 的时候看着乱。
1.3 从 RGB 到其他颜色空间的换算逻辑
实际项目里,RGB 往往不是终点,而是起点。比如你做图像处理,经常要把 RGB 转成 HSV 或者 HSL,因为这两个空间更符合人对颜色的直觉——你想"把这个颜色调亮一点",在 HSV 里改 V 就行,在 RGB 里你得三个通道一起动,很麻烦。
那 H 通道(色相)是怎么从 RGB 算出来的?这是热词里有人问的问题,我展开说一下。
假设 R、G、B 已经归一化到 0-1 之间,先找出最大值 max 和最小值 min,然后算差值 delta = max - min。
- 如果 delta = 0,说明三个通道一样,是灰色,H 无定义(通常设为 0)。
- 如果 max = R,H = 60 × ((G - B) / delta mod 6)
- 如果 max = G,H = 60 × ((B - R) / delta + 2)
- 如果 max = B,H = 60 × ((R - G) / delta + 4)
算出来的 H 在 0 到 360 度之间,对应色环上的角度。这个公式看着复杂,但逻辑很清晰:谁最大就以谁为基准,然后看另外两个通道的相对关系决定偏移量。
我举个实际例子。假设 RGB = (255, 127, 80),归一化后是 (1.0, 0.498, 0.314)。
- max = 1.0 (R),min = 0.314 (B),delta = 0.686
- 因为 max = R,用第一个公式:H = 60 × ((0.498 - 0.314) / 0.686 mod 6) = 60 × (0.268 mod 6) = 60 × 0.268 ≈ 16.1 度
16 度左右正好是橙红色,跟珊瑚红的直觉对得上。这个计算过程在写代码的时候一定要小心浮点精度,尤其是 delta 接近 0 的时候,容易出 NaN,得加个判断。
2. 用 Python 读取图片 RGB 值的完整实操
2.1 为什么选 Python,以及环境怎么搭
热词里"python 读取图片 rgb 值"排得很靠前,说明这是很多人的刚需。Python 干这事确实合适——库多、语法简单、调试快。核心就两个库:Pillow(处理常规图片)和OpenCV(处理复杂图像任务)。
装起来很简单:
pip install Pillow opencv-python numpy这里有个坑我得提前说:OpenCV 读进来的图片默认是 BGR 顺序,不是 RGB。这是历史遗留问题,因为早期 OpenCV 主要面向某些特定的图像采集设备。很多人第一次用 OpenCV 读图,发现红色和蓝色反了,就是栽在这上面。Pillow 则是老老实实的 RGB,所以如果你只是读个像素值,Pillow 更省心。
2.2 用 Pillow 读取单个像素和区域像素
先看最基础的,读某个坐标点的 RGB:
from PIL import Image img = Image.open('test.jpg') # 注意:Pillow 的坐标是 (x, y),x 是列,y 是行 pixel = img.getpixel((100, 200)) print(pixel) # 输出类似 (255, 127, 80)如果你要读一整块区域,比如做颜色统计,用crop加getdata效率更高:
from PIL import Image img = Image.open('test.jpg') # 裁剪出 (100, 200) 到 (200, 300) 的区域 region = img.crop((100, 200, 200, 300)) pixels = list(region.getdata()) # 算平均颜色 r = sum(p[0] for p in pixels) / len(pixels) g = sum(p[1] for p in pixels) / len(pixels) b = sum(p[2] for p in pixels) / len(pixels) print(f"平均颜色: ({r:.0f}, {g:.0f}, {b:.0f})")提示:
getpixel单点读取在循环里用会非常慢,因为每次都要走一次 Python 到 C 的调用。如果要点遍历整张图,一定要用getdata()或者直接转成 numpy 数组。
2.3 用 numpy 批量处理,速度提升几十倍
真正干活的时候,没人会一个像素一个像素读。标准做法是把图片转成 numpy 数组,然后向量化操作:
import numpy as np from PIL import Image img = Image.open('test.jpg') arr = np.array(img) # 形状是 (height, width, 3) print(arr.shape) # 比如 (1080, 1920, 3) # 取 (200, 100) 这个点的 RGB,注意 numpy 是 [行, 列] pixel = arr[200, 100] print(pixel) # array([255, 127, 80], dtype=uint8) # 算整张图的平均颜色 mean_color = arr.mean(axis=(0, 1)) print(mean_color) # 三个值,分别是 R、G、B 的平均这个写法比循环快几十倍不止。我之前做过一个项目,要统计几万张图片的主色调,用循环读像素跑了快两个小时,改成 numpy 之后几分钟就完事了。
2.4 处理透明通道和不同色彩模式
实际图片不一定是 RGB,可能是 RGBA(带透明通道)、L(灰度)、P(调色板)等等。直接读会出问题,得先转换:
from PIL import Image img = Image.open('test.png') print(img.mode) # 可能是 RGBA # 统一转成 RGB if img.mode != 'RGB': img = img.convert('RGB') arr = np.array(img)这里有个经验:PNG 带透明通道的时候,转 RGB 会把透明部分变成黑色(默认行为)。如果你不想要黑底,得先合成到一个白色背景上:
from PIL import Image img = Image.open('test.png').convert('RGBA') background = Image.new('RGB', img.size, (255, 255, 255)) background.paste(img, mask=img.split()[3]) # 用 alpha 通道做 mask arr = np.array(background)这个技巧在处理图标、logo 的时候特别有用,不然你统计出来的颜色全是黑的。
3. RGB 灯与 PWM 调光的硬件实战
3.1 RGB 灯珠的工作原理
RGB 灯本质上就是三个不同颜色的 LED 封装在一起,共阴或者共阳。共阴就是三个 LED 的负极接在一起接地,每个正极单独控制;共阳反过来。你给哪个通道通电,哪个颜色就亮,三个通道的亮度比例决定了最终颜色。
这里要区分两种灯:普通 RGB 灯和WS2812 这类可寻址灯。普通 RGB 灯是四根线(R、G、B、GND),你只能控制整条灯带同一个颜色。WS2812 是每颗灯珠内置了驱动芯片,一根数据线串起来,可以单独控制每一颗的颜色,做流水灯、彩虹效果就靠它。
3.2 PWM 调光的原理和频率选择
LED 的亮度不能靠调电压来实现——电压低了它直接不亮,而且颜色会偏。正确做法是PWM(脉冲宽度调制):快速地开关 LED,通过改变开的时间占总周期的比例(占空比)来调节视觉亮度。
人眼有视觉暂留,只要开关频率够高,看到的就是连续亮度。那频率选多少合适?
- 低于 100Hz:人眼能感觉到闪烁,尤其是余光看的时候更明显。
- 200Hz 到 1kHz:大部分场景够用,摄像头拍摄可能会有条纹。
- 1kHz 以上:基本无闪烁,但频率太高开关损耗增加,驱动电路要求也高。
我一般推荐1kHz 到 5kHz这个区间。太低会闪,太高对普通 MOS 管驱动来说没必要。
占空比和亮度的关系不是线性的,这点很关键。人眼对亮度的感知近似对数关系,所以如果你直接把占空比从 0 线性调到 100%,会感觉前段变化很快、后段几乎没变化。要做平滑的呼吸灯效果,得做伽马校正:
// 简化的伽马校正查表,输入 0-255,输出实际占空比 uint16_t gamma_table[256]; for (int i = 0; i < 256; i++) { float normalized = i / 255.0f; gamma_table[i] = (uint16_t)(powf(normalized, 2.2f) * 65535); }这个 2.2 就是伽马值,是显示领域的经验常数。加上校正之后,调光的手感会自然很多。
3.3 三通道独立调光的颜色偏移问题
调 RGB 灯最容易踩的坑就是颜色偏移。你设了 RGB(255, 128, 0) 想要橙色,结果出来偏黄或者偏红。原因有几个:
第一,三颗 LED 的亮度效率不一样。同样的电流下,红光 LED 通常比绿光、蓝光亮,因为它们的材料不同(红光用 AlInGaP,蓝绿光用 InGaN)。所以你需要做通道校准,给每个通道乘一个系数。
第二,PWM 分辨率不够。如果你用 8 位 PWM,低亮度时只有几个档位,颜色过渡会很粗糙。建议至少用 10 位或 12 位。
第三,电源压降。灯带长了之后,末端电压下降,蓝色 LED 因为正向压降最高(约 3.2V),最先受影响,会明显变暗。长灯带一定要两端供电或者多点注入。
校准的做法是:先让三个通道都输出同一个值(比如 128),用色度计或者摄像头测实际颜色,然后调整系数让它们看起来一致。没有专业设备的话,用手机拍一张,看 RGB 值也能大致校准。
4. FPGA 实现 RGB 转 TMDS 的要点
4.1 为什么需要 RGB 转 TMDS
HDMI 接口传的就是 TMDS(最小化传输差分信号)信号。FPGA 内部处理的是并行的 RGB 数据,要送到 HDMI 显示器,就得转成串行的 TMDS。这个过程包括编码和串行化两步。
TMDS 编码的核心目的是直流平衡和减少电磁干扰。简单说,就是让传输线上的 0 和 1 数量尽量均衡,这样平均电压稳定,接收端好判断。编码规则是:8 位数据经过异或或者同或运算,加上一位标志位,变成 9 位,再根据当前的不平衡度决定是否取反,最终变成 10 位。
4.2 编码器的实现逻辑
TMDS 编码分两个阶段。第一阶段是最小化跳变:统计 8 位数据里 1 的个数,如果超过 4 个,就用同或(XNOR),否则用异或(XOR),同时输出一个标志位。第二阶段是直流平衡:根据前面累计的 1 和 0 的差值,决定这 10 位要不要整体取反。
用 Verilog 实现的话,核心是一个组合逻辑块。我贴一段关键部分:
// 第一阶段:最小化跳变 always @(*) begin case (din[7:0]) 8'b00000000: begin q_m = 8'b11111110; dc_bias = 1'b0; end // ... 其他特殊值 default: begin // 统计 1 的个数 ones = din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7]; if (ones > 4 || (ones == 4 && din[0] == 0)) begin q_m[0] = din[0]; q_m[1] = ~(q_m[0] ^ din[1]); // ... 依次异或 dc_bias = 1'b0; end else begin q_m[0] = din[0]; q_m[1] = q_m[0] ^ din[1]; // ... 依次同或 dc_bias = 1'b1; end end endcase end这段代码看着繁琐,但逻辑是死的,照着规范写就行。真正容易出问题的是时序。
4.3 串行化和时钟域处理
编码完是 10 位并行数据,要串行化输出。HDMI 的像素时钟如果是 148.5MHz(1080p60),TMDS 时钟就是它的 10 倍,1485MHz。这个频率对 FPGA 的 IO 要求很高,一般用OSERDES(专用串行化器)来实现,而不是自己写移位寄存器。
时钟域的处理是重点。像素时钟域产生 RGB 数据,TMDS 时钟域负责串行输出,中间要做跨时钟域同步。我的经验是:编码后的 10 位数据先打一拍到 TMDS 时钟域,用异步 FIFO 或者简单的双缓冲,然后再送进 OSERDES。直接跨域会出亚稳态,表现为屏幕上随机出现彩色噪点。
还有一点,TMDS 的三条数据通道和一条时钟通道要严格等长,PCB 走线长度差控制在几个毫米以内,否则眼图会闭合,显示器直接不认。
5. 嵌入式平台上的 RGB 屏调试
5.1 RGB 屏黑屏的常见原因排查
热词里"ssd202 芯片 rgb 屏黑屏"是个典型问题。RGB 屏黑屏,排查思路要系统化,不能瞎试。我按概率从高到低列一下:
第一,时序参数不对。RGB 屏对时序极其敏感,包括行同步(HSYNC)、场同步(VSYNC)、前后肩(porch)、有效像素区。这些参数必须跟屏的规格书完全一致。差一个像素都可能黑屏或者花屏。常见错误是把前肩和后肩搞反了,或者把同步信号的极性设错了。
第二,时钟频率不对。像素时钟(PCLK)必须匹配屏的要求。比如 1024×600 的屏,典型 PCLK 是 51.2MHz 左右。频率低了会闪,高了直接不显示。
第三,背光没开。这个听起来很蠢,但真的很多人栽在这。RGB 屏的显示和背光是两套系统,你时序全对了,背光没使能,屏幕还是黑的。先量一下背光使能引脚的电平。
第四,电源和复位。屏的供电电压(通常是 3.3V 或 5V)要正常,复位信号要有时序。有些屏要求复位在电源稳定后延迟一段时间。
第五,数据线接反或者虚焊。尤其是自己画的板子,R、G、B 数据线顺序接错,会显示成奇怪的颜色而不是黑屏,但如果连时钟线都虚焊了,那就是全黑。
排查的时候,我习惯先用示波器量 PCLK、HSYNC、VSYNC 三个信号,确认它们有没有输出、频率对不对、极性对不对。这三个对了,再去看数据线。
5.2 RGB 到 MIPI DSI 的转换
现在很多屏用的是 MIPI DSI 接口,不是传统的 RGB 并口。如果你的主控只有 RGB 输出,就得加一颗转换芯片,比如某些专用的桥接芯片。
RGB 转 MIPI DSI 的关键点在于初始化序列。MIPI DSI 的屏在上电后,需要通过 DSI 命令通道发送一串初始化命令,配置屏的内部寄存器。这串命令通常由屏厂提供,是一堆十六进制的数据。你得把这些命令正确地通过 I2C 或者 SPI 写到桥接芯片里。
这里有个坑:初始化命令的时序。有些命令之间需要延时,比如发完复位命令要等 120ms 才能发下一条。如果你一股脑全发出去,屏可能初始化失败,表现为白屏或者花屏。我的做法是把初始化序列做成一个数组,每条命令带一个延时字段,用状态机逐条发送。
5.3 多光谱与 RGB 图像对齐的思路
热词里提到"御 3M 的 RGB 和多光谱对齐",这属于无人机遥感领域。多光谱相机和 RGB 相机是分开的,拍出来的图像有视差,要做对齐才能融合分析。
对齐的核心是配准。基本流程是:先做特征点提取(比如 SIFT 或者 ORB),然后在两幅图之间做特征匹配,算出单应性矩阵,最后用这个矩阵把多光谱图像变换到 RGB 图像的坐标系下。
难点在于多光谱图像和 RGB 图像的特征差异很大。多光谱的某些波段(比如近红外)人眼看不见,纹理特征跟 RGB 完全不一样,直接做特征匹配效果很差。实际做法通常是先降维,把多光谱数据合成一张灰度图,或者用某个跟 RGB 亮度相关性高的波段来做配准,配准完再把变换应用到所有波段。
另外,无人机的姿态变化会导致两幅图不只是平移,还有旋转和缩放,所以必须用能处理仿射或者投影变换的模型,不能用简单的平移配准。
6. 工业软件里的 RGB 应用
6.1 WinCC 中 C 脚本操作 RGB
在 WinCC 里做界面,经常需要动态改颜色。C 脚本里操作 RGB 有个固定的套路:
// WinCC 中设置对象背景色 long color = RGB(255, 127, 80); SetBackColor(lpszPictureName, "Rectangle1", color);注意 WinCC 的 RGB 宏定义顺序是R、G、B,但内部存储的时候可能是 BGR 或者别的顺序,这个跟具体版本有关。我遇到过设了红色显示蓝色的情况,后来发现是版本差异,得用SetBackColor之前先确认一下。
还有一个坑:颜色值的数据类型。WinCC 里颜色是 32 位整数,高 8 位可能是保留位或者 alpha 通道,实际有效的是低 24 位。如果你从外部读进来一个颜色值,记得做掩码& 0x00FFFFFF。
6.2 颜色在 HMI 设计中的可读性考量
做工业 HMI,颜色不只是好看,更重要的是可读性和安全性。有几个原则:
- 背景和文字对比度要够。深色背景配浅色文字,或者反过来。别搞个中灰背景配中灰文字,操作工在车间光线下一眼看不清。
- 报警色要统一。红色代表故障,黄色代表警告,绿色代表正常,这是行业惯例,别乱改。
- 色盲友好。大约 8% 的男性有红绿色盲,如果只靠红绿区分状态,这部分人就看不出区别。解决办法是颜色加形状或者文字,比如红色故障灯旁边加个"F"图标。
我见过一个项目,操作工把正常状态看成故障,就是因为那个绿色偏黄,跟黄色警告色太接近。后来把绿色调成了更纯的#00CC00,问题就解决了。
7. 几个容易混淆的 RGB 概念澄清
7.1 RGB 与 sRGB、Adobe RGB 的区别
很多人以为 RGB 就是 sRGB,其实不是。RGB 是一个颜色模型,sRGB 是一个具体的颜色空间标准。同样写 RGB(255, 0, 0),在 sRGB 里和在 Adobe RGB 里,实际显示出来的红色是不一样的。
sRGB 的色域比较窄,是当年为了兼容 CRT 显示器定的,但现在绝大多数屏幕默认都是 sRGB。Adobe RGB 色域更宽,主要用在印刷和专业摄影。如果你在 Adobe RGB 的屏幕上修图,导出成 sRGB 给客户看,颜色会变淡——因为超出 sRGB 色域的那部分颜色被裁掉了。
处理这个问题的关键是色彩管理:在图片里嵌入 ICC 配置文件,软件根据配置文件做转换。做跨设备颜色一致的项目,这一步不能省。
7.2 线性 RGB 与伽马校正 RGB
还有一个容易搞混的:线性 RGB和伽马校正 RGB。
线性 RGB 是指数值和实际光强成正比。伽马校正 RGB 是指数值经过了伽马编码,更符合人眼感知。我们平时说的 RGB(128, 128, 128) 是伽马校正后的,它对应的实际光强不是 50%,而是大约 21%。
这个区别在做图像混合、光照计算的时候特别重要。如果你直接把两张伽马校正的图做平均,结果会偏暗,因为你在非线性空间里做了线性运算。正确做法是先转成线性 RGB,算完再转回去。
import numpy as np def srgb_to_linear(c): c = c / 255.0 return np.where(c <= 0.04045, c / 12.92, ((c + 0.055) / 1.055) ** 2.4) def linear_to_srgb(c): c = np.clip(c, 0, 1) return np.where(c <= 0.0031308, c * 12.92, 1.055 * (c ** (1/2.4)) - 0.055) * 255这段代码我用了很多次,做图像融合、HDR 处理的时候是基础工具。别小看这个转换,不做的话,混合出来的颜色会明显发暗发灰。
7.3 RGB 与 YUV 的转换关系
视频领域用的是 YUV,不是 RGB。Y 是亮度,U 和 V 是色度。为什么要转?因为人眼对亮度敏感、对色度不敏感,所以可以降低色度的采样率来压缩数据,这就是 YUV420 的由来——色度只存四分之一。
RGB 转 YUV 的公式(BT.601 标准):
- Y = 0.299R + 0.587G + 0.114B
- U = -0.147R - 0.289G + 0.436B
- V = 0.615R - 0.515G - 0.100B
注意这些系数是加权的,绿色占的比重最大(0.587),因为人眼对绿光最敏感。这也解释了为什么 RGB 灯里绿色 LED 通常看起来最亮。
反过来 YUV 转 RGB:
- R = Y + 1.140V
- G = Y - 0.395U - 0.581V
- B = Y + 2.032U
做视频采集或者摄像头驱动的,这两个公式得背下来。而且要注意,不同标准(BT.601、BT.709、BT.2020)的系数不一样,用错了颜色会偏。标清用 601,高清用 709,4K 用 2020,这是规矩。
8. 我踩过的几个 RGB 相关的坑
8.1 颜色值溢出导致的显示异常
有一次做 LED 灯带控制,我算出来的颜色值超过了 255,直接写进了 PWM 寄存器,结果灯珠显示的颜色完全不对。原因是没有做钳位。RGB 三个通道在混合、叠加、调亮度之后,很容易超过 255 或者低于 0。
def clamp_rgb(r, g, b): return (max(0, min(255, int(r))), max(0, min(255, int(g))), max(0, min(255, int(b))))这个函数看着简单,但少了它,你的颜色计算就是不可靠的。尤其是做渐变、叠加效果的时候,一定要在最后一步钳位。
8.2 字节序问题:RGB 还是 BGR
前面提过 OpenCV 是 BGR,但这个问题比想象的更普遍。很多摄像头模组、显示驱动、图像格式,RGB 的顺序都不一样。我遇到过:
- OpenCV 读图:BGR
- 某些 Android 的 Bitmap:ARGB
- 某些 LCD 驱动 IC:RGB 但每个像素是 16 位(RGB565)
- 某些图像文件格式:存储时是 BGR
解决办法是在数据进入你的处理流程时,第一时间统一成一种顺序,并且写清楚注释。我现在的习惯是,所有内部处理都用 RGB,只在跟外部接口交互的时候做转换,转换的地方加醒目注释。
8.3 浮点精度导致的颜色偏差
做颜色空间转换的时候,浮点运算的精度问题会累积。比如 RGB 转 HSV 再转回 RGB,如果中间用了 float32,可能会有 1-2 个色阶的偏差。大部分场景无所谓,但如果你做的是颜色精确匹配或者无损往返转换,就得用 float64,或者干脆用整数运算。
整数运算的做法是把系数放大成整数,比如乘以 256 或者 1024,算完再右移。这样没有浮点误差,速度快,适合嵌入式。
// 整数版 RGB 转灰度 uint8_t gray = (77 * r + 150 * g + 29 * b) >> 8; // 77/256≈0.301, 150/256≈0.586, 29/256≈0.113,接近标准系数这种写法在单片机上很常见,比浮点快得多,精度也够用。
8.4 屏幕色域和实际颜色的差距
最后一个坑,也是最容易被忽略的:你代码里写的 RGB 值,跟屏幕上实际显示的颜色,可能差很远。
原因包括:屏幕的色域覆盖、出厂校准、亮度设置、环境光影响。同一张图,在手机上看和在显示器上看,颜色可能完全不同。
如果你的项目对颜色准确性有要求(比如医疗影像、印刷打样、商品展示),必须做色彩校准。用校色仪(比如 X-Rite 或者 Datacolor 的产品)对屏幕做校准,生成 ICC 配置文件,系统加载后颜色才准。
没有校色仪的话,至少做到:用同一台设备预览和最终输出,别在 A 电脑上调好颜色,到 B 电脑上发布。这个习惯能避免大部分颜色事故。
RGB 这东西,入门容易精通难。表面上是三个数字,背后牵扯到光学、电子、图像处理、色彩科学一大堆东西。我写这篇的初衷,就是把平时散落在各个项目里的经验集中起来,让后来的人少走点弯路。颜色对照表只是起点,真正重要的是理解每个数值背后的物理意义,以及它在你的具体场景里会怎么表现。多动手测,多拿设备量,比看一百篇文章都管用。