1 光
視覺系統要從光開始說起。光作爲電磁波在空間中傳播,也是一種粒子流(光子)。
可見光區大致爲 380 ~ 740 nm
.
2 人眼
眼睛主要有2種感光細胞:
cone cells
:視錐細胞。細節,色彩視覺,需要很多光子激活,亮光。集中在視網膜中心位置。rod cells
:視杆細胞。低光,單色視覺。分散在視網膜兩外邊。
3 顏色
3.1 感知亮度
波長不同,感知的亮度也是不同的。感知亮度和波長曲線如下圖:
This is hard to see |
This is easy to see |
3.2 三原色
每個感光細胞都有一個響應曲線,對不同波長光反應有所不同:
- 視杆細胞:峯值在
498nm
附近。 - 視錐細胞:3種,短的峯值在
420nm
附近,中等的峯值在530nm
附近,長的峯值在560nm
附近。
1920s年代末期,William Wright 和 John Guild等認爲三種視錐細胞的感知峯值分別在藍色,綠色,紅色上(現在技術已經證明這一結論並不是完全準確,但對他們得出的結論並不影響),通過實驗發現控制3中“基本光”可以獲得所有的顏色。
4 顏色空間
顏色空間用來表示儘可能多的視覺系統可以感知的顏色,不同的顏色空間可以表示的顏色範圍可能是不同的。
4.1 XYZ color space
XYZ
color space是根據人類視覺系統對顏色的響應而做的,即視錐細胞對可見光譜的響應。CIE通過大量實驗獲得了CIE標準觀察顏色匹配函數
可以用以下公式將光譜轉化爲X, Y, Z 三個值:
其中 代表光譜強度。
4.2 rgb color space
圖5展示了rgb的顏色匹配函數,其中在438.1~546.1nm
之間R值出現了負值,這說明有一部分顏色是rgb空間不能包含的。
- XYZ RGB
4.3 Hue color space
- Hue: 色調,從紅色開始按逆時針方向計算,紅色爲0. 範圍取
[0,1)
- Saturation: 飽和度,表示顏色接近光譜色的程度。
- Value:明度,表示顏色明亮的程度。
將紅,黃,綠,青,藍,品紅6種顏色均勻放在一條軸上,再採用線性插值的辦法得到色調。
- RGB HSV
參考C代碼:
定義簡單的image
結構體:
typedef struct{
int w,h,c;
float *data;
} image;
色彩空間轉換函數:
void rgb_to_hsv(image im)
{
assert(im.c == 3);
int i, j;
float r, g, b;
float h, s, v;
for(j = 0; j < im.h; j++) {
for(i = 0; i < im.w; i++) {
r = get_pixel(im, i, j, 0);
g = get_pixel(im, i, j, 1);
b = get_pixel(im, i, j, 2);
float maxVal = three_way_max(r, g, b);
float minVal = three_way_min(r, g, b);
float delta = maxVal - minVal;
// compute Value
v = maxVal;
// compute Saturation and Hue
if(maxVal == 0) {
s = 0;
h = 0;
} else {
s = delta / maxVal;
if(delta == 0) {
h = 0;
} else if(v == r) {
h = (g - b) / delta;
} else if (v == g) {
h = (b - r) / delta + 2;
} else if (v == b) {
h = (r - g) / delta + 4;
}
if(h < 0) h += 6;
h /= 6;
}
set_pixel(im, i, j, 0, h);
set_pixel(im, i, j, 1, s);
set_pixel(im, i, j, 2, v);
}
}
}
- HSV RGB
參考C代碼:
void hsv_to_rgb(image im)
{
assert(im.c == 3);
int i, j;
float h, s, v;
float r, g, b;
for(j = 0; j < im.h; j++) {
for(i = 0; i < im.w; i++) {
h = 6 * get_pixel(im, i, j, 0);
s = get_pixel(im, i, j, 1);
v = get_pixel(im, i, j, 2);
float maxVal = v;
float minVal = v * (1 - s);
float delta = v * s;
int hInt = floor(h);
// float deltaH = h - hInt;
// float p = minVal + delta * deltaH;
// float q = minVal + delta * (1 - deltaH);
float val = minVal + delta * (1 - fabs(fmod(h, 2) - 1));
switch(hInt) {
case 0:
r = maxVal; b = minVal; g = val; break; // g = p;
case 1:
g = maxVal; b = minVal; r = val; break; // r = q;
case 2:
g = maxVal; r = minVal; b = val; break; // b = p;
case 3:
b = maxVal; r = minVal; g = val; break; // g = q;
case 4:
b = maxVal; g = minVal; r = val; break; // r = p;
default:
r = maxVal; g = minVal; b = val; // b = q;
}
set_pixel(im, i, j, 0, r);
set_pixel(im, i, j, 1, g);
set_pixel(im, i, j, 2, b);
}
}
}