111

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

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

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

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

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

4

2 回答 2

273

假设您正在使用 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 上有多亮,除非你先校准它。伽玛校正意味着转换您开始使用的颜色(可能是线性的)并将它们转换为用户 CRT 的伽玛。OpenGL 来自这个时代,这就是为什么它的 sRGB 行为有时会有点令人困惑。但是 GPU 供应商现在倾向于使用我上面描述的约定:当您在纹理或帧缓冲区中存储 8 位强度时,它是 sRGB,而当您处理颜色时,它是线性的。例如,一个 OpenGL ES 3.0,每个帧缓冲区和纹理都有一个“sRGB 标志”,您可以打开它以在读取和写入时启用自动转换。您根本不需要明确地进行 sRGB 转换或伽马校正。

于 2012-10-15T10:50:50.380 回答
1

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

如前所述,我不是专家,无论如何,我认为,如果您想做一些颜色正确的转换,您应该在 YUV 空间中进行,然后将其转换为 RGB(或对 RGB 进行数学等效操作,请注意数据丢失)。此外,我不确定 YUV 是颜色的最佳原生表示,但摄像机提供了这种格式,这就是我遇到问题的地方。

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

于 2012-10-15T09:58:51.593 回答