ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在寻找某种公式或算法来确定给定 RGB 值的颜色的亮度。我知道这不可能像将 RGB 值相加并获得更高的总和更亮那样简单,但我不知道从哪里开始。

感知亮度是我想我正在寻找的,谢谢。
有一篇很好的文章 (Manipulating colors in .NET - Part 1) 关于色彩空间和它们之间的对话,包括理论和代码 (C#)。有关答案,请查看文章中的模型之间的转换 主题。
我已经成为会员很多年了,我以前从未这样做过。我可以建议您查看答案并重新考虑接受哪个答案吗?

L
Lionel Rowe

该方法可能因您的需要而异。这里有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 颜色添加示例。]


请注意,这两者都强调生理方面:人的眼球对绿光最敏感,对红色不太敏感,对蓝色最不敏感。
另请注意,所有这些可能都是针对线性 0-1 RGB 的,并且您可能有经过伽马校正的 0-255 RGB。他们并没有像你想象的那样转变。
不正确。在应用线性变换之前,必须首先对颜色空间应用伽马函数的逆。然后在应用线性函数之后,应用伽马函数。
在最后一个公式中,是 (0.299*R)^2 还是 0.299*(R^2) ?
@KaizerSozay 正如这里所写,它的意思是 0.299*(R^2) (因为取幂在乘法之前)
G
Glenn Slayden

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

我喜欢你输入精确的值,但也包括一个快速的“足够接近”类型的快捷方式。 +1。
@Jonathan Dumaine - 两个快速计算公式都包括蓝色 - 第一个是(2*Red + Blue + 3*Green)/6,第二个是(3*Red + Blue + 4*Green)> >3.诚然,在这两个快速近似值中,蓝色的权重最低,但它仍然存在。
@JonathanDumaine 那是因为人眼对蓝色的感知力最差;-)
快速版本运行良好。经过测试并应用于拥有数千名用户的实际应用程序,一切看起来都很好。
如果你这样做,快速版本会更快:Y = (R<<1+R+G<<2+B)>>3(在 ARM 上只有 3-4 个 CPU 周期),但我想一个好的编译器会为你做优化。
M
Myndex

“已接受”的答案不正确且不完整

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

简要地:

sRGB 必须在应用系数之前进行线性化。

亮度(L 或 Y)与光一样是线性的。

感知亮度 (L*) 与人类感知一样是非线性的。

HSV 和 HSL 在感知方面甚至都不是很准确。

sRGB 的 IEC 标准指定阈值为 0.04045,而不是 0.03928(来自过时的早期草案)。

为了有用(即相对于感知),欧几里得距离需要感知一致的笛卡尔向量空间,例如 CIELAB。 sRGB 不是一个。

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

因为这个帖子在搜索引擎中出现的频率很高,所以我添加这个答案是为了澄清关于这个主题的各种误解。

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

Perceived lightness被CIELAB等一些视觉模型使用,这里的L*(Lstar)是perceptual lightness的一个值,是非线性的以近似人类视觉非线性响应曲线。 (也就是说,与感知呈线性关系,但与光呈非线性关系)。

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

Luma (Y´ prime) 是在某些视频编码 (Y´I´Q´) 中使用的伽马编码加权信号。不要与线性亮度混淆。

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

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

寻找亮度:

...因为显然它在某个地方丢失了...

第一步:

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

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

第二步:

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

https://i.stack.imgur.com/syfhh.png

其中 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 的标准系数:

https://i.stack.imgur.com/TZoZA.png

使用上述函数的伪代码:

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

寻找感知到的轻盈:

第四步:

从上方获取亮度 Y,并转换为 L*

https://i.stack.imgur.com/bgmlT.png

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's Gamma FAQ


