Gamma空间是什么,为什么我们需要它

什么是Gamma,为什么我们需要它

翻译来源:http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/

光辐射与感知亮度

下图中,两个相邻的格子之间光能量辐射差异是常量。

这里写图片描述

下图中,两个向量格子之间感知的光能量辐射差异是常量。

上面两张图,第二张看着更均匀。因为眼睛对光照强度的感知是非线性的。在第一张图片中,两个相邻格子之间的所谓的光照强度是均匀的:

这里写图片描述

第二张图片中,格子间的差异不是常量,而相邻格子间存在幂的关系。 所有人类的感觉知觉在刺激的大小和感知强度方面都遵循类似的权力关系。
所以,我们可以说在所谓的无力感强度和感知亮度之间存在一个幂值关系。

物理和感知的线性

我们想要在计算机的一张图片里存储这样的一个物体:

这里写图片描述

假设我们只能存5位的灰度图片,那么从纯黑到纯白之间就会有32个不同的灰度值。在这个计算机上,灰度值正比于它相应的物理光强度(会生成32个灰度值如下图所示)。可以说相邻值之间,物理上的光子发射量是线性的。如果我们只用这32个值编码渐变的梯度,就会是这样的(暂时不考虑dither抖动):

这里写图片描述

过渡比较突兀,尤其是在左侧。为何左侧的步长比右侧的长很多?因为我们用的是物理光子辐射的线性灰度,而不是我们眼睛感知强度的线性值。原始图和5bit编码后的值在图片中是不均匀的,在较暗的地方比较亮的地方更明显。也就是说我们在较暗的地方丢失了大量的精度,而在较亮的地方用了相对更多的精度。也就是说,我们最好选择适合的32位灰度,使这个错误均匀分布。

如果我们用感知线性来存储原始图片,但对于光子辐射强度是非线性的,而这个非线性和人类感知的非线性比较符合,我们会得到像上面图二中的效果:

这里写图片描述

这里所说的非线性就是上面提到的幂关系,而这个非线性转换(从物理线性灰度值到感知线性灰度值的变换)称为Gamma校正。

有效率的图片编码

为什么上面说的很重要?存储在所谓的”真实色彩“或”24bit“图片中的色彩数据是以每个像素3个8bit的整数的形式存储的。8位,可以代表256个不同的强度等级,如果这些等级是物理线性的,我们会在暗的地方丢失大量精度,而在较亮的地方又不必要的精度。

显示,这是不理想的。一个解决方案就是继续用物理线性,将每个通道提高到16位,显然会浪费空间。因此,又另一个方案:让256个不同等级代表感知线性强度值,这样,大量的图片足够在每个通道8bit的情况下充分显示。

这种用感知线性强度来表示物理线性强度的数据的转换称为gamma编码。

RGB24每个通道采用8位的Gamma编码值来表示光照强度。也就是说RGB(128,128,128)不会辐射RGB(255,255,255)的50%的能量,而是大约22%。由于人类感知的非线性本质,一个光源需要衰减到他的原始光子强度的22%来和人类感知相同,RGB(128,128,128)和人类感知的RGB(255,255,255)一样亮。

当然Gamma校正假设这个图片被从电脑上看到。

Gamma转移函数

把值从线性空间转换到Gama空间的过程成为Gamma编码(Gamma压缩),相反的过程成为Gamma解码(Gamma解压)。

这两个操作的公式特别简单,只需要用前面说的幂函数:

这里写图片描述

标准gamma值在计算机显示系统中用的是2.2,这个值会接近人类感知。准确的值每个人跟每个人都不同,也依赖于光照条件和其他因素,但2.2就是标准值,2.2会表现的很好。

另外一个关键点,就是输入值是0到1之间,输出值也在0到1之间。编码的时候gamma值在0到1之间,解码的时候,值大于1. 下面图中,左侧:gamma编码(值为1/2.2约为0.4545),中间为线性Gamma,右侧为gamma解码(值为2.2).

这里写图片描述

我们现在看的只是灰度的例子,而RGB图片和这个原理一样,我们只要用 同样的公式分别的编码和解码每个颜色通道即可。

Gamma和sRGB

sRGB是一些消费电子设备(显示器、数码相机、扫描仪、打印机、掌上设备)用的事实标准的色彩空间。也是互联网图像的标准颜色空间。sRGB规格定义了编码解码图片用的gamma,sRGB gamma接近2.2,但在特别暗的部分会有一小段线性部分。 一般99%情况下,你想要用sRGB代替Gamma。这是因为所有显卡从2005年就有硬件sRGB支持,所以编码和解码一般都是免费的。你的显示器的主体色彩空间一般是sRGB,所以如果你把一个sRGB编码的像素数据直接丢到framebuffer里,结果图片会在屏幕上看起来是正确的。流行的图片格式比如jpeg,png可以存储颜色空间信息,但图片经常不包含这些信息,所有图片查看器、浏览器习惯上地把他们解释成sRGB。

