【问题标题】:What are the practical differences when working with colors in a linear vs. a non-linear RGB space?在线性和非线性 RGB 空间中使用颜色时,实际的区别是什么?
【发布时间】:2012-09-13 12:39:45
【问题描述】:

线性 RGB 空间的基本属性是什么,非线性空间的基本属性是什么?当谈论这 8 个(或更多)位中每个通道内的值时,有什么变化?

在 OpenGL 中,颜色是 3+1 值,我的意思是 RGB+alpha,每个通道保留 8 位,这是我清楚的部分。

但是当谈到伽玛校正时,我不明白在非线性 RGB 空间中工作的效果是什么。

因为我知道如何在图形软件中使用曲线进行照片编辑,所以我的解释是,在线性 RGB 空间中,您可以按原样获取值,没有任何操作,也没有附加数学函数,而是当它不是-线性每个通道通常遵循经典的幂函数行为。

即使我认为这个解释是真实的,我仍然不明白什么是真正的线性空间,因为在计算之后,所有非线性 RGB 空间都变成了线性的,最重要的是我没有得到这个部分其中非线性色彩空间更适合人眼,因为最终所有 RGB 空间对于我的理解都是线性的。

【问题讨论】:

  • 实际上,您可以在 SVG 中指定线性或标准 RGB 作为颜色空间,但我不知道效果是什么,除了这似乎很重要 :-)
  • @MichaelMullany 这个线性的东西看起来更像是对用户的提示,而不是真正的独特品质。
  • 对我有帮助的东西:我认为是一位数学老师告诉我“当你听到线性时思考线”。不确定这是否有帮助,但对我来说,这是一个“哦,是的!”时刻

标签: graphics colors rgb color-space color-coding


【解决方案1】:

假设您正在使用 RGB 颜色:每种颜色用三个 强度 或亮度表示。您必须在“线性 RGB”和“sRGB”之间进行选择。现在,我们将通过忽略三种不同的强度来简化事情,并假设您只有一种强度:也就是说,您只处理灰色阴影。

在线性色彩空间中,您存储的数字与它们所代表的强度之间的关系是线性的。实际上,这意味着如果将数字加倍,强度就会加倍(灰色的亮度)。如果您想将两个强度加在一起(因为您要根据两个光源的贡献来计算强度,或者因为您要在不透明对象的顶部添加一个透明对象),您可以通过添加两个数字在一起。 如果您要进行任何类型的 2D 混合或 3D 着色,或几乎任何图像处理,那么您希望您的强度在线性色彩空间中,这样您就可以进行加、减、乘和将数字相除以对强度产生相同的影响。大多数颜色处理和渲染算法只能在线性 RGB 上给出正确的结果,除非你给所有东西添加额外的权重。

这听起来很简单,但有一个问题。人眼对光的敏感度在低强度下比在高强度下要好。也就是说,如果你列出所有你能分辨的强度,那么暗的比亮的多。换句话说,你可以比浅灰色更好地区分深灰色。特别是,如果您使用 8 位来表示您的强度,并且您在线性颜色空间中执行此操作,您最终会得到太多的浅色调,而没有足够的深色调。您在黑暗区域会出现条带,而在明亮区域,您会在用户无法区分的不同色调的近白色上浪费位。

为了避免这个问题,并充分利用这 8 位,我们倾向于使用 sRGB。 sRGB 标准告诉您要使用的曲线,以使您的颜色非线性。曲线在底部较浅,因此您可以使用更多深灰色,而在顶部更陡峭,因此您可以使用更少的浅灰色。如果你把数字加倍,你的强度就会增加一倍以上。这意味着如果将 sRGB 颜色加在一起,最终会得到比应有的颜色更亮的结果。如今,大多数显示器将其输入颜色解释为 sRGB。因此,当您在屏幕上添加颜色或将其存储在每通道 8 位纹理中时,请将其存储为 sRGB,这样您就可以充分利用这 8 位。

您会注意到我们现在遇到了一个问题:我们希望我们的颜色在线性空间中处理,但存储在 sRGB 中。这意味着您最终会在读取时进行 sRGB 到线性的转换,并在写入时进行线性到 sRGB 的转换。正如我们已经说过的,线性 8 位强度没有足够的暗度,这会导致问题,所以还有一条更实用的规则:如果可以,不要使用 8 位线性颜色躲开它。遵循 8 位颜色始终为 sRGB 的规则已成为惯例,因此您在将强度从 8 位扩大到 16 位或从整数到浮点的同时进行 sRGB 到线性的转换;同样,当您完成浮点处理后,在转换为 sRGB 的同时缩小到 8 位。如果您遵循这些规则,您就不必担心伽马校正。

