Python colorsys 标准库深入解析:RGB/YIQ/HLS/HSV 颜色空间双向转换全指南
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
colorsys是 Python 标准库中负责颜色系统之间双向转换的轻量模块。它以显示器普遍采用的 RGB(红绿蓝)坐标为基准,提供与 YIQ、HLS(色调/亮度/饱和度)、HSV(色调/饱和度/明度)三种坐标系统互相转换的全部六个函数。本文以 colorsys 官方文档 为主线,结合模块完整实现 Lib/colorsys.py 与其单元测试 Lib/test/test_colorsys.py,逐条讲解每个函数的数学原理、取值范围、浮点精度边界与经典应用场景,帮助你在调色、取色、配色、图像处理与视频信号模拟等实践中安全、正确地完成颜色空间换算。
模块定位:从一段广播级视频标准说起
colorsys的设计目标非常聚焦:同一颜色在不同坐标体系中的数值映射。模块文档明确指出,它支持在显示器使用的 RGB 空间与三种其他坐标系统之间做双向(bidirectional)转换:
- YIQ:亮度(Luminance)+ 色度(Chrominance),历史上用于 NTSC 复合视频信号;
- HLS:色调(Hue)、亮度/明度(Lightness)、饱和度(Saturation);
- HSV:色调(Hue)、饱和度(Saturation)、明度值(Value)。
模块源码首部的模块 docstring(Lib/colorsys.py)用一句话概括了 API 形态:对每个颜色系统 ABC,提供一对函数rgb_to_abc(r, g, b)与abc_to_rgb(a, b, c),两者互为逆运算。文档亦在「另见」中指引读者前往色彩学资料(如 Poynton 的 ColorFAQ 等站点)深入了解各颜色空间的背景理论。
取值范围约定:浮点三元组
所有坐标都以浮点数表达,模块 docstring 与文档正文给出了精确的边界约定:
- 绝大多数空间中,坐标均处于[0.0, 1.0]区间内;
- 唯一的例外是 YIQ 中的 I 与 Q 分量——Y 严格位于 0 到 1 之间(0 为黑、1 为白),而 I、Q 可以取正值或负值;
- 数值越界的输入不保证得到有意义的结果:模块文档注释明确指出"Inputs outside the valid range may cause exceptions or invalid outputs"(超界输入可能导致异常或无效输出),因此调用方应自行保证输入在合法区间。
这一约束还体现在测试对roundtrip(往返一致性)的验证方式上:测试 Lib/test/test_colorsys.py 只在[0.0, 1.0]内以 0.2 步长枚举 RGB 输入,再断言rgb -> hsv -> rgb能还原原值。
六个转换函数的完整规格
模块共导出 6 个函数(对应 Lib/colorsys.py 中的__all__),全部为纯函数、不依赖任何第三方库:
| 函数签名 | 功能 |
|---|---|
rgb_to_yiq(r, g, b) | RGB 坐标 → YIQ 坐标 |
yiq_to_rgb(y, i, q) | YIQ 坐标 → RGB 坐标 |
rgb_to_hls(r, g, b) | RGB 坐标 → HLS 坐标 |
hls_to_rgb(h, l, s) | HLS 坐标 → RGB 坐标 |
rgb_to_hsv(r, g, b) | RGB 坐标 → HSV 坐标 |
hsv_to_rgb(h, s, v) | HSV 坐标 → RGB 坐标 |
RGB → YIQ:亮度分离的线性变换
RGB 转 YIQ(Lib/colorsys.py)是线性组合,其系数来自 FCC 版 NTSC 制式(源码注释原文:"The ones in this library uses constants from the FCC version of NTSC"):
def rgb_to_yiq(r, g, b): y = 0.30*r + 0.59*g + 0.11*b i = 0.74*(r-y) - 0.27*(b-y) q = 0.48*(r-y) + 0.41*(b-y) return (y, i, q)其中 Y 是加权灰度值(人眼对绿色最敏感,故绿色权重 0.59 最高),I、Q 携带色度信息。由于 RGB 坐标均在 [0,1],Y 必落在 [0,1];而 I、Q 则可正可负——这正是文档特别强调的范围例外。测试 test_yiq_values 中记录的典型端点值印证了这一点:
- 纯红
(1,0,0)→(0.3, 0.599, 0.213) - 纯蓝
(0,0,1)→(0.11, -0.3217, 0.3121) - 纯绿
(0,1,0)→(0.59, -0.2773, -0.5251) - 纯白
(1,1,1)→(1.0, 0.0, 0.0),纯灰(0.5,0.5,0.5)→(0.5, 0.0, 0.0)
YIQ → RGB:一个会做「钳位」的逆向
逆变换同样以线性代数形式直接给出(Lib/colorsys.py):
def yiq_to_rgb(y, i, q): r = y + 0.9468822170900693*i + 0.6235565819861433*q g = y - 0.27478764629897834*i - 0.6356910791873801*q b = y - 1.1085450346420322*i + 1.7090069284064666*q if r < 0.0: r = 0.0 # ... g、b 同理 if r > 1.0: r = 1.0 # ... return (r, g, b)源码注释保留了手工推导过程(以r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48)等三个等式表示),系数约为原分式展开后的浮点结果。注意这里存在有意为之的钳位(clamping)行为:若计算结果超出 [0,1],会强制截断到边界。
为什么需要钳位?因为任意 YIQ 三元组并不都对应合法 RGB——例如测试 test_yiq_to_rgb_clamping 中的输入(0.25, -1.0, -1.0)与(0.0, -1.0, 0.5)本身来自非合法 RGB 的投影,逆变换后会落在 RGB 立方体之外,此时模块主动收敛到边界。这一点与 HSV/HLS 的逆变换行为不同,属于 YIQ 系的独特语义,务必在使用时留意。
HLS 与 HSV:两类"色调-饱和度"模型的转换细节
HLS 与 HSV 都把颜色分解为Hue(色相/色调)、Saturation(饱和度)与一个亮度轴,差异在于亮度轴与几何模型的定义。在colorsys中两者都采用归一化到 [0,1] 的色相——而不是图形软件常见的 0–360 度角。
换算规则:若你手中的色相以度为单位(如
hue=120),传入函数前请除以 360 得到hue/360.0;模块内部用h/6.0 % 1.0等方式完成从"六段扇形"到单位圆的归约。
从 min/max 求 HLS
RGB 转 HLS(Lib/colorsys.py)使用经典的 min/max 三分法:
def rgb_to_hls(r, g, b): maxc = max(r, g, b) minc = min(r, g, b) sumc = (maxc+minc) rangec = (maxc-minc) l = sumc/2.0 if minc == maxc: return 0.0, l, 0.0 # 无彩色:饱和度为 0 if l <= 0.5: s = rangec / sumc # 公式 A else: s = rangec / (2.0-maxc-minc) # 公式 B(gh-106498) rc = (maxc-r) / rangec gc = (maxc-g) / rangec bc = (maxc-b) / rangec if r == maxc: h = bc-gc elif g == maxc: h = 2.0+rc-bc else: h = 4.0+gc-rc h = (h/6.0) % 1.0 return h, l, s关键点在于:
- 灰阶捷径:当
minc == maxc(即 R=G=B)时直接返回(0.0, l, 0.0),色相取 0、饱和度取 0,避免除零; - 饱和度按亮度分支:亮度
l <= 0.5时用rangec / sumc,否则用rangec / (2.0-maxc-minc)。源码注释特别注明后者"Not always 2.0-sumc: gh-106498",即接近白色(如(0.9999999999999999, 1, 1))时直接写2.0-sumc会让分母趋近于 0、诱发除零/数值灾难,故改用等价的2.0-maxc-minc。对应回归测试见 test_hls_nearwhite,而仓库变更记录 Misc/NEWS.d/3.13.0a1.rst 也记载了一次导致除零的改动被回退的历史; - 色相六段定位:根据哪个通道取最大值,用
(maxc-某通道)/rangec之差确定色相所在扇区,最后(h/6.0) % 1.0归约到 [0,1)。
HLS 逆变换hls_to_rgb(Lib/colorsys.py)先把问题归约为两基色m1、m2:
def hls_to_rgb(h, l, s): if s == 0.0: return l, l, l if l <= 0.5: m2 = l * (1.0+s) else: m2 = l+s-(l*s) m1 = 2.0*l - m2 return (_v(m1, m2, h+ONE_THIRD), _v(m1, m2, h), _v(m1, m2, h-ONE_THIRD))辅助函数_v(m1, m2, hue)(Lib/colorsys.py)把色相折回单位圆后按1/6、1/2、2/3三个断点分段插值,其中模块顶部预定义常量ONE_THIRD = 1/3、ONE_SIXTH = 1/6、TWO_THIRD = 2/3(Lib/colorsys.py)。当s == 0.0(灰)时三通道直接等于亮度 l,避免无意义计算。
测试锚点:HLS 端点值表
单元测试 test_hls_values 给出了直接可校验的锚点:
| 颜色 | RGB | HLS |
|---|---|---|
| 黑 | (0,0,0) | (0, 0.0, 0.0) |
| 白 | (1,1,1) | (0, 1.0, 0.0) |
| 灰 | (0.5,0.5,0.5) | (0, 0.5, 0.0) |
| 红 | (1,0,0) | (0, 0.5, 1.0) |
| 绿 | (0,1,0) | (2/6, 0.5, 1.0) |
| 蓝 | (0,0,1) | (4/6, 0.5, 1.0) |
| 青 | (0,1,1) | (3/6, 0.5, 1.0) |
注意同为主色,其亮度恒为 0.5、饱和度恒为 1.0,只是色相依次相差 1/6——这是 HLS 圆柱模型对纯色的标准描述。
HSV:最贴近"取色器直觉"的模型
RGB 转 HSV(Lib/colorsys.py)与前文 HLS 高度对称,但明度轴V直接取三通道最大值:
def rgb_to_hsv(r, g, b): maxc = max(r, g, b) minc = min(r, g, b) rangec = (maxc-minc) v = maxc if minc == maxc: return 0.0, 0.0, v s = rangec / maxc # ... 色相扇区判断与 HLS 相同 return h, s, v而反向函数hsv_to_rgb(Lib/colorsys.py)采用色相六边形(hexcone)的经典扇形划分算法:
def hsv_to_rgb(h, s, v): if s == 0.0: return v, v, v i = int(h*6.0) # 落入哪个 60° 扇区 f = (h*6.0) - i # 扇区内的相对位置 p = v*(1.0 - s) q = v*(1.0 - s*f) t = v*(1.0 - s*(1.0-f)) i = i%6 # 依据 i 的值,从 (v,t,p)/(q,v,p)/(p,v,t)/(p,q,v)/(t,p,v)/(v,p,q) 中选取 ...其中p、q、t是三条边上的插值量,源码注释中的# XXX assume int() truncates!提示该实现依赖int()向零截断(对负色相需先归约)。当s == 0.0时返回灰阶(v, v, v)。
文档示例与测试锚点
官方文档给出的示例恰好演示了这对函数的互逆性:
>>> import colorsys >>> colorsys.rgb_to_hsv(0.2, 0.4, 0.4) (0.5, 0.5, 0.4) >>> colorsys.hsv_to_rgb(0.5, 0.5, 0.4) (0.2, 0.4, 0.4)端点值表(test_hsv_values)进一步给出:
| 颜色 | RGB | HSV |
|---|---|---|
| 黑 | (0,0,0) | (0, 0.0, 0.0) |
| 白 | (1,1,1) | (0, 0.0, 1.0) |
| 红 | (1,0,0) | (0, 1.0, 1.0) |
| 黄 | (1,1,0) | (1/6, 1.0, 1.0) |
| 绿 | (0,1,0) | (2/6, 1.0, 1.0) |
| 青 | (0,1,1) | (3/6, 1.0, 1.0) |
| 蓝 | (0,0,1) | (4/6, 1.0, 1.0) |
| 品红 | (1,0,1) | (5/6, 1.0, 1.0) |
对比可见两模型色相排序一致(红→黄→绿→青→蓝→品红),这也是测试test_hsv_values与test_hls_values共用同一组色相期望值的深层原因。
精度、往返一致性与边界行为
roundtrip:为什么"转过去再转回来"基本还原
测试套件为三组变换各实现了*_roundtrip用例(Lib/test/test_colorsys.py),做法一致:在[0,1]网格上枚举 RGB → 转出 → 转回,再用assertAlmostEqual(约 7 位有效数字的浮点容差)比对。之所以不能要求逐位相等,是因为中间插值存在浮点舍入;但对输入在合法域内的合法颜色,往返误差始终被控制在可忽略量级。
值得一提的例外是HLS 的"近白"区域:test_hls_nearwhite(针对 gh-106498)注释明确写道 "these do not work in reverse"。例如rgb_to_hls(0.9999999999999999, 1, 1)得到(0.5, 1.0, 1.0),该元组再经hls_to_rgb只能回到纯白(1.0, 1.0, 1.0)——这是 HLS 模型在亮度极值附近饱和度定义固有的病态,并非函数缺陷。实践启示:不要对处于亮度极值(0 或 1)附近、以 HLS 为中间态的"改饱和度再转回"操作期待完美还原。
色相的 360° 周期等价
测试还显式验证了色相的整周期不变性(Lib/test/test_colorsys.py):对任意(h, s, v),hsv_to_rgb(h + 1.0, s, v)与hsv_to_rgb(h, s, v)结果相同。这提醒使用者:色相传入超界并不会报错,模块按周期自动折回;若你依赖"输入越界即报错"来做防御,需要自行校验。
YIQ 与 HLS/HSV 的钳位差异
前文已述,yiq_to_rgb会把结果硬性钳制到 [0,1](Lib/colorsys.py),而 HLS/HSV 的逆变换则依赖公式本身的几何约束,不做显式钳位。因此用任意 YIQ 元组做逆变换总能得到合法 RGB,而用"越界的 H/S/V"做逆变换则可能得到超出 [0,1] 的通道值——需要时请自行 clamp。
实战:在 CPython 生态中如何使用
标准用法
colorsys是内置纯 Python 模块,无需安装、不依赖 C 扩展,直接导入即可:
import colorsys # 取色器拿到 #40ff80 这种 8 位十六进制颜色时先归一化: def hex_to_rgb01(hexstr): hexstr = hexstr.lstrip('#') r, g, b = (int(hexstr[i:i+2], 16) for i in (0, 2, 4)) return r/255.0, g/255.0, b/255.0 r, g, b = hex_to_rgb01('#40ff80') h, s, v = colorsys.rgb_to_hsv(r, g, b) print(f"H={h:.3f} ({h*360:.0f}°) S={s:.3f} V={v:.3f}")输出形如H=0.396 (143°) S=0.749 V=1.000。
常见应用范式
- 基于色相排序/分组:把调色板中杂乱的颜色转成 HSV,按 H 排序即可得到"彩虹序";
- 统一明暗处理:
v *= 0.8或l *= 1.1后转回 RGB,可实现不改色相的调亮/调暗,GUI 控件高亮、主题调色器均可使用; - 去饱和/灰化:把
s置 0 再转回,即得对应灰度; - 色彩对比分析:用
rgb_to_yiq提取 Y 亮度做前景/背景可读性粗判,因其 Y 即加权灰度,数学上等价于常见亮度公式0.299R+0.587G+0.114B的归一化版本; - 转换门面层:
colorsys只接受 0–1 浮点,很多配色库(如 tkinter 颜色、matplotlib colormap)输出都是 0–1 浮点,可直接对接;若处理 0–255 整数,请先除以 255、输出前乘回 255 并四舍五入。
数值合法性检查清单
参照模块 docstring 与文档约定,调用前建议自查:
- RGB 三通道是否都在
[0, 1]?否则逆变换可能越界; - 色相若以度为单位是否已
/360.0归一化? - 是否依赖往返无损?若是,避开 HLS 亮度≈0/≈1 的近白近黑区域;
- YIQ 逆变换自带钳位,其余逆变换的越界通道需自行 clamp。
验证与迭代:测试如何守护这 150 行代码
整个模块连同注释不足 170 行,却由一套相当完备的回归测试守护(Lib/test/test_colorsys.py),可作为修改或移植时的行为规格书:
test_hsv_roundtrip/test_hls_roundtrip/test_yiq_roundtrip:全域网格往返一致性;test_hsv_values/test_hls_values/test_yiq_values:端点与基准色的数值锚点(含负 I/Q);test_hls_nearwhite(gh-106498):近白区域的除零回归防护;test_yiq_to_rgb_clamping:非法 YIQ 输入的钳位行为。
需要重新生成该模块文档时,可参考仓库 Doc 构建体系(如 Doc/README.rst);而对colorsys做任何数学层面的改动,都必须先跑通上述测试矩阵,尤其是三组 roundtrip——它们是"转换函数互为逆映射"这一模块核心承诺的直接验证。
小结
colorsys用最少的 API 覆盖了四个颜色空间间的全部双向路径:RGB 作为中枢与 YIQ(NTSC 线性亮度色度)、HLS 与 HSV(两类色调-饱和度圆柱)相连。理解它的三个要点——浮点范围约定、色相归一化到 [0,1]、以及 HLS/HSV 与 YIQ 逆变换行为差异——就足以在生产代码中放心使用;而 Lib/colorsys.py 与 Lib/test/test_colorsys.py 加起来不过三百余行,本身也是一份"纯 Python 数值算法 + 回归测试"极佳的研读范本。
【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考