Gamma校正

我们已经谈了gamma编码和解码,那么Gamma校正是啥?像上面所说,99%的显示器用sRGB色彩空间,但由于制造不精确,大部分显示器可以通过Gamma校正获取最好的效果。如果你不校正你的显示器不代表它不用Gamma校正,大部分过去和现在的CRT和LCD都被设计和制作在sRGB上操作。

将gamma校正视为微调,你的显示器一般sRGB操作,但通过微调,显示器的gamma转换曲线会更接近于我们上面说的理想的Gamma转换曲线。

处理Gamma编码图片

如果整个世界都默认是sRGB,会有啥问题?如果我们的摄像机写入sRGB,jpeg文件,我们仅编码jpeg数据,将它拷贝到显卡的帧缓冲,这个图片在我们的sRGB LCD显示器上大概会正常显示。

问题会出现在,我们开始直接在sRGB像素缓存上运行图片处理算法。记住,gamma编码是一个非线性转换,sRGB编码是在0.4545左右进行gamma编码。所有计算机图片文献上你能找到的图片处理算法都假设像素数据是线性编码光强度,也就意味着,将sRGB编码的数据输入给这些算法将会有些(某种情况下会有较明显的)错误。这包含调整大小、模糊、合成、在像素值间插值、锯齿等等一些最常见的操作。

Gamma错误的影响

色阶

下面的图片显示了线性和sRGB空间下计算的色阶(上面是线性,下面是sRGB)。sRGB值的直接混合,造成了大部分较暗,有时候更饱和的图片:

这里写图片描述

而真实世界中,颜色混合是在线性空间下的。
几乎所有人做的都是错的,css色阶和渐变,photoshop也是错的,还有选项修正这个错误。

颜色混合

在伽马不正确的绘图程序中,某些鲜艳的色彩组合会产生奇怪的黑暗过渡带。如下所示,左侧是正确的,右侧是错误的,是一个渐变问题。

这里写图片描述

alpha混合或组合

另外一个颜色混合,我们看下透明度混合,我们来测试一下彩色的长方形。左侧的图片是现实生活中颜色,右侧为sRGB空间混合,有一些明暗度偏移:

这里写图片描述

错误的颜色在将两个照片混合的时候也很明显,左侧是Gamma正确的,皮肤色调和红黄色还保留但很自然地渐变为带蓝色的图片,而在右侧,显然全都变成蓝绿色了。

这里写图片描述

图片改变大小

手机设备屏幕比一般显示器Gamma的不正确性更明显。所以下面的图片最好在电脑上看。下面的图包含了一个简单的黑色和白色棋盘像素模式(100%zoom左侧,400%zoom右侧)。黑色的RGB(0,0,0),白色RGB(255,255,255).如果你咪眼下,你的眼睛会使图片上的光模糊,你看到一个黑白之间的灰色。

这里写图片描述

pbr

要得到理想的视觉效果,gamma要在整个渲染流程中正确的控制,一般会有两个地方容易出错:
1. 在线性空间中计算,但没有将最终的图片转换成sRGB;
2. 没有将sRGB图片转换到线性空间。
这两个问题合并起来造成很多错误,最终效果无法达到理想效果(比如二次光线衰减,高光过强、诡异的色彩、饱和度偏移等)。
为了演示第一个错误,下面左侧图片显示了一个简单但pbr光照准确性很自然的图片,这个渲染是在线性空间进行,然后帧缓冲的内容在写入硬盘前转换成了sRGB。
在右侧图片中,最后一个转换的步骤忽略了,我试图调整光的强度来达到左侧gamma正确的图片的明暗度。很显然,无法达到目标。所有东西都显得反差很大或过饱和。再怎么改,都失去了物理的意义,换到其他场景和光照条件下又有问题了。所以这样只是白费力气。

这里写图片描述

在Gamma不对的情况下,渲染真实人的皮肤几乎是不可能的,高光无法正确。

这里写图片描述

总结

我们对图片用gamma编码的唯一原因是它允许我们在bit长度有限的情况下更有效的存储图片。它有个优点,用一种接近人类感知的,也就是我们感知亮度用一种幂函数的方式。大部分图片处理算法都期望以线性方式对光强度编码,所以gamma编码的图片需要在允许算法之前进行gamma解码(转换成线性空间)。一般结果需要再次转换到gamma空间,存储到硬盘上,或这在要求gamma编码的显示器上显示。sRGB颜色空间用接近2.2的gamma值。这是互联网,大部分显示器、扫描仪、打印机上的默认色彩空间。 记住,大多数的应用和软件库都没有很好的控制Gamma,所以将他们用到你的工作流之前一定要进行扩展测试。对于一个正确的线性工作流,这个工作链上的所有软件都要100%的gamma正确。 如果你是图像软件的开发者,一定要确定你做的是否正确,清楚的在软件的文档里写上要求的输入输出的颜色空间。