在线性和非线性RGB空间中处理颜色时,实际的区别是什么?


88

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

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

但是,当涉及伽玛校正时,我不了解在非线性RGB空间中工作的效果。

由于我知道如何在图形软件中使用曲线进行照片编辑,因此我的解释是,在线性RGB空间中,您按原样使用值,没有任何操作,也没有数学函数,而是分别使用非线性值通道通常遵循经典的幂函数行为而演变。

即使我将这种解释作为真实的解释,我仍然不知道真正的线性空间是什么,因为在计算之后,所有非线性RGB空间都变为线性,而且最重要的是,我没有得到非线性空间。 -linear色彩空间更适合人眼,因为最终,根据我的理解,所有RGB空间都是线性的。


实际上,您可以在SVG中将线性或标准RGB指定为您的色彩空间,除了这似乎很重要以外,我不知道效果是什么:-)
Michael Mullany 2012年

@MichaelMullany这个线性的东西看起来更像是给用户的提示,而不是真正独特的质量。
肯(Ken)

有什么帮助我的:我认为是一位数学老师告诉我“当您听到线性时想想线条”。不确定这是否有帮助,但是对我来说,这是“哦,是的!” 瞬间
乔·普兰特

Answers:


229

假设您正在使用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转换或伽马校正。


6
很棒的答案,谢谢,看到事情的解释总是很棒的,我只会要一本书或一种资源,您认为这对于这个主题,色彩空间以及进行sRGB转换所用的公式是足够好的<->线性的,或者近似于此行为的函数是什么。
肯(Ken)

恐怕我对任何好的书籍或资源都不了解。在维基百科页面是全面的,包括所有关于白点,以及如何小色域(我没有提到的,因为大多数人并不需要知道它)的东西,但它使一个有点令人费解。
丹·赫尔姆



1

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

如前所述,我不是专家,我想,如果您想进行一些颜色校正的转换,则应在YUV空间中进行,然后将其转换为RGB(或对RGB进行数学上等价的操作,请注意。数据丢失)。另外,我不确定YUV是颜色的最佳本地表示形式,但是摄像机提供了这种格式,这正是我所遇到的问题。

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


1
请注意RGB <-> YUV转换以及返回。我不确定是否在每种情况下都可以,但是YUV颜色空间有时会转换为16-235的范围,而不是24位RGB的0-255。因此,每次进行色彩空间转换时,都可能会丢失数据。大多数人倾向于说如果可以的话,请保持在相同的色彩空间内。
乔·普兰特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.