调过屏、点过灯、处理过图片的人电脑里几乎都存着一张颜色对照表。我自己最早那张表是从旧论坛上复制下来的表格里只有颜色名和十六进制码后来做单片机LCD驱动、给WS2812灯条写特效、用Python批量抽帧取色才发现光有十六进制码根本不够——很多场景要8位RGB三通道值很多屏要16位RGB565打包值还有16位每通道的PNG图给的值又变成0到65535。这三个东西搞混轻则显示偏色重则整屏发黑。这篇文章就把RGB值在8位、16位这些常见形态下的对应关系整理全附上可以直接抄的速查表、手动换算方法还有我踩过的几个坑。1. 8位和16位这两个词经常指的不是同一件事很多初学者看到“8位颜色、16位颜色”就直接懵了因为这两个词在不同场景下含义完全不同。我建议先把概念拆开不然后面查表都会查错。1.1 通道位宽和整像素位宽是两套体系第一套体系说的是“每个颜色通道用几位来表示”。RGB三个通道各占8位就是最常见的8位每通道每个通道的取值范围是0到255三个通道组合起来叫24位真彩色能表示1677万多种颜色。网页里的#FF00FF、Photoshop里RGB滑块的0到255、Python读出来的像素值全都是这套体系。第二套体系说的是“一个像素总共占几位”。比如RGB565一个像素一共16位红色占5位、绿色占6位、蓝色占5位这种格式在单片机屏幕、FPGA、嵌入式GUI里极其常见。它也叫16位色和“每通道16位”完全是两码事。第三套体系是“每通道16位”也就是R、G、B每个通道都占16位取值范围0到65535。这种格式在PNG图片、RAW照片、HDR图像、医学影像里会遇到用来保留更高精度的颜色过渡。所以你搜“RGB值 8位 16位”的时候得先搞清楚自己到底要哪种驱动屏幕要用RGB565处理图像文件要分清楚是8位每通道还是16位每通道调LED灯条又是另外一套位宽逻辑。1.2 一个像素到底占几个字节我用一张表把这几种常见格式占用的空间列一下这样你对“位宽”会有更直观的感受。格式每像素位数每像素字节数通道取值典型场景8位索引色8 bit1 字节0-255查调色板GIF、老游戏、终端RGB56516 bit2 字节R0-31, G0-63, B0-31MCU屏幕、嵌入式GUIRGB88824 bit3 字节每通道0-255网页、普通PNG/JPGRGBA888832 bit4 字节每通道0-255游戏贴图、UIRGB4848 bit6 字节每通道0-6553516位PNG、RAW1.3 为什么RGB565里绿色多给了一位人眼对绿色最敏感对蓝色最不敏感。RGB565把绿色分到6位能在只有65536种颜色的限制下最大程度减少人眼能感知的色阶断层。这是从CRT时代就定下来的老规矩一直沿用到现在。记住这个“绿多一位”的规律后面手动换算时就不容易忘。2. 高频颜色速查表十六进制、8位RGB、RGB565三列对照下面这些颜色是我在项目和日常处理图片时最常用到的按色系整理。中间一列的8位RGB值给Python、C语言、网页用最后一列的RGB565值可以直接写进MCU屏幕驱动或图形库的缓冲区。2.1 灰度、黑白和红色系颜色名Web十六进制8位RGB0-255RGB5650x????黑 Black#0000000, 0, 00x0000白 White#FFFFFF255, 255, 2550xFFFF银 Silver#C0C0C0192, 192, 1920xC618灰 Gray#808080128, 128, 1280x8410暗灰 DimGray#40404064, 64, 640x4208红 Red#FF0000255, 0, 00xF800深红 DarkRed#8B0000139, 0, 00x8800栗色 Maroon#800000128, 0, 00x8000粉红 Pink#FFC0CB255, 192, 2030xFE19珊瑚 Coral#FF7F50255, 127, 800xFBEA番茄红 Tomato#FF6347255, 99, 710xFB08火砖红 FireBrick#B22222178, 34, 340xB104棕色 Brown#A52A2A165, 42, 420xA145橙色 Orange#FFA500255, 165, 00xFD20橙红 OrangeRed#FF4500255, 69, 00xFA20金色 Gold#FFD700255, 215, 00xFEA02.2 黄绿、蓝紫和常用特殊色颜色名Web十六进制8位RGB0-255RGB5650x????黄 Yellow#FFFF00255, 255, 00xFFE0黄绿 YellowGreen#9ACD32154, 205, 500x9E66橄榄 Olive#808000128, 128, 00x8400酸橙 Lime#00FF000, 255, 00x07E0绿 Green#0080000, 128, 00x0400海绿 SeaGreen#2E8B5746, 139, 870x2C4A浅绿 LightGreen#90EE90144, 238, 1440x9772春绿 SpringGreen#00FF7F0, 255, 1270x07EF蓝绿 Teal#0080800, 128, 1280x0410青 Cyan#00FFFF0, 255, 2550x07FF淡青 LightCyan#E0FFFF224, 255, 2550xE7FF军蓝 CadetBlue#5F9EA095, 158, 1600x5CF4蓝 Blue#0000FF0, 0, 2550x001F深蓝 DarkBlue#00008B0, 0, 1390x0011海军蓝 Navy#0000800, 0, 1280x0010道奇蓝 DodgerBlue#1E90FF30, 144, 2550x1C9F皇家蓝 RoyalBlue#4169E165, 105, 2250x435C天蓝 SkyBlue#87CEEB135, 206, 2350x867D钢蓝 SteelBlue#4682B470, 130, 1800x4416靛蓝 Indigo#4B008275, 0, 1300x4810紫 Purple#800080128, 0, 1280x8010深紫 DarkViolet#9400D3148, 0, 2110x901A蓝紫 BlueViolet#8A2BE2138, 43, 2260x895C洋红 Magenta#FF00FF255, 0, 2550xF81F兰花紫 Orchid#DA70D6218, 112, 2140xDB9A薰衣草 Lavender#E6E6FA230, 230, 2500xE73F2.3 另一种16位每通道0到65535怎么换算如果你拿到的是16位每通道的PNG或RAW图你需要的就不是RGB565而是把0到255的8位值等比扩展到0到65535。换算公式很简单16位值 8位值 × 257等价于(8位值 8) | 8位值为什么是257因为255对应6553565535除以255正好等于257。用位运算理解更直接把一个8位值同时放在高8位和低8位就完成了线性扩展。举三个例子#FF0000的红色通道255 × 257 65535绿色和蓝色都是0得到(65535, 0, 0)#FFA500的绿色通道165 × 257 42405橙色变成(65535, 42405, 0)#404040的灰色64 × 257 16448三个通道都是164483. 256色调色板8位索引色到底按什么规律排的除了R、G、B各8位的真彩色还有一种“8位颜色”指的是8位索引色整张图只有256个颜色编号每个编号对应调色板里一个具体的RGB值。GIF格式、老式游戏机、部分终端界面都用它。既然标题写了“比较全”这块也得说清楚。3.1 216安全色6×6×6的色彩立方体Web安全色本质上就是一个256色调色板的子集也叫216安全色。它的排列规律非常整齐RGB三个通道都只取6个固定值分别是0、51、102、153、204、255也就是十六进制的00、33、66、99、CC、FF。三个通道各有6个选择组合起来就是6×6×6216种颜色。取任意一个颜色比如#CC6600其实就是R取第4档204G取第2档102B取第0档0。如果你需要这个色板的完整对照不需要去背按下面的编号公式自己就能生成颜色编号 (R档位 × 36) (G档位 × 6) (B档位)其中档位从0开始数。#CC6600就是 4×36 2×6 0 156号色。3.2 系统保留的40色和经典16色标准256色调色板里除了216安全色还有40个系统保留色主要是给操作系统界面用的渐变灰和其他固定色。底层硬件和驱动不同这40个颜色在不同设备上可能有差异所以实际开发时我不会依赖这40个。真正值得记的是经典16色也就是VGA彩色文本模式那套在终端、飞控OSD、老式图形界面里经常碰到编号颜色编号颜色0黑8亮黑/灰1蓝9亮蓝2绿10亮绿3青11亮青4红12亮红5品红13亮品红6棕14黄7白15亮白注意6号是棕14号才是黄这个很多人会记错。亮色通常是普通色的加亮版本但棕色加亮后并不是橙色而是黄。3.3 索引色的实际坑我在给一个老式点阵屏写显示驱动时直接用了标准256色调色板去索引结果显示出来的红色明显发紫。查了半天发现那款屏幕控制器的内置调色板RGB顺序是BGR编号完全错位。后来我先往调色板寄存器写一个纯红测试色再根据实际显示结果反向校准才把整张色板纠正过来。所以遇到8位索引色先确认RGB顺序再确认调色板是否可编程最后才谈查表。4. RGB565手动换算位操作一步步拆给你看有人拿到速查表很开心但换一个不在表里的颜色又不会算了。RGB565的换算其实就三步移位加拼接我拆开讲一遍你以后自己就能算。4.1 换算公式现有RGB888三个8位值记为R8、G8、B8R5 R8 3 // 取R8的高5位 G6 G8 2 // 取G8的高6位 B5 B8 3 // 取B8的高5位 RGB565 (R5 11) | (G6 5) | B5原理很简单把低几位直接丢弃只保留高位然后按“红占最高5位、绿占中间6位、蓝占最低5位”排列。为什么是右移3、2、3因为8减5等于38减6等于2。4.2 手算例子橙色 #FFA500R8 0xFF 255右移3位得31二进制11111G8 0xA5 165右移2位得41二进制101001B8 0x00 0右移3位得0按位拼起来R5(5位) G6(6位) B5(5位) 11111 101001 00000转成十六进制就是0xFD20。我在速查表里写的橙色RGB565就是0xFD20和手算结果一致。可以把它拆回二进制验证0xFD20对应二进制1111 1101 0010 0000前5位11111是31中间6位101001是41最后5位00000是0。4.3 反向换算从RGB565回到RGB888屏幕显示没问题后有时候需要把读回来的RGB565还原成近似RGB888比如做截图、做颜色识别。反推公式R8 (R5 3) | (R5 2) G8 (G6 2) | (G6 4) B8 (B5 3) | (B5 2)低几位用“高位复制”的方式补齐比简单地补0更接近原色。比如RGB565的0xFD20R53131324831272487255正好还原成255G64141216441421642166原值是165差1个色阶肉眼基本看不出。4.4 用Python批量生成任意颜色的RGB565对照遇到“比较全”的需求一张写死的表永远不够用。我写了个小脚本想生成多少颜色都行def rgb888_to_rgb565(r, g, b): return ((r 3) 11) | ((g 2) 5) | (b 3) colors { 红 Red: (255, 0, 0), 黄 Yellow: (255, 255, 0), 橙 Orange: (255, 165, 0), 青 Cyan: (0, 255, 255), 品红 Magenta: (255, 0, 255), 天蓝 SkyBlue: (135, 206, 235), 灰 Gray: (128, 128, 128), } for name, (r, g, b) in colors.items(): val rgb888_to_rgb565(r, g, b) print(f{name:12s} #{r:02X}{g:02X}{b:02X} 0x{val:04X})输出结果红 Red #FF0000 0xF800 黄 Yellow #FFFF00 0xFFE0 橙 Orange #FFA500 0xFD20 青 Cyan #00FFFF 0x07FF 品红 Magenta #FF00FF 0xF81F 天蓝 SkyBlue #87CEEB 0x867D 灰 Gray #808080 0x84104.5 手动换算时最容易踩的坑第一个坑是移位方向。有人会把(r 3)写成(r 3)结果颜色乱成一团。第二个坑是忘了按位或而是用加号拼(31 11) (41 5) 0虽然在这类场景通常结果一样但一旦位重叠就会出错所以统一用按位或更安全。第三个坑是字节序。同一个0xFD20在小端设备的内存里是0x20 0xFD如果你直接按字节数组发送给屏幕可能红蓝对调。ILI9341、ST7789这些屏普遍要求高字节在前点亮前先发纯红色0xF800测试看到纯红了再继续能省很多排查时间。5. Python读取图片RGB值8位和16位位深的实际差异热搜词里有一条“python读取图片rgb值”这个太常见了。很多人在这一步遇到颜色不对、图片偏暗的问题十有八九是没搞清图片的位深。5.1 用Pillow读普通8位图最常见的做法是Pillow读出来每个像素是三通道0到255的整数from PIL import Image im Image.open(demo.png).convert(RGB) r, g, b im.getpixel((0, 0)) print(r, g, b) # 输出类似 255 128 0这种像素值可以直接拿去和速查表里的8位RGB列对照。5.2 读16位PNG时要注意数据范围Pillow对16位灰度图支持I;16模式但对16位彩色PNG支持不太友好经常会自动降成8位。所以我处理16位彩色图时改用imageio或OpenCVimport cv2 # 必须加 IMREAD_UNCHANGED否则OpenCV会按8位读取 img cv2.imread(demo_16bit.png, cv2.IMREAD_UNCHANGED) print(img.dtype, img.shape) # dtype 是 uint16shape 是 (高, 宽, 3) # 注意OpenCV默认通道顺序是BGR不是RGB b, g, r img[0, 0] print(r, g, b) # 范围0到65535如果图是16位每通道显示的数值会是0到65535比如#FF0000纯红在这个图上取出来是65535, 0, 0。而你拿速查表查到的8位红色是255两者差257倍。这就是需要上一节那个乘257公式的地方。5.3 实际排查案例图片整体偏暗或者偏色有次我处理一批航拍素材用cv2.imread()默认参数读图所有图片颜色都发暗天空部分甚至接近黑色。我打印像素值一看最大才255再去文件属性一查原来是16位PNG。OpenCV默认参数会直接把16位数据的高8位丢掉只保留低8位导致数值被严重压缩。改用cv2.IMREAD_UNCHANGED后数据正常但显示时又要除257转回8位img cv2.imread(demo_16bit.png, cv2.IMREAD_UNCHANGED) img_8bit (img 8).astype(uint8) # 取高8位也行 # 或者 img_8bit (img / 257).astype(uint8)偏色则是通道顺序问题。OpenCV默认BGRPillow默认RGB如果混用后没转换红和蓝就会互换。我一般固定一套流程读图、转RGB、归一化到0.0到1.0之间再去做后续计算这样无论8位还是16位图代码逻辑都统一。5.4 编写通用读取函数的经验我现在写项目时会放一个通用的取色函数避免每次重复踩位深和顺序的坑import cv2 import numpy as np def read_rgb_unified(path): img cv2.imread(path, cv2.IMREAD_UNCHANGED) if img.dtype np.uint16: img (img 8).astype(np.uint8) # 转到RGB顺序BGR是OpenCV默认 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) return img_rgb这样不管输入是8位还是16位图函数输出的永远是0到255的RGB顺序数组后续逻辑简单很多。6. 嵌入式场景MCU点屏、LED灯条、FPV飞控里怎么用这张表最后聊点实战里的具体用法。这块最容易出问题的不是不知道颜色数值而是不知道设备要求什么顺序、什么字节序。6.1 单片机LCD驱动直接填RGB565用ILI9341、ST7789这类控制器驱动TFT彩屏时画点函数底层大多需要16位RGB565值。比如你要画一个橙色直接把速查表里的0xFD20写进显存或SPI发送缓冲区就行。如果驱动库里用的是两个字节分开发送高字节是0xFD低字节是0x20顺序错了颜色就会偏成完全不同的东西。经验写驱动前先画满屏纯红0xF800再画纯绿0x07E0最后纯蓝0x001F三原色对了剩下的颜色基本都能对上。6.2 WS2812灯条颜色顺序是GRB不是RGBWS2812这类智能灯珠用的是“每通道8位”的GRB数据格式发送24位数据时高位先发的是绿色接着是红色最后是蓝色。很多人第一次点亮灯条发现想亮绿色结果亮红色就是因为直接把RGB888值按R、G、B顺序发出去了。正确的拼法def ws2812_color(r, g, b): # WS2812要求GRB顺序24bit从高位到低位依次是G、R、B return ((g 16) | (r 8) | b) 0xFFFFFF所以速查表里那列8位RGB值在这里要重新排列。RGB565那列也不能直接用WS2812不认RGB565。6.3 FPV飞控场景颜色配置和地面站的显示不一致FPV圈子里总会出现“电调分8位和32位”这种说法注意这里的8位指的是电调主控MCU的位宽和我们说的颜色位宽是两回事。飞控上的LED灯条、OSD颜色配置本质还是GRB灯珠和调色板索引那套逻辑。我遇到过飞控调参软件里选好颜色预览显示正常机架上的灯条亮出来却红蓝互换的情况原因就是飞控底层发送灯条数据时用了BGR顺序调参软件却按RGB解析。排查方法很笨但有效先给灯条单独写一个全红测试程序如果亮出来是绿就说明颜色映射反了手动把驱动里的R和B对调即可。6.4 建议维护一张自己的速查表脚本我现在维护着一张“颜色源表”里面不只有RGB888和RGB565还把规格化成0.0到1.0的浮点值、Unity/Unreal里的颜色向量、CSS变量都写在同一个JSON文件里。每次遇到新项目直接跑一段脚本生成对应的头文件或主题文件。这样做的好处是团队同事不用再去翻网页查颜色也避免每个人手里的表数值对不上。你完全可以按同样思路拿本文第4.4小节的脚本做底子把项目里用到的颜色都加进去生成一份属于你自己的“比较全”的颜色对照表。实际上查表这件事本身不难难的是搞清楚你手上设备要的是哪种“8位、16位”。先把格式认准再套数值基本就不会翻车。