为什么每个程序员都应该知道Gamma

首先来做一个小的测试

如果你曾经写过或正在计划写图像处理方面相关的代码,你应该试着完成一下这个测试。如果测试结果中有一个或多个的回答是“是”,那么你写的代码很可能有一些错误导致输出不正确的结果。但你可能不会立即觉察出来,因为这个问题并不那么明显,在有些情况下可能会比较容易发现。

测试如下:

  • 我不知道什么是伽马校正(吁!)
  • Gamma是CRT显示器时代的遗留问题,现在几乎都用LCDs了,可以安全的忽略它。
  • Gamma在要求颜色显示非常精确的打印产业比较重要,一般的图像处理可以忽略它。
  • 我是一个游戏程序员,我不需要知道gamma。
  • 操作系统的图形库已经正确的处理了gamma问题。
  • 我用的流行图形库如OpenGL,DirectX等,已经正确处理了gamma问题。
  • (128,128,128)RGB值的像素显示亮度大约是(255,255,255)RGB值的像素显示亮度的一半。
  • 只需直接使用一些随机库和图像处理算法,就可以将像素数据从流行的图像格式(JPEG,PNG,GIF等)加载到buffer中。

如果你的大多数答案都是“是”,不用沮丧。因为一周之前我大部分的回答也是“是”。在某种程度上,Gamma问题并没有引起用户的注意(包括编写商业图形软件的程序员),而且到目前为止,大多数图形库、图像查看器,图形编辑器和绘图软件仍然没有正确处理Gamma问题,并显示了不正确的结果。
接着往下看,到最后,你将比大多数程序员都更了解Gamma问题。

神秘的Gamma校正

视觉可以说是人机交互中最重要的感官输入通道。但另人惊讶的是Gamma校正是程序员间谈论最少的话题之一,而且在技术文献中也很少提到,包括计算机图形学相关的书籍。在大多数计算机图形学的教科书里都没有明确提到Gamma校正的重要性,有些书虽然提到了Gamma校正,但表达的比较模糊和抽象,既没有举例说明实际应该怎么做,也没有展示不正确处理Gamma校正而产生的错误图像的例子。

在我写光线跟踪器时发现需要正确的处理Gamma问题,然而我当时对这个问题的理解也非常浅显。所以花了几天时间在网上查找相关资料,结果发现大部分文章都比较抽象和模糊,有一些包含了许多有趣但无关的细节,另一些则缺少形象的例子。其实Gamma问题并不是一个很难理解的概念,但是找到一篇能正确,完整和清晰的解释这个问题的文章却没那么容易。

什么是Gamma问题,我们为什么需要它?

我尝试从零开始,全面的解释一下Gamma问题
为了确保文中图像示例显示的正确性,需要在显示器(CRT或LCD无所谓)上使用现代浏览器查看。与显示器相比,平板电脑和手机通常显示的不准确,尽量避免使用。

光发射 vs 感知亮度

不管你信不信,下面图像中任意两条相邻竖线之间的光能发射的差是一个常数。换句话说,屏幕发出的光能量从左到右,从一个竖条到另一个竖条是以一个常量增加的。
image

图像1–均匀分布的发射光强的灰度条

现在考虑下面这幅图像:
image
图像2–均匀分布的感知光强的灰度条

上面哪幅图像的渐变显的更均匀呢?明显是第二幅!但为什么呢?我们刚刚建的第一幅图可是从最暗的黑色到最亮白色均匀增加的。但是为什么我们看到的不是一个自然的渐变呢。为什么我们感觉第二幅图才是均匀的线性渐变呢?

答案就在于人眼对光强度的反应,是非线性的。在第一幅图中,两个相邻竖条之间的差值是常数的:

Δ linear=I n − I n−1

而在第二幅图像中,坚条与坚条间并不是常数,而是遵循幂律关系。所有人类的感觉,知觉在刺激的大小和感知强度方面也都遵循相似的幂律关系

因此,我们认为在实际的物理光强度和感知亮度之间存在幂律关系

物理vs性线感知

假设我们想在电脑上存储一幅反应现实世界的图像(让我们假设现实世界存在完美的灰度渐变),下面是“现实世界对象”看起来的样子:
image

图像3–理想的灰度渐变

现在假设我们有一个特定的计算机系统,只能存储5-bit灰度图像,这可以表示32种从纯黑到纯白的不同灰度的色调。另外,在这台特定的电脑上,灰度值与相应的物理的光强度成正比,结果会显示如图1那样的有32个竖条的灰度图,这个灰度图表示光强度值之间是连续线性的。
如果我们只使用这32种灰度值编码表示平滑渐变,我们得到类似下图的显示:
image
图像4–理想的用32位物理线性灰度值表示的平滑灰度渐变