@Rotem 谢谢你——我看到了一些奇怪和不完整的陈述,觉得把它确定下来会很有帮助,特别是因为这个帖子在搜索引擎上仍然排名很高。
@asdfasdfads 是的,L*a*b* 没有考虑到许多心理物理属性。亥姆霍兹-科尔劳施效应是其中之一,但还有很多其他的。 CIELAB 无论如何都不是一个“完整”的图像评估模型。在我的帖子中,我试图尽可能完整地涵盖基本概念,而不会冒险进入非常深的细节。 Hunt 模型、Fairchild 模型和其他模型做得更完整,但也更加复杂。
@Myndex,没关系,我的实施是基于疲劳的,我的糟糕结果来自于此:(非常感谢您的帮助和您的帖子,这很有价值!
很公平,@Myndex。我已经发现比色法是一个棘手的课题。
小错字...似乎 if ( Y <= (216/24389) { 缺少 )
a
aleclarson

我在接受的答案中比较了三种算法。我在循环中生成颜色,其中大约每 400 种颜色被使用。每种颜色由 2x2 像素表示,颜色从最暗到最亮(从左到右,从上到下)排序。

第一张图片 - Luminance (relative)

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 Color Model

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

第 4 张图片 - WCAG 2.0 SC 1.4.3 relative luminancecontrast ratio 公式(见 @Synchro's 答案 here

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

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

https://i.imgur.com/I4alyQe.png


对我来说,这是最好的答案,因为 oyu 使用一种图片模式,可以让您感知不同的色调是否以相同的亮度呈现。对于我和我目前的显示器来说,第三张图片是“最好看的”,因为它也比第四张更快,这是一个加号
您的比较图像不正确,因为您没有为所有功能提供正确的输入。第一个函数需要线性RGB输入;我只能通过提供非线性(即伽马校正)RGB 来重现条带效应。纠正这个问题,您不会得到任何条带伪影,第一个函数是明显的赢家。
@Max 第三个公式中包含的 ^2sqrt 是一种从非线性 RGB 逼近线性 RGB 的更快方法,而不是更正确的 ^2.2^(1/2.2)。不幸的是,使用非线性输入而不是线性输入非常普遍。
@Max,你是绝对正确的。有关每个算法的更新视觉效果,请参阅 my answer,比较 gamma 压缩与线性 RGB 输入。
x
xjcl

以下是将浏览器等中使用的 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)
    );
}

这就是定义 sRGB 的方式。我认为原因是它避免了一些接近零的数值问题。如果您只是将数字提高到 2.2 和 1/2.2 的幂,这不会有太大区别。
JMD - 作为视觉感知实验室工作的一部分,我在 CRT 显示器上进行了直接亮度测量,并且可以确认在值范围的底部存在线性亮度区域。
我知道这已经很老了,但它仍然在那里有待搜索。我认为这不可能是正确的。不应该灰色(255,255,255)=灰色(255,0,0)+灰色(0,255,0)+灰色(0,0,255)吗?它没有。
@DCBillen:不,因为这些值在非线性伽马校正的 sRGB 空间中,所以你不能只是将它们相加。如果你想把它们加起来,你应该在调用 gam_sRGB 之前这样做。
@DCBillen Rdb 是正确的。将它们相加的方法显示在函数 int gray(int r, int g, int b) 中,它“取消调用”gam_sRGB。让我痛苦的是,四年后,正确答案的评分如此之低。 :-) 不是真的.. 我会克服的。
G
Gust van de Wal

与其在此处提到的随机选择的公式中迷失方向,我建议您选择 W3C 标准推荐的公式。

这是 WCAG 2.0 SC 1.4.3 relative luminancecontrast ratio 公式的简单但准确的 PHP 实现。它生成的值适用于评估 WCAG 合规性所需的比率,如 this page 所示,因此适用于任何 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);
}

