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


当前回答

再加上其他人说的话:

所有这些方程在实践中都工作得很好,但如果你需要非常精确,你必须首先将颜色转换为线性颜色空间(应用逆图像-gamma),对原色进行权重平均,如果你想显示颜色- 把亮度调回监控器伽马。

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

其他回答

与其迷失在这里提到的随机选择的公式中,我建议您使用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);
}

我认为你正在寻找的是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

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

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

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

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

0.299 r + 0.587 g + 0.114 b

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

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

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

0.2126 r + 0.7152 g + 0.0722 b

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

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

正如@Nils Pipenbrinck所提到的:

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

这里有一个完全自包含的JavaScript函数,它做了“额外的”工作来获得额外的准确性。它基于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的答案以获得更准确的计算。

“接受”的答案是不正确和不完整的

唯一准确的答案是@ ji- dadson和@EddingtonsMonkey的答案,并支持@ niles -pipenbrinck。其他答案(包括已接受的答案)链接到或引用了错误的、不相关的、过时的或坏的来源。

简要:

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


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

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

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

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

亮度是一种感知属性,它不具有“物理”度量。然而,一些颜色外观模型确实有一个值,通常用“Q”表示感知亮度,这与感知亮度不同。

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

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

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


寻找亮度:

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

第一步:

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

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

第二步:

将gamma编码的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%的灰卡,代表一张照片曝光的中间(安塞尔·亚当斯V区)。

引用:

IEC 61966-2-1:1999标准 维基百科sRGB 维基百科CIELAB 维基百科CIEXYZ Charles Poynton的Gamma常见问题解答