当您读取 sRGB 图像并且想要线性强度时,请将此公式应用于每个强度:

float s = read_channel();
float linear;
if (s <= 0.04045) linear = s / 12.92;
else linear = pow((s + 0.055) / 1.055, 2.4);

反之,当您想将图像写入 sRGB 时,将这个公式应用于每个线性强度:

float linear = do_processing();
float s;
if (linear <= 0.0031308) s = linear * 12.92;
else s = 1.055 * pow(linear, 1.0/2.4) - 0.055; ( Edited: The previous version is -0.55 )

在这两种情况下,浮点 s 的值都在 0 到 1 之间,因此,如果您正在读取 8 位整数,则首先要除以 255,如果您正在写入 8 位整数,则要最后乘以 255,与通常的方式相同。这就是使用 sRGB 所需要知道的全部内容。

到目前为止,我只处理了一种强度,但还有一些更聪明的方法可以处理颜色。人眼可以比不同的色调更好地分辨不同的亮度(从技术上讲,它比色度具有更好的亮度分辨率),因此您可以通过将亮度与色调分开存储来更好地利用 24 位。这就是 YUV、YCrCb 等表示尝试做的事情。 Y 通道是颜色的整体亮度,并且比其他两个通道使用更多的位(或具有更高的空间分辨率)。这样,您就不需要(总是)像使用 RGB 强度那样应用曲线。 YUV 是一个线性颜色空间,因此如果将 Y 通道中的数字加倍,颜色的亮度就会加倍,但不能像 RGB 颜色那样将 YUV 颜色相加或相乘,所以它不用于图像处理,仅用于存储和传输。

我认为这回答了您的问题,所以我将以简短的历史记录结束。在 sRGB 之前,旧的 CRT 曾经内置了非线性。如果将像素的电压加倍,则强度将增加一倍以上。每个监视器还有多少不同,这个参数被称为 gamma。这种行为很有用,因为它意味着你可以获得比亮更多的暗,但这也意味着你无法判断你的颜色在用户的 CRT 上有多亮,除非你先校准它。 Gamma 校正 意味着转换您开始使用的颜色(可能是线性的)并将它们转换为用户 CRT 的 Gamma。 OpenGL 来自这个时代,这就是为什么它的 sRGB 行为有时会有点令人困惑。但是 GPU 供应商现在倾向于使用我上面描述的约定:当您在纹理或帧缓冲区中存储 8 位强度时,它是 sRGB,而当您处理颜色时,它是线性的。例如,一个 OpenGL ES 3.0,每个帧缓冲区和纹理都有一个“sRGB 标志”,您可以打开以在读取和写入时启用自动转换。您根本不需要明确地进行 sRGB 转换或伽马校正。

【讨论】:

【解决方案2】:

我不是“人类颜色检测专家”,但我在 YUV->RGB 转换上遇到过类似的事情。 R/G/B 通道有不同的权重,所以如果你改变源颜色 x,RGB 值会改变不同的数量。

如上所述,我不是专家,无论如何,我认为,如果你想做一些颜色正确的转换,你应该在 YUV 空间中进行,然后将其转换为 RGB(或在RGB,小心数据丢失)。另外,我不确定 YUV 是颜色的最佳原生表示,但摄像机提供了这种格式,这就是我遇到的问题。

这是包含秘密数字的神奇 YUV->RGB 公式:http://www.fourcc.org/fccyvrgb.php

【讨论】:

  • 小心 RGBYUV 转换和返回。我不确定这是否适用于所有情况,但 YUV 颜色空间有时会转换为 16-235 的范围,而不是 24 位 RGB 中的 0-255。因此,每次进行色彩空间转换时都可能会丢失数据。如果可以的话,大多数人倾向于说保持在相同的色彩空间内。
猜你喜欢
  • 2018-02-02
  • 2011-07-16
  • 2015-03-12
  • 2019-09-24
  • 1970-01-01
  • 2012-10-08
  • 2016-02-25
  • 2021-01-05
  • 1970-01-01
相关资源
最近更新 更多