然而,这个过渡看起来相当不自然,尤其是左边,因为我们只有32种灰度值。如果眯一眯眼睛,就很容易让自己相信这是大概“精确”的平滑渐变。但注意这个之所以看起来比较平滑是因为左边比右边的跨度大,这样做是因为光发射是线性的,而我们的眼睛的感知是非线性的。

上面这种显示方式,虽然人眼看起来是比较平滑的渐变了,但明显失去了许多黑色的精度值,多了更多的白色的精度值,我们最好选择另一套不同的32种灰度值,让灰度值与感知上的灰度一致,而不是线性的光强度值。这样我们得到与图4差不多的图像:

image

图5–理想的用32位物理线性灰度值表示的平滑灰度渐变

我们在这里讨论的非线性是我们之前提到的幂律关系,将物理线性灰度值变换成感知线性灰度值的过程叫做Gamma校正

高效的图像编码

为什么上述的对应关系比较重要呢?所谓的“真彩色”或“24位”位图图像中的颜色数据被存储为三组8bit数据。每组能表示256种不同的颜色强度,如果这些颜色信息是物理线性的,那么我们将失去大量的暗色调颜色,而多了许多不必要的亮色调颜色,就像上面展示的那样。

显然,这并不是理想的结果。一种解决方案是将每个8位表示增加到16位(或更多).这将增加两倍或更多的存储需求,而这显然不是一个好的方案。另一种方案就是将256种表示成感知线性尺度上的强度值,使用这种编码,绝大多数图像都能充分的表示出来。

通过算法或线性捕捉设备(如数码相机或扫描仪的CMOS)生成的物理线性强度转换成离散的感知线性强度的过程叫Gamma编码

现在几乎所有的消费级电子设备都采用每通道8位的Gamma编码值表示光强度。如果你还记得前面讨论过的RGB(128,128,128)像素的问题,这个值不是RGB(255,255,255)像素的50%光强度,而只有22%左右。还是因为人类视觉感知是非线性的,在人类视觉看来,光源实际衰减到原来光强的22%,恰好是视觉感知的亮度的50%。RGB(128,128,128)像素亮度看起来是RGB(255,255,255)的一半。如果你发现这里比较困惑,那就稍微思考一下,对目前所讨论的内容有一个坚实的理解是非常重要的。

当然,Gamma编码总是假设图像最终是由人在计算机屏幕上看。可以将其看作是一个图像的有损MP3压缩。如果是其它目的(如用于科学分析或后处理图像),则通常使用线性比例是更好的选择。这一点稍后会说到

Gamma转换函数

将线性空间转换为Gamma空间的过程称为Gamma编码(或Gamma压缩),反之则称为Gamma解码(或Gamma解压)

这两个转换公式非常简单,只需要使用上述的幂律函数:

V encoded=V linear 1/γ

V linear=V encoded γ

计算机显示中使用的标准γ值是2.2.主要是因为Gamma的2.2近似于人眼知觉的灵敏度。虽然受照明或其它条件的影响,每个人的确切的值不一定相同,但必须选一个标准值的话,2.2这个值已经证明足够好。

还有一个问题是,许多文章并没有提到一个非常重要的问题,那就是输入值必须在0-1的范围内,输出也将映射到相同的范围。可以看出,0-1之间的Gamma值用于编码(压缩),大于1的用于解码(解压)。下图演示了编码和解码的Gamma转换函数,加上Gamma值为1的情况。
image

图6–Gamma转换函数
a)Gamma编码或Gamma压缩(γ=1/2.2=0.4545)(输入是物理亮度,输出是感知亮度)
b)线性Gamma(γ=1.0)
c)Gamma解码或Gamma解压(γ=2.2)(输入是感知亮度,输出是物理亮度)

到目前为止,我们只看了灰度值的例子,但对于RGB图像也没什么特别的–我们也仅仅是单独对每一个颜色通道使用同样的方法编码和解码。

Gamma vs sRGB

sRGB是一种颜色空间,是目前消费级电子设备(包括显示器,数码相机,扫描仪,打印机和手持设备)的事实标准。也是互联网上图像的标准颜色空间。

sRGB规范定义了Gamma,用于编码和解码sRGB图像。sRGB的Gamma值非常接近标准的2.2Gamma值。但它有一个短的直线在非常暗的范围,以避免出现斜率为0导致的无穷大的情况。

