4、RGB与Lab互转:通过XYZ中间空间的转换流程
说实话,RGB和Lab之间的转换,是很多图像处理新手的第一道坎。
我刚开始做色彩管理时,也在这上面栽过跟头。RGB转Lab,不能直接转。必须经过一个中间人——XYZ色彩空间。这个流程,说白了就是:RGB → XYZ → Lab,反过来也一样。
为什么非要经过XYZ?
你想想看,RGB是设备相关的。你的显示器、我的手机、他的相机,各自的RGB标准都不一样。而Lab是设备无关的,它描述的是人眼看到的颜色。
XYZ色彩空间,就是这两者之间的桥梁。它是一个标准的、数学上定义好的中间空间。所有颜色转换,都先统一到XYZ,再转到目标空间。
我在项目中遇到过一个问题:直接用RGB数值去匹配颜色,结果不同设备上显示完全不一样。后来才意识到,必须先把RGB转到Lab,才能做真正的颜色比较。
第一步:RGB转XYZ
这一步,核心是线性化。RGB值通常是经过伽马校正的,不能直接拿来用。
关键点:先做伽马校正的逆运算,把非线性RGB转成线性RGB。
以sRGB标准为例,转换公式如下:
# 1. 伽马校正逆运算(sRGB标准)
def srgb_to_linear(c):
if c <= 0.04045:
return c / 12.92
else:
return ((c + 0.055) / 1.055) ** 2.4
# 2. 线性RGB转XYZ(sRGB标准矩阵)
M = [
[0.4124564, 0.3575761, 0.1804375],
[0.2126729, 0.7151522, 0.0721750],
[0.0193339, 0.1191920, 0.9503041]
]
def linear_rgb_to_xyz(r, g, b):
x = M[0][0]*r + M[0][1]*g + M[0][2]*b
y = M[1][0]*r + M[1][1]*g + M[1][2]*b
z = M[2][0]*r + M[2][1]*g + M[2][2]*b
return x, y, z
我的习惯:实际项目中,我会用NumPy做矩阵运算,比手写循环快得多。而且要注意,RGB值要归一化到[0,1]范围再计算。
第二步:XYZ转Lab
这一步,需要先定义白点。白点就是标准光源下的参考白色。最常用的是D65标准光源。
转换公式如下:
# 3. XYZ转Lab(D65白点)
Xn, Yn, Zn = 0.95047, 1.0, 1.08883 # D65白点
def xyz_to_lab(x, y, z):
# 归一化
fx = x / Xn
fy = y / Yn
fz = z / Zn
# 非线性变换
def f(t):
if t > 0.008856:
return t ** (1/3)
else:
return (7.787 * t) + (16/116)
L = 116 * f(fy) - 16
a = 500 * (f(fx) - f(fy))
b = 200 * (f(fy) - f(fz))
return L, a, b
我曾经踩过的坑:白点选错,结果全偏色。D65和D50的白点数值不一样,转换结果天差地别。做印刷相关项目时,一定要确认用哪个白点标准。
第三步:Lab转回RGB
反向转换,就是逆着来一遍:
# 4. Lab转XYZ
def lab_to_xyz(L, a, b):
fy = (L + 16) / 116
fx = a / 500 + fy
fz = fy - b / 200
def f_inv(t):
if t > 0.206897:
return t ** 3
else:
return (t - 16/116) / 7.787
x = Xn * f_inv(fx)
y = Yn * f_inv(fy)
z = Zn * f_inv(fz)
return x, y, z
# 5. XYZ转线性RGB(逆矩阵)
M_inv = [
[ 3.2404542, -1.5371385, -0.4985314],
[-0.9692660, 1.8760108, 0.0415560],
[ 0.0556434, -0.2040259, 1.0572252]
]
def xyz_to_linear_rgb(x, y, z):
r = M_inv[0][0]*x + M_inv[0][1]*y + M_inv[0][2]*z
g = M_inv[1][0]*x + M_inv[1][1]*y + M_inv[1][2]*z
b = M_inv[2][0]*x + M_inv[2][1]*y + M_inv[2][2]*z
return r, g, b
# 6. 线性RGB转sRGB(伽马校正)
def linear_to_srgb(c):
if c <= 0.0031308:
return 12.92 * c
else:
return 1.055 * (c ** (1/2.4)) - 0.055
完整流程总结
| 方向 | 步骤 | 关键操作 |
|---|---|---|
| RGB → Lab | 3步 | 伽马逆校正 → 矩阵乘法 → 非线性变换 |
| Lab → RGB | 3步 | 非线性逆变换 → 逆矩阵乘法 → 伽马校正 |
核心要点:
- RGB和Lab不能直接互转,必须经过XYZ
- 转换前一定要确认RGB标准(sRGB、Adobe RGB等)
- 白点选择影响最终结果,D65最常用
- 伽马校正和逆校正不能搞反
嗯,这套流程看起来步骤多,但实际写代码时,封装成几个函数就清爽了。我一般会写一个ColorConverter类,把RGB、XYZ、Lab之间的转换都包进去,用的时候直接调方法就行。
你想想看,理解了这套流程,以后做颜色分析、图像增强、风格迁移,心里就有底了。Lab空间的L通道单独处理亮度,a和b通道处理颜色,很多图像预处理算法都是基于这个思路设计的。