在线性与非线性RGB空间中处理颜色时有哪些实际差异?

时间:2012-09-21 05:14:56

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

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

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

但是当谈到伽马校正时,我无法得到在非线性RGB空间中工作的效果。

由于我知道如何在图形软件中使用曲线进行照片编辑,我的解释是在线性RGB空间中,您可以按原样获取值,不需要操作,也不附加数学函数,相反,当它不是 - 每个通道的线性通常随着经典的幂函数行为而发展。

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

2 个答案:

答案 0 :(得分:174)

我们假设您正在使用RGB颜色:每种颜色都用三种强度或亮度表示。你必须在"线性RGB"之间做出选择。和" sRGB"。现在,我们通过忽略三种不同的强度来简化事情,并假设你只有一种强度:即你只处理灰色阴影。

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

这听起来很简单,但问题很严重。人眼对光的敏感度在低强度时比在高强度下更精细。这就是说,如果你列出你可以区分的所有强度,那么黑暗的那些比光的更强。换句话说,你可以用浅色调的灰色来分辨深色调的灰色。特别是,如果您使用8位来表示强度,并且在线性色彩空间中进行此操作,则最终会出现太多的浅色调,而且暗色调不够。你会在你的黑暗区域进行划分,而在你的亮区,你会在不同的近白色阴影上浪费一些用户无法区分的位。

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

您会注意到我们现在遇到了问题:我们希望我们的颜色在线性空间中处理,但存储在sRGB中。这意味着您最终会在读取时进行sRGB到线性的转换,并在写入时进行线性到sRGB的转换。由于我们已经说线性8位强度没有足够的黑暗,这会引起问题,因此还有一个更实用的规则:不要使用8-比特线性颜色如果可以避免的话。遵循8位颜色始终为sRGB的规则变得传统,因此您在进行sRGB到线性转换的同时将强度从8位扩大到16位,或从整数扩展到浮动 - 点;同样,当您完成浮点处理时,在转换为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 )

在这两种情况下,浮点值的范围都是0到1,所以如果你要读取8位整数,你想先除以255,如果你要写8位你想要最后乘以255的整数,就像你通常那样。这是您使用sRGB所需要知道的全部内容。

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

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

答案 1 :(得分:1)

我不是“人类色彩检测专家”,但我在YUV-> RGB转换上遇到了类似的事情。 R / G / B通道有不同的权重,因此如果您将源颜色更改为x,则RGB值会更改不同的数量。

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

这是神奇的YUV-> RGB公式,其中包含密码:http://www.fourcc.org/fccyvrgb.php