其实不需要了解这些细节。最重要的是要知道,在99%的情况下,你可以使用sRGB代替普通gamma。这是因为自2005年左右,所有的显卡都在硬件上支持了sRGB颜色空间,大大节省了编码和解码时间。你的显示器的原生颜色空间几乎都是sRGB(除非是于于照片或视频作品的专业图像显示器),所以把一个sRGB编码的像素数据直接输出到帧缓冲,最终在屏幕上生成的图像看起来是正确的。流行的图像格式,如JPEG和PNG可以存储颜色空间的信息,但通常的图像不包含这些数据,在这种情况下,几乎所有的图片查看器和浏览器都按约定将颜色解释到sRGB空间。

Gamma校正

上面已经说完了Gamma的编码和解码,但什么是Gamma校正呢?

虽然现在99%的显示器都使用sRGB色彩空间,但由于制造误差,绝大多数显示器会额外的使用Gamma校正达到最佳的显示效果。你可能从没校准过你的显示器,但并不意味着,它不会使用Gamma。

通过Gamma校正的微调,显示器可以使终在sRGB空间工作。通过校正显示器的Gamma曲线(在显卡或操作系统级别),可以得到更接近理想的Gamma函数。

处理Gamma编码图像

如果整个世界默认为sRGB,那么通过相机生成的sRGB JPEG文件,就可以直接解码JPEG图像数据,复制到显卡的帧缓冲区,图像会正确的在sRGB LCD显示器中显示(这里的“正确”,意味着能更准确的反映拍摄的真实场景)

这问题就发生在用任意图像处理算法直接处理sRGB像素缓冲区的时候。由于Gamma编码是一个非线性变换,sRGB编码是 γ=1/2.2的非线性变换。但几乎在所有计算机图形学的书上涉及的图像处理算法,都假设是按实际光强线性编码的。这意味着直接将sRGB编码数据做为这些算法的输入时,渲染结果可能发生微妙或明显的错误。包括,缩放,模糊,组合,插值,抗锯齿等常见操作。

错误处理Gamma的效果

说了这么多的理论,来看看这些错误实际显示是什么样子的!我们将探讨最常见的当直接处理sRGB编码数据时的不正确结果。除了说明性的目的,这些示例,也有助于在绘图程序和图像处理库中发现Gamma处理不正确的的行为或错误。

这些例子能清楚的表明,gamma不正确的问题。多数情况下,生动,饱和的颜色最明显。柔和的颜色可能不那么明显,甚至可以忽略不计。但误差总是存在的。

渐变

下面的图像显示了渐变在线性空间(上面的部分)和sRGB空间(下面的部分)显示的不同之处。直接在sRGB空间插值会产生更暗或更饱和的图像。

仅看这些,可能更喜欢sRGB空间的图像,特别是最后两个。但这并不是光在真实世界中的表现方式(想像两种颜色的光源照亮白色的墙。结果更像线性空间显示的情况)
image

图7–在图中的每一对渐变中,上半部分是两种颜色在线性空间插值后转换成sRGB(Gamma正确)的结果,下半部分是同样的两种颜色直接在sRGB空间(Gamma不正确)的插值结果

几乎每个人都错了:CSS渐变和过渡是错误的(看 这里了解细节),Photoshop是错误的(如CS6),甚至没有一个选项来修复它。
两个绘图程序得到的是正确结果, KritaPixelmator。SVG也可以 让用户指定是否使用线性空间或sRGB空间进行插值,组合和动画.

颜色混合

使用没正确处理Gamma的绘图程序中的笔刷画图时,在某些生动的颜色组合的情况下,会产生奇怪的黑乎乎的过渡带。(笔刷就是一个小的渐变)

一些人在Adobe论坛中宣称如何在Photoshop中混合颜色来模仿现实生活中的画面。这其实和那无关,这只是由于直接到sRGB像素数据编程的结果,导致的遗留问题。
image

图8–Gamma不正确的颜色混合,左边是Gamma校正的图像(通过在Photoshop CS6启用“使用Gamma1.0混合颜色”选项),右边是不正确的Gamma(关闭Gamma1.0混合选项,使用默认的过期模式)

Alpha 混合或合成

作为颜色混合的另一种变体,来看看Alpha混合是如何显示的。看下面的彩色矩阵图像。左边Gamma正确的图像模仿了光在现实生活中的行为,而右边的sRGB空间混合显示了一些怪异的色调和亮度变化。
image

图9–Gamma不正确对Alpha混合的影响,上面的条是100%不透明,下面是50%透明.左图是Gamma校正图像(在Photoshop CS6处理)右图是不正确的结果,类似图8

