🎯要点
- 深度学习网络两种选择的强制选择对比度检测
- 贝叶斯自适应估计对比敏感度函数
- 空间观察对比目标
- 量化视觉皮质感知差异
- 亮度、红/绿值、蓝/黄值色彩空间改变OpenCV图像对比度
- 对比敏感度函数模型
- 空间对比敏感度估计
- 眼球运动医学研究
- 空间时间颜色偏心率对比敏感度函数模型
JavaScript人眼颜色对比差异
- sRGB:sRGB 是一种三刺激色彩模型,是 Web 的标准,用于大多数计算机显示器。它使用与高清电视标准 Rec709 相同的原色和白点。sRGB 与 Rec709 的区别仅在于传输曲线,通常称为伽马。
- 伽马:这是用于存储和传输各种图像编码方法的曲线。它通常类似于人类视觉的感知曲线。在数字中,伽马的作用是赋予图像较暗区域更多权重,以便由更多位定义它们,从而避免出现诸如“带状”之类的伪影。
- 亮度:(记为 L 或 Y):光的线性测量或表示(即无伽马曲线)。测量单位通常是 cd/m2。表示单位是 Y,如 CIEXYZ,通常为 0(黑色)至 100(白色)。亮度具有光谱加权,基于人类对不同波长光的感知。但是,亮度在明暗度方面是线性的 - 也就是说,如果 100 个光子测量值为 10,那么 20 个光子就是 200 个光子。
- L* (又名 Lstar):感知亮度,由 CIELAB 定义 ( L ∗ a ∗ b ∗ ) \left.L^* a^* b^*\right) L∗a∗b∗) 其中亮度与光量呈线性关系, L ⋆ L ^{\star} L⋆ 基于感知,因此在光量方面是非线性的,其曲线旨在匹配人眼的明视觉(伽马约为 ∧ 0.43 { }^{\wedge} 0.43 ∧0.43 )。
亮度 vs L : ∗ 0 L:^* 0 L:∗0 和 100 在光亮(写为 Y Y Y 或 L )和亮度(写为 L ∗ L^* L∗ )方面是相同的,但在中间它们却非常不同。我们确定的中间灰色位于 L ∗ L^* L∗ 的中间 50 处,但这与亮度 (Y) 中的 18.4 相关。在 sRGB 中,这是 #777777 或 46.7 % 46.7 \% 46.7%。
对比度:定义两个 L 或两个 Y 值之间差异的术语。对比有多种方法和标准。一种常见的方法是韦伯对比,即 Δ L / L \Delta L / L ΔL/L。对比度通常以比率 (3:1) 或百分比 (70%) 表示。
对于紫色测试示例,我使用十六进制 #9d5fb0(代表 R:157、G:95、B:176),对于绿色测试示例,我使用十六进制 #318261(代表 R:49、G:130、B:97)。
function HexToRGB(hex) {
hex = String(hex);
if(hex.length==3){hex='#'+hex.substr(0, 1)+hex.substr(0, 1)+hex.substr(1, 1)+hex.substr(1, 1)+hex.substr(2, 1)+hex.substr(2, 1);}
if(hex.length==4){hex='#'+hex.substr(1, 1)+hex.substr(1, 1)+hex.substr(2, 1)+hex.substr(2, 1)+hex.substr(3, 1)+hex.substr(3, 1);}
if(hex.length==6){hex='#'+hex;}
let R = parseInt(hex.substr(1, 2),16);
let G = parseInt(hex.substr(3, 2),16);
let B = parseInt(hex.substr(5, 2),16);
console.log("rgb from "+hex+" = "+[R,G,B]);
return [R,G,B];
}
最常见的灰度程序平均方法是:
灰度
=
round
(
(
R
+
G
+
B
)
/
3
)
灰度=\operatorname{round}((R+G+B) / 3)
灰度=round((R+G+B)/3)
function RGBToGRAY(rgb) {
let avg = parseInt((rgb[0]+rgb[1]+rgb[2])/3);
return [avg,avg,avg];
}
这会将紫色变成 #8f8f8f 因为平均值 = 143,将绿色变成#5c5c5c,因为平均值 = 92。92 和 143 之间的差异太大,会错误地通过我预期的测试。
现在,正如之前所解释的,我们应该使其呈线性并应用 gamma 2.2 校正。
R
′
∧
2.2
=
R
lin
G
′
∧
2.2
=
G
lin
B
′
∧
2.2
=
B
lin
R^{\prime \wedge} 2.2=R \operatorname{lin} G^{\prime \wedge} 2.2=G \operatorname{lin} B^{\prime \wedge} 2.2=B \operatorname{lin}
R′∧2.2=RlinG′∧2.2=GlinB′∧2.2=Blin
function linearFromRGB(rgb) {
let R = rgb[0]/255.0;
let G = rgb[1]/255.0;
let B = rgb[2]/255.0;
let gamma = 2.2;
R = Math.pow(R, gamma);
G = Math.pow(G, gamma);
B = Math.pow(B, gamma);
let linear = [R,G,B];
console.log('linearized rgb = '+linear);
return linear;
}
紫色的伽马校正线性结果现在为
R
:
0.3440
、
G
:
0.1139
、
B
:
0.4423
R:0.3440、G:0.1139、B:0.4423
R:0.3440、G:0.1139、B:0.4423,绿色的结果为
R
:
0.0265
、
G
:
0.2271
、
B
:
0.1192
R:0.0265、G:0.2271、B:0.1192
R:0.0265、G:0.2271、B:0.1192。现在通过应用系数获得亮度 L 或(XYZ 比例的 Y)如下:
Y
=
Rlin*
0.2126
+ Glin*
0.7152
+
Blin*
0.0722
Y=\text { Rlin* } 0.2126 \text { + Glin* } 0.7152+\text { Blin* } 0.0722
Y= Rlin* 0.2126 + Glin* 0.7152+ Blin* 0.0722
function luminanceFromLin(rgblin) {
let Y = (0.2126 * (rgblin[0]));
Y = Y + (0.7152 * (rgblin[1]));
Y = Y + (0.0722 * (rgblin[2]));
console.log('luminance from linear = '+Y);
return Y;
}
现在两个 Y(或 L)值之间的感知对比度:
(L较亮 - L较暗) / (L较亮 + 0.1)
\text { (L较亮 - L较暗) / (L较亮 + 0.1) }
(L较亮 - L较暗) / (L较亮 + 0.1)
function perceivedContrast(Y1,Y2){
let C = ((Math.max(Y1,Y2)-Math.min(Y1,Y2))/(Math.max(Y1,Y2)+0.1));
console.log('perceived contrast from '+Y1+','+Y2+' = '+C);
return C;
}
现在所有上述功能合并为一步输入/输出
function perceivedContrastFromHex(hex1,hex2){
let lin1 = linearFromRGB(HexToRGB(hex1));
let lin2 = linearFromRGB(HexToRGB(hex2));
let y1 = luminanceFromLin(lin1);
let y2 = luminanceFromLin(lin2);
return perceivedContrast(y1,y2);
}
测试
var P = perceivedContrastFromHex('#318261','#9d5fb0');
alert(P);
// shows 0.034369592139888626
var P = perceivedContrastFromHex('#000','#fff');
alert(P);
// shows 0.9090909090909091
完整解析代码
const Color_Parser = {
version: '1.0.0.beta',
name: 'Color_Parser',
result: null,
loging: true,
parseHex: function(_input) {
if (this.loging) {
console.log(this.name + ', input: ' + _input);
}
this.result = {};
if (!_input) {
this.result.error = true;
console.log(this.name + ', error');
return this.result;
}
this.result.hex = String(_input);
if (this.result.hex.length == 3) {
this.result.hex = '#' + this.result.hex.substr(0, 1) + this.result.hex.substr(0, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1);
}
if (this.result.hex.length == 4) {
this.result.hex = '#' + this.result.hex.substr(1, 1) + this.result.hex.substr(1, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(2, 1) + this.result.hex.substr(3, 1) + this.result.hex.substr(3, 1);
}
if (this.result.hex.length == 6) {
this.result.hex = '#' + this.result.hex;
}
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.hex);
}
this.result.rgb = {
r: null,
g: null,
b: null
};
this.result.rgb.r = parseInt(this.result.hex.substr(1, 2), 16);
this.result.rgb.g = parseInt(this.result.hex.substr(3, 2), 16);
this.result.rgb.b = parseInt(this.result.hex.substr(5, 2), 16);
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.rgb);
}
this.result.int = ((this.result.rgb.r & 0x0ff) << 16) | ((this.result.rgb.g & 0x0ff) << 8) | (this.result.rgb.b & 0x0ff);
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.int);
}
this.result.dec = {
r: null,
g: null,
b: null
};
this.result.dec.r = this.result.rgb.r / 255.0;
this.result.dec.g = this.result.rgb.g / 255.0;
this.result.dec.b = this.result.rgb.b / 255.0;
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.dec);
}
this.result.lin = {
r: null,
g: null,
b: null
};
for (var i = 0, len = 3; i < len; i++) {
if (this.result.dec[['r', 'g', 'b'][i]] <= 0.04045) {
this.result.lin[['r', 'g', 'b'][i]] = this.result.dec[['r', 'g', 'b'][i]] / 12.92;
} else {
this.result.lin[['r', 'g', 'b'][i]] = Math.pow(((this.result.dec[['r', 'g', 'b'][i]] + 0.055) / 1.055), 2.4);
}
}
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.lin);
}
this.result.y = (0.2126 * (this.result.lin.r)); // red channel
this.result.y += (0.7152 * (this.result.lin.g)); // green channel
this.result.y += (0.0722 * (this.result.lin.b)); // blue channel
if (this.loging) {
console.log(this.name + ', added to result: ' + this.result.y);
}
this.result.invert = {
r: null,
g: null,
b: null,
hex: null
};
this.result.invert.r = (255 - this.result.rgb.r);
this.result.invert.g = (255 - this.result.rgb.g);
this.result.invert.b = (255 - this.result.rgb.b);
this.result.invert.hex = this.result.invert.b.toString(16);
if (this.result.invert.hex.length < 2) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = this.result.invert.g.toString(16) + this.result.invert.hex;
if (this.result.invert.hex.length < 4) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = this.result.invert.r.toString(16) + this.result.invert.hex;
if (this.result.invert.hex.length < 6) {
this.result.invert.hex = '0' + this.result.invert.hex;
}
this.result.invert.hex = '#' + this.result.invert.hex;
this.result.error = false;
if (this.loging) {
console.log(this.name + ', final output:');
}
if (this.loging) {
console.log(this.result);
}
return this.result;
}
}