我正在寻找某种公式或算法来确定给定 RGB 值的颜色的亮度。我知道这不可能像将 RGB 值相加并获得更高的总和更亮那样简单,但我不知道从哪里开始。
21 回答
该方法可能因您的需要而异。这里有3种计算亮度的方法:
亮度(某些色彩空间的标准):
(0.2126*R + 0.7152*G + 0.0722*B)
来源亮度(感知选项 1):
(0.299*R + 0.587*G + 0.114*B)
来源亮度(感知选项 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)来源
[编辑:使用按每种方法排序的命名 css 颜色添加示例。]
我认为您正在寻找的是 RGB -> Luma转换公式。
光度/数字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
“已接受”的答案不正确且不完整
唯一准确的答案是@jive- dadson和@EddingtonsMonkey的答案,并支持@nils-pipenbrinck。其他答案(包括接受的)链接或引用错误、不相关、过时或损坏的来源。
简要地:
- sRGB必须在应用系数之前进行线性化。
- 亮度(L 或 Y)与光一样是线性的。
- 感知亮度 (L*) 与人类感知一样是非线性的。
- HSV 和 HSL 在感知方面甚至都不是很准确。
- sRGB 的 IEC 标准指定阈值为 0.04045,而不是0.03928(来自过时的早期草案)。
- 有用(即相对于感知),欧几里得距离需要感知一致的笛卡尔向量空间,例如 CIELAB。sRGB 不是一个。
以下是正确而完整的答案:
因为这个帖子在搜索引擎中出现的频率很高,所以我添加这个答案是为了澄清关于这个主题的各种误解。
亮度是光的线性度量,对正常视力进行光谱加权,但未针对亮度的非线性感知进行调整。它可以是一个相对量度,如 CIEXYZ 中的Y ,或者是L ,一个以 cd/m 2 为单位的绝对量度(不要与 混淆L*
)。
感知亮度被一些视觉模型如CIELAB使用,这里L*
(Lstar)是感知亮度的一个值,是非线性的,以近似人类视觉的非线性响应曲线。
亮度是一种感知属性,它没有“物理”度量。然而,一些颜色外观模型确实有一个值,通常表示为感知亮度的“Q”,它与感知亮度不同。
Luma ( Y´ prime) 是一种伽马编码的加权信号,用于某些视频编码 (Y´I´Q´)。不要与线性亮度混淆。
Gamma或传输曲线 (TRC) 是一种通常类似于感知曲线的曲线,通常应用于存储或广播的图像数据,以减少感知噪声和/或提高数据利用率(以及相关原因)。
要确定感知亮度,首先将伽马编码的 R´G´B´ 图像值转换为线性亮度 (L
或Y
),然后转换为非线性感知亮度 ( L*
)
寻找亮度:
...因为显然它在某个地方丢失了...
第一步:
将所有 sRGB 8 位整数值转换为十进制 0.0-1.0
vR = sR / 255;
vG = sG / 255;
vB = sB / 255;
第二步:
将伽马编码的 RGB 转换为线性值。例如,sRGB(计算机标准)需要大约 V^2.2 的功率曲线,尽管“准确”的变换是:
其中 V´ 是 sRGB 的伽马编码 R、G 或 B 通道。
伪代码:
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 = (0.2126 * sRGBtoLin(vR) + 0.7152 * sRGBtoLin(vG) + 0.0722 * sRGBtoLin(vB))
寻找感知到的轻盈:
第四步:
从上方获取亮度 Y,并转换为 L*
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% 的灰卡,代表摄影曝光的中间(Ansel Adams 区域 V)。
参考:
IEC 61966-2-1:1999 Standard
Wikipedia sRGB
Wikipedia CIELAB
Wikipedia CIEXYZ
Charles Poynton 的 Gamma 常见问题解答
我在接受的答案中比较了三种算法。我在循环中生成颜色,其中大约每 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)
第 4 张图片 - WCAG 2.0 SC 1.4.3 相对亮度和对比度公式(请参阅@Synchro的答案)
根据一行中颜色的数量,有时会在第一张和第二张图片上发现图案。我从未在第 3 或第 4 算法的图片上发现任何图案。
如果我必须选择,我会选择算法 3,因为它更容易实现,并且比第四算法快 33%。
以下是将浏览器等中使用的 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)
);
}
与其在此处提到的随机选择的公式中迷失方向,我建议您选择 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);
}
添加所有其他人所说的话:
所有这些方程在实践中都工作得很好,但如果你需要非常精确,你必须首先将颜色转换为线性颜色空间(应用逆图像伽玛),然后对原色进行加权平均 - 如果你想显示颜色 - 将亮度带回监视器伽玛。
在深灰色中,忽略伽玛和做正确伽玛之间的亮度差异高达 20%。
我发现这段代码(用 C# 编写)在计算颜色的“亮度”方面做得很好。在这种情况下,代码试图确定是在颜色上放置白色还是黑色文本。
有趣的是,RGB=>HSV 的这个公式只使用 v=MAX3(r,g,b)。换句话说,您可以使用 (r,g,b) 的最大值作为 HSV 中的 V。
我检查并在Hearn & Baker的第 575 页上,这也是他们计算“价值”的方式。
我今天用 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 Recommendation BT.601和BT.709),并清楚地说明了为什么您应该使用线性值(不是经过伽马校正的值)进行计算。
首先,这是旧的 ITU BT.601 算法的结果。左边的那个使用原始 sRGB 值。右边的使用线性值。
ITU-R BT.601 色彩亮度梯度
0.299 R + 0.587 G + 0.114 B
在这个分辨率下,左边的实际上看起来非常好!但是如果你仔细观察,你会发现一些问题。在更高的分辨率下,不需要的伪影更加明显:
线性的不受这些影响,但那里有很多噪音。让我们将其与 ITU-R Recommendation BT.709 进行比较……</p>
ITU-R BT.709 色彩亮度梯度
0.2126 R + 0.7152 G + 0.0722 B
好家伙。显然不打算与原始 sRGB 值一起使用!然而,这正是大多数人所做的!
在高分辨率下,您可以真正看到该算法在使用线性值时的有效性。它几乎没有早期的噪音那么大。虽然这些算法都不是完美的,但这个算法已经做到了最好。
这是一些应该正确计算感知亮度的 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
}
HSV 颜色空间应该可以解决问题,请参阅维基百科文章,具体取决于您使用的语言,您可能会获得库转换。
H 是色调,它是颜色的数值(即红色、绿色……)
S 是颜色的饱和度,即它有多“强烈”
V 是颜色的“亮度”。
RGB 亮度值 = 0.3 R + 0.59 G + 0.11 B
http://www.scantips.com/lumin.html
如果您正在寻找颜色与白色的接近程度,您可以使用 (255, 255, 255) 的欧几里得距离
我认为 RGB 颜色空间相对于 L2 欧几里得距离在感知上是不均匀的。统一空间包括 CIE LAB 和 LUV。
Jive Dadson 的反伽玛公式在 Javascript 中实现时需要去掉半调整,即函数 gam_sRGB 的返回需要返回 int(v*255); 不返回 int(v*255+.5); 半调整四舍五入,这可能导致 R=G=B(即灰色三元组)上的值太高。R=G=B 三元组的灰度转换应该产生一个等于 R 的值;这是公式有效的一个证明。有关实际操作的公式,请参见灰度的九种阴影(没有半调整)。
我想知道这些 rgb 系数是如何确定的。我自己做了一个实验,结果如下:
Y = 0.267 R + 0.642 G + 0.091 B
与长期建立的 ITU 系数接近但明显不同。我想知道每个观察者的这些系数是否可能不同,因为我们每个人的眼睛视网膜上可能有不同数量的视锥细胞和视杆细胞,尤其是不同类型视锥细胞之间的比例可能不同。
以供参考:
国际电联 BT.709:
Y = 0.2126 R + 0.7152 G + 0.0722 B
国际电联 BT.601:
Y = 0.299 R + 0.587 G + 0.114 B
我通过在亮红色、亮绿色和亮蓝色背景上快速移动一个小灰色条进行测试,并调整灰色直到它尽可能地融合。我还用其他色调重复了这个测试。我在不同的显示器上重复了测试,即使是一个固定伽马因子为 3.0 的显示器,但在我看来一切都一样。此外,ITU 系数对我来说是错误的。
是的,我大概有正常的色觉。
HSV 的“V”可能是您正在寻找的。MATLAB 有一个 rgb2hsv 函数,之前引用的 wikipedia 文章充满了伪代码。如果 RGB2HSV 转换不可行,则不太准确的模型将是图像的灰度版本。
此链接深入解释了所有内容,包括为什么这些乘数常数存在于 R、G 和 B 值之前。
编辑:这里也有一个答案的解释(0.299*R + 0.587*G + 0.114*B)
为了用 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
为了清楚起见,使用平方根的公式需要
sqrt(coefficient * (colour_value^2))
不是
sqrt((coefficient * colour_value))^2
这一点的证明在于将 R=G=B 三元组转换为灰度 R。只有当您将颜色值平方而不是颜色值乘以系数时,这才是正确的。见九级灰度
请定义亮度。如果您正在寻找颜色与白色的接近程度,您可以使用(255, 255, 255)的欧几里得距离