当将两张照片混合是,颜色错误的问题也很明显。在下图中左侧的Gamma正确的图像上,皮肤上的红色和黄色被保留并以自然的方式过渡到蓝色,而在右图则有一个明显的绿色投射。同样,这可能是你喜欢的你一个效果,但并不是正确的Alpha合成结果。
image
图10–合成照片时,Gamma不正确的显示结果,上面两个原始图像分别放在彼此的底部,在蓝色图像上有60%的不透明度。左图是Gamma校正的图像,右图是不正确的(由Photoshop cs6测试)

图像缩放

下面的图像包含一个简单的黑白棋盘像素图案(左边是100%缩放,右边是400%的缩放)。眼睛眯一些来看,你会看到一个介于绝对黑色和白色的灰色强度(即50%的灰度)。
image

图像11–黑白格子图案常用于简单的Gamma校正程序,图像平均光强,等于50灰色正方形的平均光像,右图是放大400%后的实际图案。

从上面来看,如果将图像缩到50%,结果应该是一个相似的处理过程,得到一个50%灰度的矩形填充。
来试一试。在下图A是一个棋盘模式,B是直接在sRGB空间缩放到50%(使用双立方插值),C是线性空间插值,然后转换到sRGB空间。
image
图12–Gamma不正确的情况下图像调整的效果。A是一个棋盘模式,B是在sRGB空间调整的结果(Photoshop CS6 8位RGB模式),C是缩放之前先转换成线性空间再缩放,然后再转回sRGB的结果(Photoshop CS6 32 RGB模式)

毫不奇怪,C给出了正确的结果。但是没有正确的Gamma校正,灰色阴影可能不是模糊棋盘图案的精确匹配。甚至数学上也清楚的表明:一个50%像素的灰色像素,其亮度比白色像素高出一半,其RGB值大约为(186,186,186),看以下Gamma编码:

0.5 1/2.2≈0.72974
0.72974*255 = 186

(不用担心图像的50%灰度是RGB(187,187,187),这小差异是因为图像是sRGB编码,这里只是使用了更简化的Gamma公式)

Gamma不正确的大小调整也会导致一些图像上奇怪的色调偏移。

抗锯齿

当遇到Gamma校正时,反走样也不例外。在Gamma=2.2的空间里,文字有些厚,像粗体(如下面的右边图像),在线性空间运行看起来会好些(左图),虽然这个看起来又有点瘦。Photoshop的反走样字体默认使用gamma=1.42,确实产生了漂亮的结果(如中间图像),这是因为大部分字体设计的是Gamma不正确的,如果使用线性空间,字体看起来会更瘦一些。
image

图13–文字反走样在Gamma不正确时的效果。左图使用”Blend Text Colors Using Gamma”设置成1.0,中间设置成1.45,右边设置成2.2

基于物理的渲染(PBR)

如果Gamma校正在一种问题上必须需要的话,那就是PBR。为了使结果更有真实感,Gamma在整个渲染管线都应该正确的处理。但通常会在这个过程中出现问题,以下是两种常见的问题:

  • 在线性空间计算,但转换最终图像到sRGB空间时失败了,则通过对材质和灯光做微小的调整来补偿。
  • 从sRGB空间转到线性空间失败(或使用硬件加速设置sRGB标志)

这两种基本错误以各种有趣的方式出现。但最终结果总是不能得到正确的实际场景。(如,二次光不出现二次了,亮度夸大了,奇怪的色相和饱和度变化等)。

用我的ray tracer演示第一个错误,下面的左边图像是一个非常简单但看起来非常自然的物理光照。这个渲染在线性空间中进行,然后在写入磁盘之前,将帧缓冲区数据转换到sRGB空间。

然后,看右图所示,展示的是,省略了最后一步的转换导致的结果,我试图调整光线亮度以匹配Gamma正确的亮度。很明显这不是正确的做法。所有物体看起来明暗差别非常强烈,所以我减淡材料的颜色,在靠近左边的物体附近补灯。但注定是一场失败的战争,再多的调整都不能使图像看起来在物理意义上是正确的。即使使用照明设置一个看起来可接受的特定场景。任何场景的变化都可能需要一轮新的调整使结果看起来正确。更重要的是选择的材质和照明参数完全没有任何物理意义。只是随机去适应特定的场景,而且不能适合其它场景。
image

图14–在漫反射球上的Gamma不正确的结果。右图是不正确的Gamma,通过调整参数,以适应左边正确的Gamma图像

