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

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

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

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

因为我知道如何在graphics软件中使用曲线进行照片编辑,所以我的解释是,在线性RGB空间中,您可以按照原样使用这些值,不需要操作,也不需要附加math函数,而是非线性的信道通常会随着经典的幂函数行为而演变。

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

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

在线性色彩空间中,您存储的数字与其表示的强度之间的关系是线性的。 实际上,这意味着如果您将数字加倍,则会使亮度加倍(灰色的亮度)。 如果你想把两个强度加在一起(因为你要根据两个光源的贡献来计算强度,或者因为你在一个不透明物体的顶部添加了一个透明的物体),你可以通过添加两个号码在一起。 如果您正在进行任何types的二维混合或三维阴影或几乎任何image processing,那么您需要在线性色彩空间中进行强度处理 ,以便可以对数字进行加,减,乘,除以得到相同的效果在强度上。 大多数颜色处理和渲染algorithm只会给出线性RGB的正确结果,除非您为所有内容添加额外的权重。

这听起来很简单,但是有一个问题。 低强度的人眼对光的敏感度比高强度的要好。 也就是说,如果你列出所有可以区分的强度,那么就会有更多的黑暗比轻的更强烈。 换一种说法,你可以比灰色的阴影更好地分辨深灰色的阴影。 特别是,如果使用8位来表示强度,并且在线性色彩空间中执行此操作,则会产生太多的光线阴影,而且没有足够的深色阴影。 在黑暗的地方,你会得到一些条纹,而在你的光线较暗的地方,你正在浪费一些近乎白色的阴影,以至于使用者无法辨别。

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

您会注意到我们现在有一个问题:我们希望我们的颜色在线性空间中处理,但存储在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色彩相加或相乘,因此不能用于image processing,仅用于存储和传输。

我认为这回答你的问题,所以我会以一个快速的历史logging结束。 在sRGB之前,旧的CRT曾经有一个非线性的内置。 如果将像素的电压加倍,则会使亮度增加一倍以上。 每台显示器有多less不同,这个参数被称为伽玛 。 这种行为是有用的,因为这意味着你可以得到比灯光更多的黑暗,但是这也意味着你不能分辨你的颜色在用户的CRT上有多亮,除非你先校准。 伽马校正意味着转换你开始的颜色(可能是线性的),并将它们转换为用户CRT的灰度系数。 OpenGL来自这个时代,这就是为什么它的sRGB行为有时会有点混乱。 但是GPU供应商现在倾向于使用上述惯例:当你在纹理或帧缓冲区中存储8位强度时,它是sRGB,当你处理颜色时,它是线性的。 例如,OpenGL ES 3.0中,每个帧缓冲区和纹理都有一个“sRGB标志”,您可以在读取和写入时打开以启用自动转换。 你根本不需要明确地做sRGB转换或伽马校正。

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

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

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