确定RGB颜色感知亮度的公式

我正在寻找某种公式或算法来确定给定RGB值的颜色的亮度。我知道这不像把RGB值加在一起那么简单,更高的总和更亮,但我有点不知所措,不知道从哪里开始。

413602 次浏览

请定义亮度。如果你想知道颜色有多接近白色,你可以使用欧氏距离 from (255, 255, 255)

我认为你正在寻找的是RGB -> 亮度转换公式。

光度/数字ITU BT.709:

Y = 0.2126 R + 0.7152 G + 0.0722 B

数字ITU BT.601(赋予R和B组件更多的权重):

Y = 0.299 R + 0.587 G + 0.114 B

如果你愿意用准确性来换取性能,有两个近似公式:

Y = 0.33 R + 0.5 G + 0.16 B


Y = 0.375 R + 0.5 G + 0.125 B

这些可以快速计算为

Y = (R+R+B+G+G+G)/6


Y = (R+R+R+B+G+G+G+G)>>3

方法可以根据您的需要而有所不同。以下是计算亮度的3种方法:

  • 亮度(某些颜色空间的标准):(0.2126*R + 0.7152*G + 0.0722*B) source < img src = " https://i.stack.imgur.com/NJ1Ci.png " alt = " img " / > < / p >

  • 亮度(感知选项1):(0.299*R + 0.587*G + 0.114*B) source < img src = " https://i.stack.imgur.com/ut7jI.png " alt = " img " / > < / p >

  • 亮度(感知选项2,计算较慢):sqrt( 0.241*R^2 + 0.691*G^2 + 0.068*B^2 )sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )(感谢@MatthewHerbst) source < img src = " https://i.stack.imgur.com/A2x05.png " alt = " img " / > < / p >

