波长与颜色的对应关系之python实现

自作多情 提交于 2019-12-09 17:06:35

光的颜色

与其说颜色是光的一种属性,不如说是人眼对可见光频率范围内的一种感应,是人眼的一种属性。而人眼对光频的感应包括三个方面,即明度、色调和饱和度。

其中,

  • 明度与光强有关
  • 色调反应的是光的频率信息
  • 饱和度表示图像上的颜色与光谱色的接近程度

当不考虑色调、饱和度为0的时候,只考虑明度,则色彩感消失,就是所谓的黑白图像,或者更严格地说是灰度图像。

色调与饱和度虽然反应色光的频率信息,但表现的是人眼对光的反馈特性,与光的频率是不同的物理量。其与光的频率之间的对应关系也是由人眼的感光细胞决定的。由于自然界中并不存在严格的单色光,人的色觉其实是人眼的两种感光细胞所产生的色觉的混合。

也就是说,几种基准频率的光按照不同的饱和度互相混合,人眼将会感受到颜色的变化,也可以说是产生新的颜色。实验表明,任意三种不能互相转换的颜色可以通过互相混合完成对光谱色的一一对应。这样的三种光叫做三原色,一般选取红绿蓝作为三原色。

根据CIE(国际照明委员会)在1931年所推荐的RGB色度学系统,规定三原色为(R):λ=700nm,绿(G):λ=546.1nm,(B):λ=435.8nm红(R):\lambda=700nm,绿(G):\lambda=546.1nm,蓝(B):\lambda=435.8nm

有了三原色,就能够通过混合匹配出其他任意一种颜色,在色度学中,三原色在混合过程中的相对剂量被定义为刺激值\textbf{刺激值}。将三刺激值进行归一化,得到的值被定义为色品\textbf{色品}

例如,某种颜色的颜色方程为

(C)=R+B+G(C)=R+B+G

其中,RR表示红色刺激值,GG表示绿色刺激值,BB表示蓝色刺激值,则其对应的色品方程为

{r=RR+G+Bg=GR+G+Bb=BR+G+B\left\{\begin{aligned} r =&\frac{R}{R+G+B}\\ g =&\frac{G}{R+G+B}\\ b =&\frac{B}{R+G+B} \end{aligned}\right.

由于三者已经归一化,所以只要知道其中两个值就能够确定色品,以rr为横坐标,gg为纵坐标,就能够表示出所有可能的色品。这种图大家并不陌生,在手机发布会上最常见,这里我们画出一个类似的图样

在这里插入图片描述

def CIE_RGB():
    rgb = np.zeros([1000,1000,3])
    r = np.arange(0,1,0.001)
    r,g = np.meshgrid(r,r)
    rgb[:,:,0] = r
    rgb[:,:,1] = g
    rgb[:,:,2] = 1-r-g
    over = rgb[:,:,2]<0
    rgb[over,:]=[1,1,1]

    #将色品转化成RGB
    maxVal = np.max(rgb,2)
    for i in [0,1,2]:
        rgb[:,:,i] /= maxVal
    #rgb[rgb<0]=0

    ax = plt.gca()
    ax.imshow(rgb)
    ax.invert_yaxis()    #plt显示图片时y轴从上到下,所以反置
    # 坐标映射
    plt.xticks(range(0,1001,200),['0','0.2','0.4','0.6','0.8','1'])
    plt.yticks(range(0,1001,200),['0','0.2','0.4','0.6','0.8','1'])
    plt.show()

然而根据实验测得,RGB系统的刺激值存在负值,也就是说上图中其实并没有将所有的颜色纳入其中,所以CIE又推荐了XYZ色度学系统,其本质上是对RGB系统的线性变换,最终让该系统所对应的刺激值为正数。其公式为

[XYZ]=[0.490.310.20.176970.81240.0106300.010.99][RGB]\begin{bmatrix} X\\Y\\Z\end{bmatrix} =\begin{bmatrix} 0.49&0.31&0.2\\0.17697&0.8124&0.01063\\ 0&0.01&0.99\end{bmatrix} \begin{bmatrix} R\\G\\B\end{bmatrix}

而XYZ系统下的色品图与RGB类似,都是对XYZ的归一化。CIE1931的标准色度观察者数据给出了光谱三刺激值与波长之间的关系,编程过程只是对这个关系的简单插值,所以并不列出。

在此我们给出出一个简化的公式,用以绘制光谱对应的色度

rgb(λ)={[λ440440380,0,1],λ[380,440)[0,λ440490440,1],λ[440,490)[0,1,λ510510490],λ[490,510)[λ510580510,1,0],λ[510,580)[1,λ645645580],λ[580,645)[1,0,0],λ[645,760) rgb(\lambda)=\left\{\begin{aligned} &[-\frac{\lambda-440}{440-380},0,1],&\lambda\in[380,440)\\ &[0,\frac{\lambda-440}{490-440},1],&\lambda\in[440,490)\\ &[0,1,-\frac{\lambda-510}{510-490}],&\lambda\in[490,510)\\ &[\frac{\lambda-510}{580-510},1,0],&\lambda\in[510,580)\\ &[1,-\frac{\lambda-645}{645-580}],&\lambda\in[580,645)\\ &[1,0,0],&\lambda\in[645,760)\\ \end{aligned}\right.

python实现为

#dWave为波长;maxPix为最大值;gamma为调教参数
def getRGB(dWave,maxPix=1,gamma=1):
    waveArea = [380,440,490,510,580,645,780]
    minusWave = [0,440,440,510,510,645,780]
    deltWave = [1,60,50,20,70,65,35]
    for p in range(len(waveArea)):
        if dWave<waveArea[p]:
            break

    pVar = abs(minusWave[p]-dWave)/deltWave[p]
    rgbs = [[0,0,0],[pVar,0,1],[0,pVar,1],[0,1,pVar],
            [pVar,1,0],[1,pVar,0],[1,0,0],[0,0,0]]
    
    #在光谱边缘处颜色变暗
    if (dWave>=380) & (dWave<420):
        alpha = 0.3+0.7*(dWave-380)/(420-380)
    elif (dWave>=420) & (dWave<701):
        alpha = 1.0
    elif (dWave>=701) & (dWave<780):
        alpha = 0.3+0.7*(780-dWave)/(780-700)
    else:
        alpha = 0       #非可见区

    return [maxPix*(c*alpha)**gamma for c in rgbs[p]]

绘制光谱

def drawSpec():
    pic = np.zeros([100,360,3])
    rgb = [getRGB(d) for d in range(400,760)]
    pic = pic+rgb
    plt.imshow(pic)
    plt.yticks([])      #隐藏y坐标轴
    plt.xticks(range(0,360,50),['400','450','500','550','600','650','700','750'])
    plt.show()

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!