在3D渲染中找出不正确的Gamma也很重要,在一些老游戏中”fake plasticky CGI look”问题(人物看起来像塑料)就是其中之一。见下图,在gamma不正确的方式上渲染真实感的人物皮肤几乎是不可能的。高光看起来从来都不正确。修复这些问题应该从源头上解决问题而不是用各种微调来拟补错误。
image
图15–在人头像上Gamma不正确时的显示结果。上面部分看起来像真实的人脸,下面看起来像蜡像(图像来源由 Unity3D manual)

结论

这就是关于Gamma编码和解码的问题,恭喜你,现在是一个官方认证的gamma-compliant程序员!:)

回顾一下,使用Gamma编码的唯一原因,就是它可以让我们在有限的位编码长度上更有效的存储图像。它利用了人类视觉以幂律函数感知亮度的特点。大多数图像处理算法都是接收的线性编码,因此在运行这些算法之前,需要将gamma编码的图像先解码到线性空间。通常计算结果需要再转回到Gamma空间以便存储到硬盘或在支持gamma编码的显卡中显示(大部分显卡都支持)。标准的sRGB空间使用的Gamma值大约是2.2。互联网上的图像和大多数显示器,扫描仪和打印机默认的都是sRGB空间。当有疑问时,使用sRGB空间。

从终端用户的角度来看,大多数应用程序和软件库,都没有正确的处理Gamma,因此在工作中使用它们时一定用要充分的了解和大量的测试。为了线性的工作流,所有工作流的中间链上都要保证Gamma100%正确性。

如果你是一个图形程序员,请确保做正确的事情。在文档中显式的声明输入和输出的色彩空间,以保证Gamma的正确性。
原文链接:http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/

### 回答1: Erich Gamma与其合著者在1994年出版了一本名为《设计模式:可复用面向对象软件的基础》的书籍,该书成为了设计模式领域重要的参考资料之一。这本书广泛地介绍了23种常见的设计模式,并提供了详细的示例和应用场景。 设计模式是一种针对面向对象软件开发的可复用解决方案。通过使用设计模式,开发人员能够在软件开发过程中面临的常见问题上提供一致的解决方案。 《设计模式:可复用面向对象软件的基础》一书的作者Erich Gamma是著名的计算机科学家和软件工程师,他出色的在设计模式领域的工作成就使他受到了广泛认可和赞誉。他与其他三位合著者共同介绍了每种设计模式的定义、结构、应用场景以及优缺点。 这本书详细介绍了23种设计模式,包括创建型模式、结构型模式和行为型模式。创建型模式旨在提供以某种方式创建对象的机制,例如工厂方法模式、抽象工厂模式和单例模式。结构型模式主要涉及对象之间的组合和实现关系,例如适配器模式、代理模式和装饰器模式。行为型模式关注对象之间的通信和相互作用,例如观察者模式、策略模式和迭代器模式。 《设计模式:可复用面向对象软件的基础》这本书在软件开发领域产生了深远的影响,并成为了设计模式领域的经典著作。它提供了一种共享的设计方法,可以帮助开发人员更好地解决复杂软件开发中的问题。 ### 回答2: 设计模式是软件工程中一种重要的编程思想,用于解决软件设计和开发中的常见问题。《设计模式》(Design Patterns)是由Erich Gamma等人于1994年出版的一本著名的书籍。该书总结了23种经典的设计模式,并提供了详细的示例和解释。 《设计模式》这本书的作者Erich Gamma是一位计算机科学家,是众多软件开发领域的重要人物之一。他是一位大师级人物,曾为IBM工作,并是Eclipse项目的发起人之一。这本书的出版对软件工程领域产生了深远的影响。 《设计模式》一书中介绍的设计模式可以分为三个类别:创建型模式、结构型模式和行为型模式。创建型模式主要解决对象的创建问题,如工厂方法模式和单例模式。结构型模式主要解决对象之间的组合和关联问题,如适配器模式和装饰器模式。行为型模式主要解决对象之间的通信和协作问题,如观察者模式和迭代器模式。 这本书的内容非常详细和实用,适合软件工程师、程序员和计算机科学专业的学生阅读。它不仅介绍了各种设计模式的概念和使用方法,还提供了丰富的示例代码和实际应用场景。通过学习这本书,读者可以了解到如何选择和应用适当的设计模式来解决实际的软件设计问题。 总之,《设计模式》这本书是软件工程领域的经典之作,是学习和掌握设计模式的重要参考书籍。它的影响力广泛,并对软件开发实践产生了积极的影响。无论是初学者还是有经验的开发者,都应该阅读并掌握这本书中的内容,以提高自己的设计和开发能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值