因为,正如我所说,W3C 和 WCAG 都推荐它?
W3C 公式在许多层面上都是不正确的。它没有考虑人类的感知,他们使用“简单”的对比度,使用线性的亮度,并且在感知上完全不均匀。除其他事项外,他们似乎基于一些可追溯至 1988 年的标准(!!!),这些标准与今天无关(这些标准基于单色监视器,例如绿色/黑色,并指的是从开到关的总对比度,不考虑灰度和颜色)。
这完全是垃圾。 Luma 是特别感知的——这就是为什么它对红色、绿色和蓝色有不同的系数。年龄与它无关 - 出色的 CIE Lab 感知色彩空间可以追溯到 1976 年。W3C 空间不是那么好,但它是一个很好的实用近似值,易于计算。如果您有建设性的建议,请发布,而不是空洞的批评。
只是为了添加/更新:我们目前正在研究更好地模拟感知对比度的替换算法(在 Github 第 695 期中的讨论)。然而,作为一个单独的问题,仅供参考,sRGB 的阈值是 0.04045,而不是从过时的早期 sRGB 草案中引用的 0.03928。权威的 IEC 标准使用 0.04045,并且即将提出拉取请求以纠正 WCAG 中的此错误。 (参考:IEC 61966-2-1:1999)这是在 Github 问题 360 中,不过要提一下,在 8 位中没有实际差异 - 接近线程 360 的结尾我有错误图表,包括 8 位中的 0.04045/0.03928。
并且添加到线程中,WCAG 3.0的替换方法是APCA,可以在myndex.com/APCA/simple看到
N
Nils Pipenbrinck

添加所有其他人所说的话:

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

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


C
Community

我发现 this code(用 C# 编写)在计算颜色的“亮度”方面做得很好。在这种情况下,代码试图确定是在颜色上放置白色还是黑色文本。


这正是我所需要的。我正在做一个经典的“彩条”演示,并想用最好的黑白选择将它们标记在颜色之上!
b
bobobobo

有趣的是,this formulation for RGB=>HSV 只使用 v=MAX3(r,g,b)。换句话说,您可以使用 (r,g,b) 的 最大值 作为 HSV 中的 V。

我检查并在 Hearn & Baker 的第 575 页上,这也是他们计算“价值”的方式。

https://i.stack.imgur.com/ubpRX.png


仅供记录,链接已失效,此处存档版本 - web.archive.org/web/20150906055359/http://…
HSV 在感知上并不统一(甚至不接近)。它只是作为一种“方便”的方式来调整颜色,但它与感知无关,V 与 L 或 Y(CIE 亮度)的真实值无关。
这是否意味着 #FF0000#FFFFFF 一样亮?
我相信它更像是 HSL 中的 lightness = (max(r, g, b) + min(r, g, b)) / 2
c
catamphetamine

我今天用 javascript 解决了一个类似的任务。我已经为 HEX RGB 颜色选择了这个 getPerceivedLightness(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))))
}

K
Kal

将此视为 Myndex's excellent answer 的附录。正如他(和其他人)解释的那样,用于计算 RGB 颜色的相对亮度(和感知亮度)的算法被设计为使用 linear RGB 值。您不能只将它们应用于原始 sRGB 值并希望获得相同的结果。

好吧,理论上这一切听起来都很棒,但我确实需要查看自己的证据,因此,受到 Petr Hurtak's colour gradients 的启发,我继续制作自己的证据。它们说明了两种最常见的算法(ITU-R 建议 BT.601BT.709),并清楚地说明了为什么应该使用线性值(不是经过伽马校正的值)进行计算。

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

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

0.299 R + 0.587 G + 0.114 B

https://i.stack.imgur.com/W29rZ.png

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

https://i.stack.imgur.com/IunRs.jpg

线性的不受这些影响,但那里有很多噪音。让我们将其与 ITU-R Recommendation BT.709 进行比较……

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

0.2126 R + 0.7152 G + 0.0722 B

https://i.stack.imgur.com/GRfaL.png

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

https://i.stack.imgur.com/S6R2T.jpg

在高分辨率下,您可以真正看到该算法在使用线性值时的有效性。它几乎没有早期的噪音那么大。虽然这些算法都不是完美的,但这个算法已经做到了最好。


G
Gust van de Wal

这是一些应该正确计算感知亮度的 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 亮度值 = 0.3 R + 0.59 G + 0.11 B

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

