如何计算具有相同感知亮度的RGB值

How to calculate RGB values with identical perceived brightness

提问人:Askaga 提问时间:2/11/2017 更新时间:11/9/2023 访问量:5317

问:

我正在尝试生成具有相同感知亮度的 RGB 颜色。

函数 R*0.2126+ G*0.7152+ B*0.0722 据说用于计算给定 RGB 颜色的感知亮度(或等效灰度颜色)。

假设我们对所有 RGB 值使用区间 [0,1],我们可以计算以下内容:

  • 黄色 = RGB(1,1,0) => 亮度=0.9278
  • 蓝色 = RGB(0,0,1) => 亮度=0.0722

因此,为了使黄色调与蓝色调一样暗淡,我可以简单地对每个RGB组件的黄色进行简单的计算:

  • dim_yellow = 黄色 * 0.0722 / 0.9278

然而,当做相反的事情时,从而将蓝色“放大”到与原始黄色相同的感知亮度时,B 分量显然超过 1,这不能显示在计算机屏幕上。

我猜想多余的 B 分量丢失的亮度可以“重新分配”到 R 和 G 分量,从而伪造出更亮的蓝色。那么,计算这些最终RGB值的最佳通用方法是什么?

数学 颜色 与语言无关 RGB

评论


答:

0赞 Dmitry Lachinov 2/11/2017 #1

我建议您使用 HSV 颜色模型而不是 RGB,因为您只需修改 Value(Brightness) 组件即可轻松实现所需的内容。 wiki 页面还包含如何将 RGB 转换为 HSV 并返回

编辑: 尝试使用CIELAB色彩空间,因为它接近人类的视觉

评论

2赞 Askaga 2/12/2017
我知道 HSL 和 HSV,但它们没有按照您的建议执行。具有完全饱和度和值的蓝色调仍然会显得“更暗”或“更暗”,然后是具有完全饱和度和值的黄色色调。所以你的答案并不能解决我的问题
0赞 Myndex 8/13/2023
@Askaga 是的,HSV、HSL 在感知上并不均匀。
3赞 Myndex 5/20/2019 #2

这些不是你要找的数学

函数 R*0.2126+ G*0.7152+ B*0.0722 据说用于计算给定 RGB 颜色的感知亮度(或等效灰度颜色)。

不,这是不正确的,或者至少是不完整的。是的,R*0.2126+ G*0.7152+ B*0.0722 是光谱系数,但这不是全部。

首先,不要在此上下文中使用术语亮度。亮度不是光的量度,它是一种感知,而不是一个可测量的量。当我们谈论光和比色法时,请使用术语“亮度”(L 或 Y)。亮度是光的线性量度,而不是感知。

CIELAB的感知亮度或L*(Lstar)是基于人类对亮度变化的感知。它接近约0.43的功率曲线。

sRGB是通常用于计算机显示器和网络的色彩空间,它不像光那样是线性的,也不完全像感知的L*曲线。sRGB的传输曲线接近1/2.2的功率曲线。也就是说,sRGB数据/信号被提高到0.455的幂,然后显示器施加2.2的幂。

坏了

你的数学运算不起作用,因为你没有考虑转移曲线。在应用系数之前,必须对 sRGB 值进行线性化。那么这些之和将等于亮度 1。

sRGB 中的 #FFFF00 等于亮度的 0.9278,但这是 96.76% 的 sRGB 值或 97.14% 的 L* 值

sRGB 中的 #0000FF 等于亮度的 0.0722,但这是 29.79% 的 sRGB 值或 32.3% 的 L* 值

下面是一个包含一些值的图表,扩展了您的示例:

SRGB VS LUMINANCE VALUES

因此,要回答其余的问题,要获得与显示器能够达到的更高亮度的蓝色,需要降低亮度,添加 R 和 G 以增加亮度。

在此图表中,我们有完全饱和但较深的红色和绿色以匹配 7% 的蓝色亮度,然后我们有 18% 的亮度(如 18% 的灰色卡片),在这里我们必须降低蓝色的饱和度以增加亮度值。

如何计算

首先,您需要线性化sRGB分量,然后应用系数,如果您需要确定亮度。如果你想出一些对线性化组件进行数学运算的值,那么你需要重新伽玛编码以回到sRGB。

我已经讨论过这是其他几个答案,比如这里。

0赞 Tom Huntington 11/8/2023 #3

对于给定的色调,您只需乘以所需的亮度与当前亮度的比率即可。

即,要找到与蓝色具有相同亮度的红色,我们可以

// l = .299 r^2 + .587 g^2 + .114 b^2
target_color = V(1, 0, 0);
desired_brightness = sqrt(sum(V(0, 0, 1) * V(0, 0, 1) * V(0.229, 0.587, 0.114)));
current_brightness = sqrt(sum(target_color * target_color * V(0.229, 0.587, 0.114)));
desired_color = target_color * desired_brightness / current_brightness 

演示 https://tom-huntington.github.io/match-brightness/

我意识到 Myndx 的公式更好,但它是非线性的,所以你必须使用割线方法。我们可以对亮度进行平方根以加速收敛(因为它的关系近似于平方):

fn sRGBtoLin(colorChannel : f32) -> f32 {
  if ( colorChannel <= 0.04045 ) {
    return colorChannel / 12.92;
  } else {
    return pow((( colorChannel + 0.055)/1.055),2.4);
  }
}

fn luminance(color : vec3<f32>) -> f32 {
  var lin = vec3<f32>(sRGBtoLin(color.x), sRGBtoLin(color.y), sRGBtoLin(color.z));
  var coef = vec3<f32>(0.2126, 0.7152, 0.0722);
  //var coef = vec3<f32>(0.299 , 0.587 , 0.114);
  var rng = lin * coef;
  return sqrt(rng.x + rng.y + rng.z);
}

main() {
  var target_brightness = brightness(target_color);
  var xi = color * 1.5;
  var xim = color / 1.5;

  for (var i = 0u; i < 20; i++) {
  var bxi = brightness(xi);
  var bxim = brightness(xim);
  var xip = xi - (bxi - target_brightness) * (xi - xim) / (bxi - bxim);
  if (distance(bxi, bxim) > 0.000000000001)
  {
    xim = xi;
    xi = xip;
  }
}