一、色彩空间
1.1 色域
先明确一个概念:色域描述的是颜色的范围,比如:在这个色域里,最红的红到底有多红?
常见的色域都以下面的马蹄图为基础的,这是一个不规则形状的图形,三个端点分别是红色、绿色和蓝色。
不同的色域通常是这个范围的子集,比如:我们常听说的sRGB、NTSC、P3、Adobe RGB、2020、CMYK等。
这些不同的色域由不同行业、不同的组织、不同的时间定义的。这些色域有重合区域,同样也有不重合区域,如下图所示。
1.2 颜色模型
同样先明确颜色模型的概念:颜色模型定义的是从哪些维度来描述一个颜色。
颜色模型是不同的描述颜色的方法,比如:Lab、RGB、HSL、HSV等,这些不同的色彩模型由不同的专家提出。通过这些模型,能够直观的、以向量的方式把颜色唯一的表示出来。
之所以有着多么的颜色模型,可能是因为有两个原因:
- 各个大师对颜色的理解不同:就像元素周期表也有不同的版本一样;
- 不同行业对颜色的应用不同:
- 显示器:是自发光物体,而且本身就以RGB为发光原理;
- 广播电视:需要兼容黑白和彩色信号,所以以亮度和色度来描述颜色,来达到新旧设备的兼容性,使用YCbCr信号;
- 印刷行业:印刷品需要反射光来显色,是利用的减色原理,所以使用的是CMY模型;
- 视频文件:视频压缩需要删除冗余信号,所以使用Yuv模型;
- ……
二、色彩空间转换
色彩空间转换这个概念针对的是:不同颜色表示方法之间的互相转换,就像角度和弧度的换算,其实说的是一回事。
这一块的具体计算方法我直接放到文章最后了,因为这一块的难度比较低,但是占太多位置了。
三、色差计算
色差描述的是两个颜色之间的差异,这个同样有不同的算法。
按照我的认知δE 2000应该是最新、最受认可的算法了,它不仅体现出了颜色之间的差异,还根据人眼的感知特性对算法做了修正,让计算结果与人的感知更加匹配。
3.1 δC
Delta C* = sqrt( ( CIE-a*2 ^ 2 ) + ( CIE-b*2 ^ 2 ) ) - sqrt( ( CIE-a*1 ^ 2 ) + ( CIE-b*1 ^ 2 ) )
可以看出δC的计算方法是很简单粗暴的,就直接取均方值了,且去掉了亮度信息。
3.2 δE CIE
Delta E* = sqrt( ( ( CIE-L*1 - CIE-L*2 ) ^ 2 )+ ( ( CIE-a*1 - CIE-a*2 ) ^ 2 ) + ( ( CIE-b*1 - CIE-b*2 ) ^ 2 ) )
δE CIE的计算方法与上面的类型,但是增加了亮度信息。
这里补充一下关于亮度的个人理解:颜色中的“亮度”描述的是颜色本身的明度信息,显示器的“亮度”描述的是显示器背光灯的发光强度。这两者理论上应该是互相独立的,以LCD型显示器为例:
- 颜色的亮度:完全取决于显示器R、G、B三个子像素的颜色,而这三个子像素的颜色完全取决于子像素背后的液晶偏转程度和RGB的CF颜色;
- 显示器的亮度:完全取决于显示器背光模组灯珠的亮度、导光板和其他各层对光的透过率。
所以,这两个亮度描述的是2个维度的事,但是从实际观感来看,好像还是会有主观感受上的差异。
3.3 δE 1994
δE1994和2000的计算方法会稍微复杂一些,因为会涉及到一些权重系数,下面是ColorMath的官方Python实现方法。
import numpy
from colormath import color_diff_matrix
def _get_lab_color1_vector(color):
"""
Converts an LabColor into a NumPy vector.
:param LabColor color:
:rtype: numpy.ndarray
"""
if not color.__class__.__name__ == 'LabColor':
raise ValueError(
"Delta E functions can only be used with two LabColor objects.")
return numpy.array([color.lab_l, color.lab_a, color.lab_b])
def _get_lab_color2_matrix(color):
"""
Converts an LabColor into a NumPy matrix.
:param LabColor color:
:rtype: numpy.ndarray
"""
if not color.__class__.__name__ == 'LabColor':
raise ValueError(
"Delta E functions can only be used with two LabColor objects.")
return numpy.array([(color.lab_l, color.lab_a, color.lab_b)])
# noinspection PyPep8Naming
def delta_e_cie1976(color1, color2):
"""
Calculates the Delta E (CIE1976) of two colors.
"""
color1_vector = _get_lab_color1_vector(color1)
color2_matrix = _get_lab_color2_matrix(color2)
delta_e = color_diff_matrix.delta_e_cie1976(color1_vector, color2_matrix)[0]
return numpy.asscalar(delta_e)
# noinspection PyPep8Naming
def delta_e_cie1994(color1, color2, K_L=1, K_C=1, K_H=1, K_1=0.045, K_2=0.015):
"""
Calculates the Delta E (CIE1994) of two colors.
K_l:
0.045 graphic arts
0.048 textiles
K_2:
0.015 graphic arts
0.014 textiles
K_L:
1 default
2 textiles
"""
color1_vector = _get_lab_color1_vector(color1)
color2_matrix = _get_lab_color2_matrix(color2)
delta_e = color_diff_matrix.delta_e_cie1994(
color1_vector, color2_matrix, K_L=K_L, K_C=K_C, K_H=K_H, K_1=K_1, K_2=K_2)[0]
return numpy.asscalar(delta_e)
# noinspection PyPep8Naming
def delta_e_cie2000(color1, color2, Kl=1, Kc=1, Kh=1):
"""
Calculates the Delta E (CIE2000) of two colors.
"""
color1_vector = _get_lab_color1_vector(color1)
color2_matrix = _get_lab_color2_matrix(color2)
delta_e = color_diff_matrix.delta_e_cie2000(
color1_vector, color2_matrix, Kl=Kl, Kc=Kc, Kh=Kh)[0]
return numpy.asscalar(delta_e)
# noinspection PyPep8Naming
[docs]def delta_e_cmc(color1, color2, pl=2, pc=1):
"""
Calculates the Delta E (CMC) of two colors.
CMC values
Acceptability: pl=2, pc=1
Perceptability: pl=1, pc=1
"""
color1_vector = _get_lab_color1_vector(color1)
color2_matrix = _get_lab_color2_matrix(color2)
delta_e = color_diff_matrix.delta_e_cmc(
color1_vector, color2_matrix, pl=pl, pc=pc)[0]
return numpy.asscalar(delta_e)
3.4 δE 2000
在δE 2000的计算过程中,有WHT-L、WHT-C、WHT-H三个权重参数的设置。
下面的Python实现是我根据EasyRGB上的脚本进行改动实现的。
import math
# 第一个颜色的Lab值
CIE_L1, CIE_a1, CIE_b1 = 60, 5, 5
# 第二个颜色的Lab值
CIE_L2, CIE_a2, CIE_b2 = 62, 5, 5
WHT_L, WHT_C