浅谈颜色空间

810 阅读6分钟

颜色模型 vs 颜色空间

首先要区别一下颜色模型和颜色空间的区别。颜色模型是指 RGB,HSL(HSV),等,它是一种描述颜色的方式方法。

比如说纯红色,用RGB模型怎么描述呢?(1, 0, 0) 或者是 (255, 0, 0) 那用 HSL 描述则用色相、饱和度、明度的维度去描述。

颜色空间(色域)则是 sRGB, Adobe RGB, Display P3 等这些标准,下面来具体讲讲这些东西是什么。

人眼可识别的所有颜色 CIE 与色域(gamut)

我们要描述的颜色必须是人眼能看到的颜色,如果人眼看不见的颜色,那描述也没什么意义。人眼看到的颜色,其实就是物体发出的光,那么我们必须先知道,人眼可以看到的颜色(光)总共有多少,或者说范围有多大。

下图是 CIE 1931 color space 所定义的可见光谱,也就是说人类肉眼所能识别的所有可见光所表示的颜色都在这里了。

image.png

那么,我们人造的显示媒介,比如显示器,打印机打印出的图像所显示的颜色能还原真实的颜色吗?比如说世界上有一种红色的花朵,看起来是这么红,假设这个红是 a,那么打印机能打印出这么红的颜色吗?显示器能显示出这么红的颜色吗?

答案显然是能,但也不能。是因为如果技术是无限的发展与提高,那么总有一天,人类的显示器或许可以实现,不能是因为以目前的技术水平,还不能让显示器完全显示所有的颜色。

也就是说,我们的显示设备目前只能展示所有可见颜色的一部分,那么这个一部分是多少呢?就有人定义出各种各样的标准了,被称为色域(gamut),比如 sRGB,Adobe RGB,Display P3 等等诸如此类。

下图展示了一个几种色域范围的对比图。

image.png

数据存储与转换

上面说了那么多,那我们一张图片上的颜色的数据是如何存储,最终又是如何与不同的颜色空间作用,如何展示到我们的眼前呢?

一张图片的数据始终是以 bitmap 的形式存储。

什么是 bitmap? bitmap 可以理解为一个二维数组,数组的大小 m * n 即是图片的宽度与高度。每个数组中的元素是图片每个像素的数据。

对于每个像素点的组成当然有各种各样的形式以满足体积或性能上的特殊要求,比如最常见的当然是 RGBA8888,也就是一个像素点使用4个字节来表示,每个组分8bit,也就是取值范围是 0-255,一共256种情况。除开alpha通道,一共可以表达 256 * 256 * 256 种颜色。

除开颜色空间的信息,这种图片直接显示,就是以 线性RGB(LinearRGB) 这个颜色空间进行展示。

下面来做一些实验,首先用 python 生成一个 256 * 4 的图片,第一行是从 (0,0,0) 全黑到 (255,255,255) 全白。 第二行,第三行,第四行,分别只对 RGB 通道做递增,也是从 0 到 255,可以得到下面这张图片。直接打开这个图片,它会显示出来,用截屏工具或者取色器进行取色,发现它所表达的颜色和我们程序写入的颜色分毫不差。

如果我们用 photoshop 打开它,就会发现有微妙的颜色区别。

image.png

可以看到在 PS 中(上面),左侧深色的部分似乎没有下面的那么深的颜色,此时,如果用屏幕取色器去查看像素点,发现取到的颜色和我们用程序写入的颜色是不一样的。

工作空间

这就涉及到一个工作空间的概念。工作空间就是说一个图像数据放到我这个系统里展示,应该是用什么颜色空间来表达呢?比如说在 IOS 中,工作空间就是 linearRGB,而在我的 PS 中,我们可以在 编辑 -> 指定配置文件 来修改当前的颜色空间。

image.png

如上图所示,可以看到,我们现在是以 sRGB 的颜色空间显示。也就是说,我们从图像的bitmap里面取出来的一个个像素的颜色数据是线性的RGB值,然后以 sRGB 的颜色空间进行展示,这里就存在一个转换,那这个转换具体是什么呢?

可以参考这篇文档中提及的信息:Understanding Color Spaces and Color Space Conversion

image.png

伽马矫正

可以看到,线性空间转换到其他的颜色空间,一个最大的特点就是做了一个 pow 运算,这个计算也被称为 伽马矫正(gamma correction)

我把这个gamma矫正的函数曲线画出来,和完全线性的做一下比较,可以看到,它将较暗的区域拉伸了,提升了对比,而较亮的区域则压缩窄了,这是因为人眼对暗部敏感,而对亮部不敏感的原因。

image.png

我用个图片来做个例子。可以看到下面这张狗的图片,当完全线性的时候,暗部的细节都糊在一起,看不清,当g开始提高,暗部的细节逐渐对比拉开,内容清晰可见,而外面的天空由于本身处于亮的区域,它的范围压缩一点也不太影响观感。

E7705151-193E-410E-A76D-AD33D007E6A6.gif

回到最开始的那张python生成的色卡图片,我们看一下这个图片文件的信息。

image.png

接着我们在 PS 里打开,选用 display p3 这个配置文件,并且另存为另一张图片。此时再打开简介信息,如下图所示,现在它多了一个颜色描述文件。

image.png

我们将两张图片打开,并分别取色,就能看出其中微妙的区别。

image.png

所以一个图像从数据变成一个显示某种颜色的光,是由两部分组成决定共同作用的。

一是数据本身,数据本身是如何存储的,RGBA8888还是最新的10bit,12bit的HDR技术,这决定了每个颜色数据本身的二进制信息。

读取出来之后,这个颜色空间默认是线性颜色空间。但是如果带有一个配置文件指定颜色空间,那么它最终就会经过转换,从一个颜色空间A转换到颜色空间B,最终展示出来的是另一个颜色。

最后用一个图片来总结一下。

image.png

附录:转换函数

LinearRGB -> sRGB

来源: www.mathworks.com/help/images…

f(u)=f(u),u<0f(u) = -f(-u), u<0 f(u)=cu,0u<df(u) = c \cdot u, 0 \le u < d f(u)=auy+b,udf(u) = a \cdot u^y + b, u \ge d

a=1.055a = 1.055 b=0.055b = -0.055 c=12.92c = 12.92 d=0.0031308d = 0.0031308 y=1/2.4y = 1 / 2.4

LinearRGB -> Adobe RGB(1998)

来源: www.mathworks.com/help/images…

v=uy,u0v = u^y, u \ge 0 v=(u)y,u<0v = -(-u)^y, u < 0 y=12.19921875y = \frac{1}{2.19921875}