如果您正在寻找颜色与白色的接近程度,您可以使用 (255, 255, 255) 的欧几里得距离

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


D
Dave Collier

Jive Dadson 的反伽玛公式在 Javascript 中实现时需要去掉半调整,即函数 gam_sRGB 的返回需要返回 int(v*255);不返回 int(v*255+.5);半调整四舍五入,这可能导致 R=G=B(即灰色三元组)上的值太高。 R=G=B 三元组的灰度转换应该产生一个等于 R 的值;这是公式有效的一个证明。请参阅 Nine Shades of Greyscale 了解实际中的公式(没有半调整)。


听起来你知道你的东西,所以我删除了 +0.5
我做了这个实验。在 C++ 中它需要 +0.5,所以我把它放回去了。我添加了一条关于翻译成其他语言的评论。
v
vortex

我想知道这些 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 系数对我来说是错误的。

是的,我大概有正常的色觉。


在您的实验中,您是否首先进行线性化以去除伽马分量?如果你不这样做可以解释你的结果。但是,这些系数与 CIE 1931 实验相关,并且是 17 名观察者的平均值,所以是的,结果存在个体差异。
并补充:此外,1931 年的 CIE 值基于 2° 观察者,此外蓝色区域存在已知误差。 10° 观察者值显着不同,因为 S 锥不在中央凹中心视觉中。在这两种情况下,都努力避免视杆侵入,保持明视区域的亮度水平。如果在中间区域进行测量,视杆侵入也会影响结果。
I
Ian Hopkinson

HSV 颜色空间应该可以解决问题,请参阅 wikipedia article,具体取决于您使用的语言,您可能会获得库转换。

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

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

V 是颜色的“亮度”。


HSV 颜色空间的问题在于,对于蓝色和黄色,您可以具有相同的饱和度和值,但色调不同。黄色比蓝色亮得多。 HSL 也是如此。
hsv 在技术意义上为您提供颜色的“亮度”。在感知亮度 hsv 真的失败了
HSV 和 HSL 在感知上并不准确(甚至不接近)。它们对于调整相对颜色的“控制”很有用,但不能用于准确预测感知亮度。使用 CIELAB 的 L* 获得感知亮度。
J
Jacob

HSV 的“V”可能是您正在寻找的。 MATLAB 有一个 rgb2hsv 函数,之前引用的 wikipedia 文章充满了伪代码。如果 RGB2HSV 转换不可行,则不太准确的模型将是图像的灰度版本。


E
Emil

This link 深入解释了所有内容,包括为什么这些乘数常数存在于 R、G 和 B 值之前。

编辑:这里也有一个答案的解释(0.299*R + 0.587*G + 0.114*B)


P
Pierre-louis Stenger

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

在我的脚本中,出于其他原因,我以前使用 HEX 系统代码,但您也可以使用 rgb2hsv {grDevices} 的 RGB 系统代码开始。文档是 here

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

 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

j
joe

如前所述 by @Nils Pipenbrinck

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

这是一个完全独立的 JavaScript 函数,它执行“额外”的工作来获得额外的准确性。它基于同一问题的Jive Dadson's C++ answer

// Returns perceived 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;
}

P
Pavel P

为了清楚起见,使用平方根的公式需要

sqrt(coefficient * (colour_value^2))

不是

sqrt((coefficient * colour_value))^2

这一点的证据在于将 R=G=B 三元组转换为灰度 R。只有当您将颜色值平方而不是颜色值乘以系数时,这才是正确的。请参阅Nine Shades of Greyscale


有括号不匹配
除非您使用的系数是正确系数的平方根。
B
Ben S

请定义亮度。如果您正在寻找颜色与白色的接近程度,您可以使用 (255, 255, 255) 中的 Euclidean Distance


不,您不能在 sRGB 值之间使用欧几里得距离,sRGB 不是感知上统一的笛卡尔/向量空间。如果您想使用欧几里得距离作为色差的度量,您至少需要转换为 CIELAB,或者更好的是,使用像 CIECAM02 这样的 CAM。