ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RGB颜色对照表与实战:从Python读取到FPGA、嵌入式及工业应用

RGB颜色对照表与实战:从Python读取到FPGA、嵌入式及工业应用 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 → FF127 → 7F80 → 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 RH 60 × ((G - B) / delta mod 6)如果 max GH 60 × ((B - R) / delta 2)如果 max BH 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 读取单个像素和区域像素先看最基础的读某个坐标点的 RGBfrom 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], dtypeuint8) # 算整张图的平均颜色 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, maskimg.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 转 TMDSHDMI 接口传的就是 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]) 8b00000000: begin q_m 8b11111110; dc_bias 1b0; 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 1b0; end else begin q_m[0] din[0]; q_m[1] q_m[0] ^ din[1]; // ... 依次同或 dc_bias 1b1; end end endcase end这段代码看着繁琐但逻辑是死的照着规范写就行。真正容易出问题的是时序。4.3 串行化和时钟域处理编码完是 10 位并行数据要串行化输出。HDMI 的像素时钟如果是 148.5MHz1080p60TMDS 时钟就是它的 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.114BU -0.147R - 0.289G 0.436BV 0.615R - 0.515G - 0.100B注意这些系数是加权的绿色占的比重最大0.587因为人眼对绿光最敏感。这也解释了为什么 RGB 灯里绿色 LED 通常看起来最亮。反过来 YUV 转 RGBR Y 1.140VG Y - 0.395U - 0.581VB Y 2.032U做视频采集或者摄像头驱动的这两个公式得背下来。而且要注意不同标准BT.601、BT.709、BT.2020的系数不一样用错了颜色会偏。标清用 601高清用 7094K 用 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 的 BitmapARGB某些 LCD 驱动 ICRGB 但每个像素是 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 这东西入门容易精通难。表面上是三个数字背后牵扯到光学、电子、图像处理、色彩科学一大堆东西。我写这篇的初衷就是把平时散落在各个项目里的经验集中起来让后来的人少走点弯路。颜色对照表只是起点真正重要的是理解每个数值背后的物理意义以及它在你的具体场景里会怎么表现。多动手测多拿设备量比看一百篇文章都管用。
返回列表