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);
}
}
}