[编辑:添加了使用命名的css颜色按每种方法排序的示例。

HSV颜色空间应该可以做到这一点,参见< >强维基百科文章< / >强,这取决于你正在使用的语言,你可能会得到一个库转换。

H是色调,是颜色的数值(即红色,绿色…)

S是颜色的饱和度,即它有多“强烈”

V是颜色的亮度。

亮度值= 0.3 R + 0.59 G + 0.11 B

http://www.scantips.com/lumin.html

如果你想知道颜色有多接近白色你可以用欧几里得距离(255,255,255)

我认为RGB颜色空间相对于L2欧几里得距离是感知上不均匀的。 均匀空间包括CIE LAB和LUV

HSV的“V”可能就是你要找的。MATLAB有一个rgb2hsv函数,之前引用的维基百科文章充满了伪代码。如果RGB2HSV转换不可行,则较不准确的模型将是图像的灰度版本。

再加上其他人说的话:

所有这些方程在实践中都工作得很好,但如果你需要非常精确,你必须首先将颜色转换为线性颜色空间(应用逆图像-gamma),做三原色的权重平均,如果你想显示颜色- 将亮度返回到监视器gamma.

在深灰色中,忽略伽玛和正确伽玛之间的亮度差异高达20%。

下面是将sRGB图像转换为灰度的唯一正确算法,如在浏览器等中使用。

在计算内积之前,有必要对颜色空间应用伽玛函数的逆。然后你把函数应用到减少的值上。未能合并gamma函数可能导致高达20%的误差。

对于典型的计算机,颜色空间是sRGB。sRGB的正确数字约为。0.21 0.72 0.07。sRGB的Gamma是一个复合函数,近似取幂1/(2.2)。这是c++的全部内容。

// sRGB luminance(Y) values
const double rY = 0.212655;
const double gY = 0.715158;
const double bY = 0.072187;


// Inverse of sRGB "gamma" function. (approx 2.2)
double inv_gam_sRGB(int ic) {
double c = ic/255.0;
if ( c <= 0.04045 )
return c/12.92;
else
return pow(((c+0.055)/(1.055)),2.4);
}


// sRGB "gamma" function (approx 2.2)
int gam_sRGB(double v) {
if(v<=0.0031308)
v *= 12.92;
else
v = 1.055*pow(v,1.0/2.4)-0.055;
return int(v*255+0.5); // This is correct in C++. Other languages may not
// require +0.5
}


// GRAY VALUE ("brightness")
int gray(int r, int g, int b) {
return gam_sRGB(
rY*inv_gam_sRGB(r) +
gY*inv_gam_sRGB(g) +
bY*inv_gam_sRGB(b)
);
}

有趣的是,此公式为RGB=>HSV只是使用v=MAX3(r,g,b)。换句话说,你可以使用(r,g,b)的最大作为HSV中的V。

我检查了,在赫恩,贝克的第575页,这也是他们计算“值”的方法。

From Hearn&Baker pg 319

与其迷失在这里提到的随机选择的公式中,我建议您使用W3C标准推荐的公式。

下面是Wcag 2.0 sc 1.4.3 相对亮度对比度公式的简单而精确的PHP实现。它生成的值适合于评估符合WCAG要求的比率,就像这个页面中那样,因此适用于任何web应用程序。这对于移植到其他语言来说是微不足道的。

/**
* Calculate relative luminance in sRGB colour space for use in WCAG 2.0 compliance
* @link http://www.w3.org/TR/WCAG20/#relativeluminancedef
* @param string $col A 3 or 6-digit hex colour string
* @return float
* @author Marcus Bointon <marcus@synchromedia.co.uk>
*/
function relativeluminance($col) {
//Remove any leading #
$col = trim($col, '#');
//Convert 3-digit to 6-digit
if (strlen($col) == 3) {
$col = $col[0] . $col[0] . $col[1] . $col[1] . $col[2] . $col[2];
}
//Convert hex to 0-1 scale
$components = array(
'r' => hexdec(substr($col, 0, 2)) / 255,
'g' => hexdec(substr($col, 2, 2)) / 255,
'b' => hexdec(substr($col, 4, 2)) / 255
);
//Correct for sRGB
foreach($components as $c => $v) {
if ($v <= 0.04045) {
$components[$c] = $v / 12.92;
} else {
$components[$c] = pow((($v + 0.055) / 1.055), 2.4);
}
}
//Calculate relative luminance using ITU-R BT. 709 coefficients
return ($components['r'] * 0.2126) + ($components['g'] * 0.7152) + ($components['b'] * 0.0722);
}


/**
* Calculate contrast ratio acording to WCAG 2.0 formula
* Will return a value between 1 (no contrast) and 21 (max contrast)
* @link http://www.w3.org/TR/WCAG20/#contrast-ratiodef
* @param string $c1 A 3 or 6-digit hex colour string
* @param string $c2 A 3 or 6-digit hex colour string
* @return float
* @author Marcus Bointon <marcus@synchromedia.co.uk>
*/
function contrastratio($c1, $c2) {
$y1 = relativeluminance($c1);
$y2 = relativeluminance($c2);
//Arrange so $y1 is lightest
if ($y1 < $y2) {
$y3 = $y1;
$y1 = $y2;
$y2 = $y3;
}
return ($y1 + 0.05) / ($y2 + 0.05);
}

我已经在接受的答案中对三种算法做了比较。我循环生成颜色,大约每400个颜色使用一次。每种颜色由2x2像素表示,颜色从最深到最浅(从左到右,从上到下)排序。

第一张图片- 亮度(相对)

0.2126 * R + 0.7152 * G + 0.0722 * B

第二张图片- http://www.w3.org/TR/AERT#color-contrast

0.299 * R + 0.587 * G + 0.114 * B

第三张图片- HSP颜色模型

sqrt(0.299 * R^2 + 0.587 * G^2 + 0.114 * B^2)

第四张图- Wcag 2.0 sc 1.4.3 相对亮度对比度公式(见@Synchro的答案在这里)

根据一行中的颜色数量,有时可以在第一张和第二张图片上发现图案。我从第3或第4算法的图片上没有发现任何模式。

如果我必须选择,我会选择算法3,因为它更容易实现,比4快33%。

感知亮度算法比较

Jive Dadson的逆伽马公式需要在Javascript中实现时删除一半调整,即从函数gam_sRGB返回需要返回int(v*255);不返回int(v*255+.5);半调整四舍五入,这可能导致一个值过高的R=G=B,即灰色色彩三合。在R=G=B三位一体上的灰度转换应产生等于R的值;这是公式有效的一个证明。 请参阅九度灰阶以获得实际运行的公式(不含半调整)。< / p >

为了清晰起见,使用平方根的公式必须是

sqrt(coefficient * (colour_value^2))

sqrt((coefficient * colour_value))^2

证明这一点的证据在于将R=G=B三位一体转换为灰度R。只有当你将颜色值平方,而不是颜色值乘以系数时,这才成立。看到九度灰阶

这里有一小段C代码,可以正确地计算可感知的亮度。

// reverses the rgb gamma
#define inverseGamma(t) (((t) <= 0.0404482362771076) ? ((t)/12.92) : pow(((t) + 0.055)/1.055, 2.4))


//CIE L*a*b* f function (used to convert XYZ to L*a*b*)  http://en.wikipedia.org/wiki/Lab_color_space
#define LABF(t) ((t >= 8.85645167903563082e-3) ? powf(t,0.333333333333333) : (841.0/108.0)*(t) + (4.0/29.0))




float
rgbToCIEL(PIXEL p)
{
float y;
float r=p.r/255.0;
float g=p.g/255.0;
float b=p.b/255.0;


r=inverseGamma(r);
g=inverseGamma(g);
b=inverseGamma(b);


//Observer = 2°, Illuminant = D65
y = 0.2125862307855955516*r + 0.7151703037034108499*g + 0.07220049864333622685*b;


// At this point we've done RGBtoXYZ now do XYZ to Lab


// y /= WHITEPOINT_Y; The white point for y in D65 is 1.0


y = LABF(y);


/* This is the "normal conversion which produces values scaled to 100
Lab.L = 116.0*y - 16.0;
*/
return(1.16*y - 0.16); // return values for 0.0 >=L <=1.0
}

我想知道这些rgb系数是如何确定的。我自己做了一个实验,得出了以下结论:

Y = 0.267 R + 0.642 G + 0.091 B

接近,但与长期建立的ITU系数明显不同。我想知道这些系数是否对每个观察者来说都是不同的,因为我们眼睛视网膜上的视锥细胞和视杆细胞的数量都是不同的,尤其是不同类型的视锥细胞之间的比例可能是不同的。

供参考:

ITU BT.709:

Y = 0.2126 R + 0.7152 G + 0.0722 B

ITU BT.601:

Y = 0.299 R + 0.587 G + 0.114 B

我在亮红色、亮绿色和亮蓝色的背景上快速移动一个小灰色条,并调整灰色,直到它尽可能地融合在一起。我还用其他色调重复了这个测试。我在不同的显示器上重复了测试,即使是gamma因子固定为3.0的显示器,但在我看来都是一样的。更重要的是,ITU系数对我的眼睛来说是错误的。

是的,我对颜色的视觉应该是正常的。

为了用R确定颜色的亮度,我将RGB系统颜色转换为HSV系统颜色。

在我的脚本中,我之前因为其他原因使用了HEX系统代码,但你也可以从RGB系统代码rgb2hsv {grDevices}开始。文档是在这里

这是我的代码的这一部分:

 sample <- c("#010101", "#303030", "#A6A4A4", "#020202", "#010100")
hsvc <-rgb2hsv(col2rgb(sample)) # convert HEX to HSV
value <- as.data.frame(hsvc) # create data.frame
value <- value[3,] # extract the information of brightness
order(value) # ordrer the color by brightness

“Accepted"答案是不正确和不完整的

唯一准确的答案是@jive-dadson@EddingtonsMonkey答案,并支持@nils-pipenbrinck。其他答案(含已录用人员)链接或引用的来源,要么是错误的,不相关的,过时的,或坏的。

简要:

  • sRGB在应用系数之前必须为线性化
  • 亮度(L或Y)与光一样是线性的。
  • 感知亮度(L*)与人类感知一样是非线性的。
  • HSV和HSL在感知方面甚至远不准确。
  • sRGB的IEC标准指定了0.04045的阈值,即 0.03928(来自过时的早期草案)。
  • 为了有用(即相对于感知),欧几里得距离需要一个感知一致的笛卡尔向量空间,如CIELAB。sRGB不是其中之一。

以下是正确而完整的回答:

由于这条线索在搜索引擎中出现频率很高,我添加了这个答案来澄清关于这个主题的各种误解。

亮度是光的线性测量,对正常视力进行光谱加权,但对亮度的非线性感知不进行调整。它可以是一个相对度量,如CIEXYZ中的Y,或cd/m2 (不要与L*混淆)中的l,一个绝对度量。

被轻被一些视觉模型使用,如CIELAB,这里L* (Lstar)是知觉轻的值,并且是非线性的,以近似人类视觉的非线性响应曲线。(也就是说,对知觉是线性的,但因此对光是非线性的)。

亮度是一个感知属性,它没有“物理”属性。衡量。然而,一些颜色外观模型确实有价值,通常表示为< >强“Q" < / >强,用于感知亮度,这与感知亮度不同。

亮度 (素数)是一种伽玛编码的加权信号,用于某些视频编码(Y´I´Q´)。不要与线性亮度混淆。

γ或传输曲线(TRC)是一种通常类似于感知曲线的曲线,通常应用于图像数据的存储或广播,以减少感知噪声和/或提高数据利用率(及相关原因)。

以确定感知到的亮度,首先将gamma编码的R´G´B´图像值转换为线性亮度(LY),然后转换为非线性感知亮度(L*)


寻找亮度:

...因为很明显它在某个地方丢失了……

第一步:

将所有sRGB 8位整数值转换为十进制0.0-1.0

  vR = sR / 255;
vG = sG / 255;
vB = sB / 255;

第二步:

将gamma编码的RGB转换为线性值。例如,sRGB(计算机标准)要求大约V^2.2的功率曲线,尽管“精确”;变换:

sRGB to Linear

< p > 其中V´为sRGB的伽玛编码R、G或B通道。
伪代码:< / p >
function sRGBtoLin(colorChannel) {
// Send this function a decimal sRGB gamma encoded color value
// between 0.0 and 1.0, and it returns a linearized value.


if ( colorChannel <= 0.04045 ) {
return colorChannel / 12.92;
} else {
return pow((( colorChannel + 0.055)/1.055),2.4);
}
}

第三步:

要找到亮度(Y),应用sRGB的标准系数:

应用系数Y = R * 0.2126 + G * 0.7152 + B * 0.0722

使用上述函数的伪代码:

Y = (0.2126 * sRGBtoLin(vR) + 0.7152 * sRGBtoLin(vG) + 0.0722 * sRGBtoLin(vB))

找到可感知的轻盈:

步骤四:

从上面取亮度Y,变换为L*

< p > L* from Y equation
伪代码:< / p >
function YtoLstar(Y) {
// Send this function a luminance value between 0.0 and 1.0,
// and it returns L* which is "perceptual lightness"


if ( Y <= (216/24389)) {       // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
return Y * (24389/27);  // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
} else {
return pow(Y,(1/3)) * 116 - 16;
}
}

L*是一个从0(黑色)到100(白色)的值,其中50是感知的“中间灰色”。L* = 50相当于Y = 18.4,换句话说,一张18%的灰卡,代表一张照片曝光的中间(安塞尔·亚当斯V区)。

引用:

< p > IEC 61966-2-1:1999 Standard
Wikipedia sRGB
Wikipedia CIELAB
Wikipedia CIEXYZ
Charles Poynton的Gamma常见问题解答 < / p >
我今天用javascript解决了一个类似的任务。 我已经确定了这个getPerceivedLightness(rgb)函数的HEX RGB颜色。 利用Fairchild和Perrotta公式对Helmholtz-Kohlrausch效应进行了亮度校正
/**
* Converts RGB color to CIE 1931 XYZ color space.
* https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
* @param  {string} hex
* @return {number[]}
*/
export function rgbToXyz(hex) {
const [r, g, b] = hexToRgb(hex).map(_ => _ / 255).map(sRGBtoLinearRGB)
const X =  0.4124 * r + 0.3576 * g + 0.1805 * b
const Y =  0.2126 * r + 0.7152 * g + 0.0722 * b
const Z =  0.0193 * r + 0.1192 * g + 0.9505 * b
// For some reason, X, Y and Z are multiplied by 100.
return [X, Y, Z].map(_ => _ * 100)
}


/**
* Undoes gamma-correction from an RGB-encoded color.
* https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation
* https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
* @param  {number}
* @return {number}
*/
function sRGBtoLinearRGB(color) {
// Send this function a decimal sRGB gamma encoded color value
// between 0.0 and 1.0, and it returns a linearized value.
if (color <= 0.04045) {
return color / 12.92
} else {
return Math.pow((color + 0.055) / 1.055, 2.4)
}
}


/**
* Converts hex color to RGB.
* https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
* @param  {string} hex
* @return {number[]} [rgb]
*/
function hexToRgb(hex) {
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
if (match) {
match.shift()
return match.map(_ => parseInt(_, 16))
}
}


/**
* Converts CIE 1931 XYZ colors to CIE L*a*b*.
* The conversion formula comes from <http://www.easyrgb.com/en/math.php>.
* https://github.com/cangoektas/xyz-to-lab/blob/master/src/index.js
* @param   {number[]} color The CIE 1931 XYZ color to convert which refers to
*                           the D65/2° standard illuminant.
* @returns {number[]}       The color in the CIE L*a*b* color space.
*/
// X, Y, Z of a "D65" light source.
// "D65" is a standard 6500K Daylight light source.
// https://en.wikipedia.org/wiki/Illuminant_D65
const D65 = [95.047, 100, 108.883]
export function xyzToLab([x, y, z]) {
[x, y, z] = [x, y, z].map((v, i) => {
v = v / D65[i]
return v > 0.008856 ? Math.pow(v, 1 / 3) : v * 7.787 + 16 / 116
})
const l = 116 * y - 16
const a = 500 * (x - y)
const b = 200 * (y - z)
return [l, a, b]
}


/**
* Converts Lab color space to Luminance-Chroma-Hue color space.
* http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
* @param  {number[]}
* @return {number[]}
*/
export function labToLch([l, a, b]) {
const c = Math.sqrt(a * a + b * b)
const h = abToHue(a, b)
return [l, c, h]
}


/**
* Converts a and b of Lab color space to Hue of LCH color space.
* https://stackoverflow.com/questions/53733379/conversion-of-cielab-to-cielchab-not-yielding-correct-result
* @param  {number} a
* @param  {number} b
* @return {number}
*/
function abToHue(a, b) {
if (a >= 0 && b === 0) {
return 0
}
if (a < 0 && b === 0) {
return 180
}
if (a === 0 && b > 0) {
return 90
}
if (a === 0 && b < 0) {
return 270
}
let xBias
if (a > 0 && b > 0) {
xBias = 0
} else if (a < 0) {
xBias = 180
} else if (a > 0 && b < 0) {
xBias = 360
}
return radiansToDegrees(Math.atan(b / a)) + xBias
}


function radiansToDegrees(radians) {
return radians * (180 / Math.PI)
}


function degreesToRadians(degrees) {
return degrees * Math.PI / 180
}


/**
* Saturated colors appear brighter to human eye.
* That's called Helmholtz-Kohlrausch effect.
* Fairchild and Pirrotta came up with a formula to
* calculate a correction for that effect.
* "Color Quality of Semiconductor and Conventional Light Sources":
* https://books.google.ru/books?id=ptDJDQAAQBAJ&pg=PA45&lpg=PA45&dq=fairchild+pirrotta+correction&source=bl&ots=7gXR2MGJs7&sig=ACfU3U3uIHo0ZUdZB_Cz9F9NldKzBix0oQ&hl=ru&sa=X&ved=2ahUKEwi47LGivOvmAhUHEpoKHU_ICkIQ6AEwAXoECAkQAQ#v=onepage&q=fairchild%20pirrotta%20correction&f=false
* @return {number}
*/
function getLightnessUsingFairchildPirrottaCorrection([l, c, h]) {
const l_ = 2.5 - 0.025 * l
const g = 0.116 * Math.abs(Math.sin(degreesToRadians((h - 90) / 2))) + 0.085
return l + l_ * g * c
}


export function getPerceivedLightness(hex) {
return getLightnessUsingFairchildPirrottaCorrection(labToLch(xyzToLab(rgbToXyz(hex))))
}

将此视为Myndex的回答非常棒的补充。正如他(和其他人)解释的那样,用于计算RGB颜色的相对亮度(和感知亮度)的算法被设计用于线性 RGB值。你不能只是将它们应用到原始sRGB值上,并希望得到相同的结果。

嗯,这一切在理论上听起来很棒,但我真的需要为自己看到证据,所以,受到Petr Hurtak的色彩渐变的启发,我继续做了我自己的。它们说明了两种最常见的算法(ITU-R建议BT.601BT.709),并清楚地说明了为什么应该使用线性值(而不是伽玛校正值)进行计算。

首先,下面是旧的ITU BT.601算法的结果。左边的使用原始sRGB值。右边的使用线性值。

ITU-R BT.601颜色亮度梯度

0.299 r + 0.587 g + 0.114 b

ITU-R BT.601 colour luminance gradients

在这个分辨率下,左边的照片实际上看起来非常好!但如果你仔细观察,你会发现一些问题。在更高的分辨率下,不需要的人工制品更加明显:

ITU-R BT.601颜色亮度梯度(高分辨率) < / >

线性的不受这些影响,但是有很多干扰。让我们将其与ITU-R建议BT.709进行比较……

ITU-R BT.709颜色亮度梯度

0.2126 r + 0.7152 g + 0.0722 b

ITU-R BT.709 colour luminance gradients

哦男孩。显然不打算与原始sRGB值一起使用!然而,这是大多数人做的事情!

ITU-R BT.709 colour luminance gradients (high-res) < / >

在高分辨率下,你可以真正看到这个算法在使用线性值时是多么有效。它没有之前那个那么多噪音。虽然这些算法都不是完美的,但这个算法已经是最好的了。

如上所述作者@Nils Pipenbrinck:

所有这些方程在实践中都很有效,但如果你需要非常精确,你就必须[做一些额外的gamma东西]。在深灰色中,忽略伽玛和正确伽玛之间的亮度差异高达20%。

下面是一个完全自包含的JavaScript函数,它执行“extra”;来获得额外的精度。它基于Jive Dadson的c++回答来解决同样的问题。

// Returns greyscale "brightness" (0-1) of the given 0-255 RGB values
// Based on this C++ implementation: https://stackoverflow.com/a/13558570/11950764
function rgbBrightness(r, g, b) {
let v = 0;
v += 0.212655 * ((r/255) <= 0.04045 ? (r/255)/12.92 : Math.pow(((r/255)+0.055)/1.055, 2.4));
v += 0.715158 * ((g/255) <= 0.04045 ? (g/255)/12.92 : Math.pow(((g/255)+0.055)/1.055, 2.4));
v += 0.072187 * ((b/255) <= 0.04045 ? (b/255)/12.92 : Math.pow(((b/255)+0.055)/1.055, 2.4));
return v <= 0.0031308 ? v*12.92 : 1.055 * Math.pow(v,1.0/2.4) - 0.055;
}

更精确的计算参见Myndex的回答