RGB颜色值全解析:8位、16位与RGB565换算实战速查
2026/9/13 3:42:02 网站建设 项目流程

调过屏、点过灯、处理过图片的人,电脑里几乎都存着一张颜色对照表。我自己最早那张表是从旧论坛上复制下来的,表格里只有颜色名和十六进制码,后来做单片机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 字节R=0-31, G=0-63, B=0-31MCU屏幕、嵌入式GUI
RGB88824 bit3 字节每通道0-255网页、普通PNG/JPG
RGBA888832 bit4 字节每通道0-255游戏贴图、UI
RGB4848 bit6 字节每通道0-6553516位PNG、RAW

1.3 为什么RGB565里绿色多给了一位

人眼对绿色最敏感,对蓝色最不敏感。RGB565把绿色分到6位,能在只有65536种颜色的限制下,最大程度减少人眼能感知的色阶断层。这是从CRT时代就定下来的老规矩,一直沿用到现在。记住这个“绿多一位”的规律,后面手动换算时就不容易忘。

2. 高频颜色速查表:十六进制、8位RGB、RGB565三列对照

下面这些颜色是我在项目和日常处理图片时最常用到的,按色系整理。中间一列的8位RGB值给Python、C语言、网页用,最后一列的RGB565值可以直接写进MCU屏幕驱动或图形库的缓冲区。

2.1 灰度、黑白和红色系

颜色名Web十六进制8位RGB(0-255)RGB565(0x????)
黑 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, 00xFEA0

2.2 黄绿、蓝紫和常用特殊色

颜色名Web十六进制8位RGB(0-255)RGB565(0x????)
黄 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, 2500xE73F

2.3 另一种16位:每通道0到65535怎么换算

如果你拿到的是16位每通道的PNG或RAW图,你需要的就不是RGB565,而是把0到255的8位值等比扩展到0到65535。换算公式很简单:

16位值 = 8位值 × 257,等价于(8位值 << 8) | 8位值

为什么是257?因为255对应65535,65535除以255正好等于257。用位运算理解更直接:把一个8位值同时放在高8位和低8位,就完成了线性扩展。

举三个例子:

  • #FF0000的红色通道:255 × 257 = 65535,绿色和蓝色都是0,得到(65535, 0, 0)
  • #FFA500的绿色通道:165 × 257 = 42405,橙色变成(65535, 42405, 0)
  • #404040的灰色:64 × 257 = 16448,三个通道都是16448

3. 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×6=216种颜色。取任意一个颜色,比如#CC6600,其实就是R取第4档204,G取第2档102,B取第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、老式图形界面里经常碰到:

编号颜色编号颜色
08亮黑/灰
19亮蓝
2绿10亮绿
311亮青
412亮红
5品红13亮品红
614
715亮白

注意6号是棕,14号才是黄,这个很多人会记错。亮色通常是普通色的加亮版本,但棕色加亮后并不是橙色,而是黄。

3.3 索引色的实际坑

我在给一个老式点阵屏写显示驱动时,直接用了标准256色调色板去索引,结果显示出来的红色明显发紫。查了半天发现那款屏幕控制器的内置调色板RGB顺序是BGR,编号完全错位。后来我先往调色板寄存器写一个纯红测试色,再根据实际显示结果反向校准,才把整张色板纠正过来。所以遇到8位索引色,先确认RGB顺序,再确认调色板是否可编程,最后才谈查表。

4. RGB565手动换算:位操作一步步拆给你看

有人拿到速查表很开心,但换一个不在表里的颜色又不会算了。RGB565的换算其实就三步移位加拼接,我拆开讲一遍,你以后自己就能算。

4.1 换算公式

现有RGB888三个8位值,记为R8、G8、B8:

R5 = 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等于3,8减6等于2。

4.2 手算例子:橙色 #FFA500

  • R8 = 0xFF = 255,右移3位得31,二进制11111
  • G8 = 0xA5 = 165,右移2位得41,二进制101001
  • B8 = 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的0xFD20,R5=31,31<<3=248,31>>2=7,248+7=255,正好还原成255;G6=41,41<<2=164,41>>4=2,164+2=166,原值是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 0x8410

4.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或OpenCV:

import cv2 # 必须加 IMREAD_UNCHANGED,否则OpenCV会按8位读取 img = cv2.imread("demo_16bit.png", cv2.IMREAD_UNCHANGED) print(img.dtype, img.shape) # dtype 是 uint16,shape 是 (高, 宽, 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默认BGR,Pillow默认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,不是RGB

WS2812这类智能灯珠用的是“每通道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位”。先把格式认准,再套数值,基本就不会翻车。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询