光的顏色
與其說顏色是光的一種屬性,不如說是人眼對可見光頻率範圍內的一種感應,是人眼的一種屬性。而人眼對光頻的感應包括三個方面,即明度、色調和飽和度。
其中,
- 明度與光強有關
- 色調反應的是光的頻率信息
- 飽和度表示圖像上的顏色與光譜色的接近程度
當不考慮色調、飽和度爲0的時候,只考慮明度,則色彩感消失,就是所謂的黑白圖像,或者更嚴格地說是灰度圖像。
色調與飽和度雖然反應色光的頻率信息,但表現的是人眼對光的反饋特性,與光的頻率是不同的物理量。其與光的頻率之間的對應關係也是由人眼的感光細胞決定的。由於自然界中並不存在嚴格的單色光,人的色覺其實是人眼的兩種感光細胞所產生的色覺的混合。
也就是說,幾種基準頻率的光按照不同的飽和度互相混合,人眼將會感受到顏色的變化,也可以說是產生新的顏色。實驗表明,任意三種不能互相轉換的顏色可以通過互相混合完成對光譜色的一一對應。這樣的三種光叫做三原色,一般選取紅綠藍作爲三原色。
根據CIE(國際照明委員會)在1931年所推薦的RGB色度學系統,規定三原色爲。
有了三原色,就能夠通過混合匹配出其他任意一種顏色,在色度學中,三原色在混合過程中的相對劑量被定義爲。將三刺激值進行歸一化,得到的值被定義爲。
例如,某種顏色的顏色方程爲
其中,表示紅色刺激值,表示綠色刺激值,表示藍色刺激值,則其對應的色品方程爲
由於三者已經歸一化,所以只要知道其中兩個值就能夠確定色品,以爲橫座標,爲縱座標,就能夠表示出所有可能的色品。這種圖大家並不陌生,在手機發佈會上最常見,這裏我們畫出一個類似的圖樣
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系統下的色品圖與RGB類似,都是對XYZ的歸一化。CIE1931的標準色度觀察者數據給出了光譜三刺激值與波長之間的關係,編程過程只是對這個關係的簡單插值,所以並不列出。
在此我們給出出一個簡化的公式,用以繪製光譜對應的色度
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()