关于Gama矫正

什么是Gama矫正

        既然我们要研究Gama矫正,那么首先我们就要知道什么是Gama矫正。

        伽玛校正(Gamma Correction),又称伽马非线性化(Gamma nonlinearity)或伽马编码(Gamma encoding),是影像系统中对光线辉度(luminance)或三色刺激值(tristimulus values)进行非线性运算的反运算方法,主要用于调整图像的非线性色调。伽玛校正通过优化数据位分配补偿人类视觉特性,确保敏感亮度区间的信息存储效率。其应用涵盖灰度计算领域。

        Gama矫正开发之初是为了让CRT管(阴极射线管,CathodeRay Tub)更适宜的显示图像的色调的。因为早期的CRT显示器他的亮度映射有一个特性,就是其输入电压与输出亮度并非成正比。而用于拍摄图像数据的机械(如照相机,摄影机等)却是一个线性关系。所以如果不对得到的图像数据做处理,则在CRT上显示的图像就和拍摄出来的图片有差距。因此在CRT上显示的图像数据值都进行了一个指数型的提升。公式如下所示:

\[ y = (x + esp)^{\gamma} \]

其中, x与y分别为输入、输出图像的灰度值,esp为补偿系数,γ即Gamma系数。通过这个公式,可以将输入图像的灰度值进行一个非线性映射,使得CRT管显示的图像与拍摄的图像更加接近。这个数据的修改是在拍摄器械上进行的,而不是在显示器上进行的。

现在为什么要进行Gama矫正

        从前文中,我们知道Gama矫正是因为CRT管的特性而产生的,而现在我们显示屏早已经可以进行线性的显示了。那我们为什么还要进行Gama矫正呢?我在网上找到了以下的回答:

  1. 行业标准兼容性,之前因为Gama矫正的存在而诞生的行业标准,现在我们也要做一定的兼容。

  2. 人眼视觉特性,人眼对亮度的感知是非线性的,近似幂函数关系。人眼对较暗色调之间的差异比较亮色调之间的差异更敏感 。比如物理功率为50%的灰色,人眼实际感知亮度约等于72.9%(数据存疑),反之,人眼认为的50%中灰色,实际功率约等于21.8%。

  3. 优化数据编码,通过Gamma矫正可以优化数据的位分配。我们可以分配更少的bit数给人眼较难区分的亮区间,而分配更多bit数给人眼较易区分的暗区间。在0-255的有限存储范围内,能更好地平衡亮暗部比例。

对于第一点,我想大家应该不难理解。所以我这里的重点就放在了第二点和第三点上。首先人眼对于亮度的感知是非线性的,关于这个说法你可以在网上找到很多的资料,我这里就不再赘述了。下图是我引用详解 gamma 校正中的一张图:

Physically linear表示物理上亮度线性提升对应显示的颜色。Perceptually linear则是视觉上亮度线性提升对应的显示颜色。我们可以从上图中看出,两者之间的差距还是比较明显的。从图中,我们可以发现一个问题,当我们视觉上感觉亮度到达了0.5时,对应的物理亮度大概是在0.2到0.3这个范围。如果按照物理亮度去存储颜色信息这会导致我们需要更高精度的信息来保留0-0.3范围内的颜色。然而我们精度是固定,所以这就不可避免的导致最终我们丢失一些暗部的细节。通过Gama矫正,我们就可以将0-0.3范围内的颜色进行一个非线性映射,从而保留更多的暗部细节。为了保证暗部的细节时,我们不可避免造成亮部细节的丢失。但是因为人眼对暗部的细节感知更敏感,所以对于亮部细节的丢失是可以接受的。

        除了上面的三个原因外,有些文章还会提到一个原因:光照计算在线性空间中进行,以便确保数学上的正确性,而结果应该在伽马空间中呈现以便让人眼看起来正确。

关于Gama矫正的一些小知识

        以前Gama矫正是在拍摄设备上进行的,又因为行业因素,所以现在部分的颜色格式是默认带有Gama矫正的。默认在sRGB空间下的颜色格式,比如JPEG,PNG等,他们都是带有Gama矫正的。

        现在我们的显示屏已经可以进行线性的显示了,所以就出现部分的数据存储格式,这些数据存储格式是没有进行Gama矫正的。比如RAW格式。因此我们直接看RAW格式下的图片会感觉到颜色偏暗,偏灰。因此在软件中“显影”RAW文件时,第一步就是应用Gamma曲线。

        综上所述,不同颜色格式下,图片所表示的信息不一致。那么这样在我们处理图像的时候就会出现一些问题。关于这一点,各各游戏引擎都会在图像导入的时候做额外的处理。比如Unity引擎会让你选择颜色空间(Color Space)。关于颜色的取值,最好看一下引擎官方文档中的描述。还是以Unity为例,虽然Unity提供了颜色空间取值,但是在图像中一些额外的设置会导致最终取到的颜色可能和我们选择的颜色空间不一致。比如我们选择了线性的颜色空间,但是在图像设置中我们设定为sRGB。那么在最终取值的时候,我们仍然会取到Gama矫正后的值。关于这点可以看一下Unity官方对应的文章:Texture Import Settings采用线性渲染的伽马纹理。特别是采用线性渲染的伽马纹理文章中明确指出:“注意:如果纹理位于线性颜色空间内,需要禁用 sRGB 采样。”

参考资料