光刺激视网膜上不同种类的视锥细胞而后由大脑处理刺激信号而产生了人对光的颜色感知,因此要标定颜色必须要研究人眼对光的颜色主观感知特性而非光作为电磁波的客观属性。在20世纪20年代末期,英国的莱特( W. D. Wright)和吉尔德( J. Guild)独立开展了一系列关于色彩匹配的实验。这些实验的结果是色度学计算的基础而正是在此基础上1931年CIE规定了1931 CIE 标准色度学系统。遗憾的是,由于年代久远,很多数据已经遗失,这里我们以吉尔德的实验为主展开讨论。
在莱特和吉尔德的年代,人们所测得的数据且能成为规范的可能只有CIE 1924 明视觉曲线,因此实验之前需要一定的假设。假设一:三束等能三色光混合得到白光。个人认为,这基于理想白光具有等能光谱的特性,而我们可以认为蓝光控制了光谱上的短波段,绿光控制中波段,红光控制长波段。一旦三种波段的能量一致,那么混合的效果应该就是白光。还要注意,现实中等能白光是不存在的,我们只能寻找相似的替代品,吉尔德用的是NPL光源。我们应把这种光源看成理想白光的近似,即把NPL光源看作理想白光。假设二:格拉斯曼混色定律中的亮度相加律成立。
如图,这是吉尔德在文章《A trichromatic colorimeter suitable for standardisation work》中的一幅插图。左侧是实验装置的俯视图,右侧的方框是产生三原色所用的滤光片的构造。
示意图中最左侧是一盏特制的灯泡,它可以在空间中发出亮度的非常均匀灯光。它的右侧是一个凸透镜,它将灯泡发出的光转化为一束平行光。平行光会打在右侧的箱子上,箱子左边立面的结构如图(a)所示。其中阴影部分为三种滤光片。滤光片处于一个环上,环上中间的空缺部分是遮光片,遮光片可以帮助调节三种色光的分配比例。理论上滤光片与其对应的遮光片都应对应环上的
同时,遮光片上还刻有按角度计量的刻度,我们可以用这些刻度计算出每种颜色的通光面积,而通光面积之比即为色彩数量之比。
待匹配的颜色从箱子的I处射入,射入前单色光先照射到S处放置的漫反射物体(如纸张、纤维等)上,而漫反射物体恰好放置在I处凸透镜的焦点位置。据吉尔德所述,这样的放置使得H处的观察者看不到漫反射物体的具体结构,而只有均匀明度的单色光。
L与M处的棱镜可以将左侧的平行光转移到接近M的一个小孔中垂直立面射入。这书光的作用便是将一种色光(不一定是三原色的一种)加入到待匹配的色光中。首先它经过可以装滤色片的转盘N,然后再经过透镜K转为平行光。此后,它在玻璃片J上与待匹配色光混合。
最后,F是两块棱镜的叠加。它们在俯视图上的位置如图,而在高度上两者的斜边上只有一半面积相对。F与凸透镜之间有上下两块各带有9mm宽4.5mm高的小隔板。当调整合适时,隔板之间的缝隙就会消失。而长管H可以将观察者视线控制在
{% hint style="info" %} F与透镜G之间的挡板在图上没有表示,但吉尔德原文第10页中间确有提到“A stop with a square opening of 9 mm. side is fitted between the cube and the lens G (Fig. 2).”个人猜测,这可能是扫描中的错误。 {% endhint %}
观察者从长管H处观察两种色光的匹配情况,匹配完成后,即可按遮光片上的度数获取光度值。如果只使用三原色无法达成匹配,很可能需要将另一种颜色加入待匹配颜色中。但如果这种颜色加入后达到匹配,还要关闭S处的光源,将加入的光源单独进行匹配。
由实验假设一节知,在匹配白光时应该使用的是三种等能白色光。对于上述仪器而言,就等价于当匹配白色光时,三种色光的遮光片开口一致。如果一位观察者使用了开口为20度的蓝光、5度的绿光、10度的红光来匹配一束白光。为了与我们的假设相符,这位观察者使用的绿光应乘上4,使用的红光应乘上2。那么此后的所有观察中,他的数据就都要乘上这样的数值。如果一次匹配中观察者使用了3度的蓝光、10度的绿光、5度的红光,那校准后的数值应为3度的蓝光、40度的绿光、10度的红光。这种做法的确是武断的、没有太多的科学依据,但最后这样的数据又可以回头验证假设一的成立。
首先,将每个波长下实验测得的三原色所需的量标准化使得其和为
虽然我们用三原色坐标很好的表示了目标颜色的色度信息,但为了让特定波长的单色光看上去与三原色组成的混合色完全一致,还需要匹配亮度信息,这样,两者之间的色彩三要素就会完全匹配。
事实表明,等量三原色的色光之间的亮度存在差异,而为了量化不同三基色坐标的颜色组合的亮度,需要引入亮度因子的概念。吉尔德在文章《The Colorimetric Properties 0f The Spectrum》中最终确定在他实验中所用三原色的亮度之间比例为
注意,三者之间的比例最为重要而不是绝对大小。这样的比例意味着等量绿光亮度是红光的4.39倍,是蓝光的91.46倍。对于三原色坐标确定的颜色,它的亮度即可以用它对应的亮度因子
比如,吉尔德测得500nm的单色光的三原色坐标为
亮度因子也可以理解为之前标准化后的三原色坐标直接相加形成一个光总量为
上一节中我们讲到匹配一定能量
我们知道一般的光源都是非理想的,即
{% hint style="info" %}
注意区分光谱三刺激值与三刺激值。光谱三刺激值是匹配等能光谱(即
这里已经把吉尔德的实验结果拷贝到了data/Wright_guild.csv
文件中,我们借助pandas
包读取它,并借助numpy
及matplotlib
加以分析。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Guild_data = pd.read_csv('data/Wright_Guild.csv')
Guild_data = Guild_data.set_index('wavelength')
我们绘制出作者所使用的NPL光源的相对能量分布曲线,显然它不是理想的白光,但因为人眼看上去是白色的,所以我们可以认为它具有相同的三刺激值。当然,这就是假设一,而下文我们就会做数值验证。
Guild_data['E_lambda'].plot(color=['k'])
我们可以尝试在麦克斯韦三角形中画出三原色坐标。为了使图标美观,我们把图表右边和上面的直线抹去,然后将坐标轴移动至相交于(0, 0)
处。
ax = plt.gca()
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position(('data', 0))
ax.spines['left'].set_position(('data', 0))
coef = Guild_data[['r', 'g', 'b']].values.T
plt.scatter(coef[0], coef[1])
当然,还有一种表达即将三个三原色坐标以波长为x轴画出。
Guild_data[['r', 'g', 'b']].plot(color=['r', 'g', 'b'])
现在,我们按上文所述亮度因子的公式求出每个波长上的值和它们对应的三刺激值,再把它们绘制出来。
Guild_data['L_lambda'] = 1.00*Guild_data['r'] + 4.390*Guild_data['g'] + 0.048*Guild_data['b']
Guild_data['r_'] = Guild_data['r'] * Guild_data['V_lambda'] / Guild_data['L_lambda']
Guild_data['g_'] = Guild_data['g'] * Guild_data['V_lambda'] / Guild_data['L_lambda']
Guild_data['b_'] = Guild_data['b'] * Guild_data['V_lambda'] / Guild_data['L_lambda']
Guild_data[['r_', 'g_', 'b_']].plot(color=['r', 'g', 'b'])
从上文知道,光源的三刺激值的求法为颜色匹配函数乘上光源能量再对波长进行数值积分。我们可以发现三个三刺激值的数值大小近似相等。这里,我们验证了假设一的成立。
r_sum = np.sum(Guild_data['r_']*Guild_data['E_lambda'])
g_sum = np.sum(Guild_data['g_']*Guild_data['E_lambda'])
b_sum = np.sum(Guild_data['b_']*Guild_data['E_lambda'])
r_sum, g_sum, b_sum
(3.583893668285496, 3.5913792051088373, 3.5908811663897824)
我们还可以验证三原色相加之后亮度与NPL光源是一致的,即亮度相加率的成立。我们再次利用亮度因子中的亮度关系。注意我们之前说过亮度关系只有比值关系,这里运算时要保持使用的绝对数值与上文一致。计算出的亮度关系再线性叠加后发现明度确实相近。
Guild_data['light'] = Guild_data['E_lambda'] * Guild_data['V_lambda']
trim_value_r = 1.00*Guild_data['r_']*Guild_data['E_lambda']
trim_value_g = 4.390*Guild_data['g_']*Guild_data['E_lambda']
trim_value_b = 0.048*Guild_data['b_']*Guild_data['E_lambda']
light_sum = np.sum(Guild_data['light'])
r_sum_ = np.sum(trim_value_r)
g_sum_ = np.sum(trim_value_g)
b_sum_ = np.sum(trim_value_b)
light_sum, r_sum_ + g_sum_ + b_sum_
(19.5224106747, 19.522410674699998)