在线性和非线性 RGB 空间中使用颜色有什么实际区别?

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

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

但说到伽玛校正,我不知道在非线性的 RGB 空间工作的效果是什么。

由于我知道如何在图形软件中使用曲线进行图片编辑,我的解释是,在线性 RGB 空间中,你可以按原样取值,不需要操作,也不需要附加数学函数,相反,当它是非线性的时候,每个通道通常会遵循一个经典的幂函数行为。

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

38836 次浏览

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

如前所述,我不是一个专家,无论如何,我认为,如果你想做一些颜色正确的转换,你应该在 YUV 空间,然后转换成 RGB (或者在 RGB 上做数学上等效的操作,小心数据丢失)。此外,我不能肯定 YUV 是最好的本地表示颜色,但视频摄像机提供的格式,这就是我遇到的问题。

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

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

在线性颜色空间中,存储的数字和它们表示的强度之间是线性关系。实际上,这意味着如果你把数字加倍,你就加倍了亮度(灰色的亮度)。如果你想把两个强度相加(因为你是基于两个光源的贡献来计算一个强度,或者因为你是在一个不透明物体的上面添加一个透明物体) ,你可以通过把两个数字相加来做到这一点。如果你正在做任何类型的2D 混合或3D 阴影,或几乎任何图像处理,那么你希望你的强度在一个线性颜色空间,所以你只需要加,减,乘,除数字就可以得到相同的强度效果。大多数颜色处理和渲染算法只能给出线性 RGB 的正确结果,除非您为所有内容添加额外的权重。

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

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

You'll notice we now have a problem: we want our colors processed in linear space, but stored in sRGB. This means you end up doing sRGB-to-linear conversion on read, and linear-to-sRGB conversion on write. As we've already said that linear 8-bit intensities don't have enough darks, this would cause problems, so there's one more practical rule: 不要使用8位线性颜色 if you can avoid it. It's becoming conventional to follow the rule that 8-bit colors are always sRGB, so you do your sRGB-to-linear conversion at the same time as widening your intensity from 8 to 16 bits, or from integer to floating-point; similarly, when you've finished your floating-point processing, you narrow to 8 bits at the same time as converting to sRGB. If you follow these rules, you never have to worry about gamma correction.

当你阅读 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);

Going the other way, when you want to write an image as sRGB, apply this formula to each linear intensity:

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 通道的数字加倍,你就可以把颜色的亮度加倍,但是你不能把 YUV 的颜色像 RGB 颜色那样加在一起或者相乘,所以它不用于图像处理,只用于存储和传输。

我认为这回答了你的问题,所以我将以一个简短的历史记录作为结束。在 sRGB 之前,老式的 CRT 内置了非线性。如果你把一个像素的电压加倍,强度就会加倍。每个监视器有多少不同,这个参数称为 Γ。这种行为是有用的,因为它意味着你可以得到更多的黑暗比灯光,但它也意味着你不能告诉你的颜色有多明亮的用户的阴极射线管,除非你先校准它。Gamma correction意味着转换你开始的颜色(可能是线性)和转换他们的用户的 CRT 的伽玛。OpenGL 来自这个时代,这就是为什么它的 sRGB 行为有时有点令人困惑。但是 GPU 供应商现在倾向于使用我上面描述的约定: 当你在纹理或帧缓冲区中存储8位强度时,它是 sRGB,当你处理颜色时,它是线性的。例如,一个 OpenGL ES 3.0,每个帧缓冲区和纹理都有一个“ sRGB 标志”,您可以打开它来启用读写时的自动转换。根本不需要显式地进行 srGB 转换或伽玛校正。