首先来做一个小的测试

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

测试如下:

  • 我不知道什么是伽马校正(吁!)
  • 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 感知亮度

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

图像1–均匀分布的发射光强的灰度条
现在考虑下面这幅图像:
图像2–均匀分布的感知光强的灰度条
上面哪幅图像的渐变显的更均匀呢?明显是第二幅!但为什么呢?我们刚刚建的第一幅图可是从最暗的黑色到最亮白色均匀增加的。但是为什么我们看到的不是一个自然的渐变呢。为什么我们感觉第二幅图才是均匀的线性渐变呢?

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

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

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

物理vs性线感知

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

图像3–理想的灰度渐变
现在假设我们有一个特定的计算机系统,只能存储5-bit灰度图像,这可以表示32种从纯黑到纯白的不同灰度的色调。另外,在这台特定的电脑上,灰度值与相应的物理的光强度成正比,结果会显示如图1那样的有32个竖条的灰度图,这个灰度图表示光强度值之间是连续线性的。
如果我们只使用这32种灰度值编码表示平滑渐变,我们得到类似下图的显示:
图像4–理想的用32位物理线性灰度值表示的平滑灰度渐变
然而,这个过渡看起来相当不自然,尤其是左边,因为我们只有32种灰度值。如果眯一眯眼睛,就很容易让自己相信这是大概“精确”的平滑渐变。但注意这个之所以看起来比较平滑是因为左边比右边的跨度大,这样做是因为光发射是线性的,而我们的眼睛的感知是非线性的。

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

图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解压)

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

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

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

图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空间的图像,特别是最后两个。但这并不是光在真实世界中的表现方式(想像两种颜色的光源照亮白色的墙。结果更像线性空间显示的情况)

图7–在图中的每一对渐变中,上半部分是两种颜色在线性空间插值后转换成sRGB(Gamma正确)的结果,下半部分是同样的两种颜色直接在sRGB空间(Gamma不正确)的插值结果
几乎每个人都错了:CSS渐变和过渡是错误的(看 这里了解细节),Photoshop是错误的(如CS6),甚至没有一个选项来修复它。
两个绘图程序得到的是正确结果, Krita和 Pixelmator。SVG也可以 让用户指定是否使用线性空间或sRGB空间进行插值,组合和动画.

颜色混合

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

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

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

Alpha 混合或合成

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

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

图像缩放

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

图像11–黑白格子图案常用于简单的Gamma校正程序,图像平均光强,等于50灰色正方形的平均光像,右图是放大400%后的实际图案。
从上面来看,如果将图像缩到50%,结果应该是一个相似的处理过程,得到一个50%灰度的矩形填充。
来试一试。在下图A是一个棋盘模式,B是直接在sRGB空间缩放到50%(使用双立方插值),C是线性空间插值,然后转换到sRGB空间。
图12–Gamma不正确的情况下图像调整的效果。A是一个棋盘模式,B是在sRGB空间调整的结果(Photoshop CS6 8位RGB模式),C是缩放之前先转换成线性空间再缩放,然后再转回sRGB的结果(Photoshop CS6 32 RGB模式)
毫不奇怪,C给出了正确的结果。但是没有正确的Gamma校正,灰色阴影可能不是模糊棋盘图案的精确匹配。甚至数学上也清楚的表明:一个50%像素的灰色像素,其亮度比白色像素高出一半,其RGB值大约为(186,186,186),看以下Gamma编码: 0.51/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不正确的,如果使用线性空间,字体看起来会更瘦一些。

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

基于物理的渲染(PBR)

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

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

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

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

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

图14–在漫反射球上的Gamma不正确的结果。右图是不正确的Gamma,通过调整参数,以适应左边正确的Gamma图像
在3D渲染中找出不正确的Gamma也很重要,在一些老游戏中”fake plasticky CGI look”问题(人物看起来像塑料)就是其中之一。见下图,在gamma不正确的方式上渲染真实感的人物皮肤几乎是不可能的。高光看起来从来都不正确。修复这些问题应该从源头上解决问题而不是用各种微调来拟补错误。
图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/

为什么每个程序员都应该知道Gamma相关推荐

  1. 100%的程序员都想挑战的算法趣题!| 码书

    计算机的世界每天都在发生着深刻的变化.新操作系统的发布.CPU性能的提升.智能手机和平板电脑的流行.存储介质的变化.云的普及--这样的变化数不胜数. 在这样日新月异的时代中,"算法" ...

  2. 凌晨三点,各类程序员都在干吗?

    1.凌晨三点,各类程序员都在干吗? 我想问,前端是不是提早下班了? 2.三天后再回头看看自己写的代码 3.代码质量 VS 开发时间 4.程序员的爱情观 5.一定要看到最后 记得点击「在看」,然后转给你 ...

  3. 每个程序员都应该知道的10件事!

    如果你已经编程了一段时间,并且想学习编程,那么你可能在想什么才是一个好的程序员?计算机科学与技术专业毕业生能做些什么,来为软件开发职业生涯做准备? 在本文中,我将分享10件我认为每个程序员都应该知道的 ...

  4. 老程序员都去哪儿了?

    摆在老程序员们面前有三条路,一是转行,二是继续钻研成为技术大牛,三是转型为管理人员. 我最近采访了十五位30岁以上的老程序员们,在此我想发表下我的观点. 网络上总有这类观点-- 「如果所有的技术都想着 ...

  5. 我敢打赌,这是98%的程序员都想挑战的算法趣题!

    计算机的世界每天都在发生着深刻的变化.新操作系统的发布.CPU性能的提升.智能手机和平板电脑的流行.存储介质的变化.云的普及--这样的变化数不胜数. 在这样日新月异的时代中,"算法" ...

  6. 优秀的程序员都在哪里 如何寻找优秀的程序员?

    优秀的程序员都在哪里 这是你第一次公开招募雇员.如同大多数人一样,你会发布广告,可能也会浏览一些大型的网上论坛,然后你就收到了一吨的简历. 一份份看下去,你会想:"嗯嗯嗯,这人应该可以.&q ...

  7. 每个程序员都应该读的书

    在国外一网站stackoverflow看到了一篇贴子,<每个程序员都应该阅读的书>,里面有上百种书,部分图书已由图灵教育出版.因为除了这里面的书,图灵教育认为还有一些值得程序员去阅读的书, ...

  8. 为什么优秀的程序员都成了无能的领导?

    作者 | Zachary Minott 译者 | 弯月 出品 | CSDN(ID:CSDNnews) 以下为译文: 小明是一位雄心勃勃且成绩斐然的开发人员. 他工作非常努力,每天都会想方设法磨练自己的 ...

  9. 35+的大龄程序员都去哪里了?

    作者 | 年素清 来源 | 码农故事汇(ID:sunianqingshi) 对于互联网人,尤其是程序员来说,35岁是个尴尬而危险的年纪,业内时常传出清退大龄程序员的消息.那么,那些35岁以上的程序员都 ...

最新文章

  1. AI:一个20年程序猿的学习资料大全—人工智能之AI/机器学习/深度学习/计算机视觉/Matlab大赛——只有你不想要的,没有你找不到的
  2. 2016面试——腾讯、蚂蚁金服、蘑菇街
  3. android 跟随动画,Android实现View拖拽跟随手指移动效果
  4. scrum 开发方式学习笔记
  5. Linux程序接口实验:取进程标志及用户信息
  6. SDOI 2009 BIll的挑战
  7. Android Studio(3)---Android Studio的配置
  8. python时间戳转为datetime格式_python 时间 时间戳 转换
  9. 推荐CSDN排名前1000博主
  10. rsync: [sender] write error: Broken pipe (32) 问题排查
  11. python中idle什么意思_python的idle是什么
  12. 高德地图API调用和数据解析
  13. 数学有什么用处?看完后恍然大悟!
  14. [附源码]java毕业设计在线学习网站的设计与实现
  15. 一套系统要不要这样贵,5亿美元
  16. 多周期时间序列分解算法——MSTL原理
  17. 【云原生】阿里云容器镜像服务产品ACR EE
  18. Unknown custom element: did you register the component correctly? For recursive compo
  19. ES倒排索引与分词详解
  20. oracle审计查询sql语句,Oracle 数据库审计

热门文章

  1. 算法训练Day50 | LeetCode123. 买卖股票的最佳时机III(最多买卖2次);LeetCode188. 买卖股票的最佳时机IV(最多买卖K次)
  2. 微信小程序快速点击两个按钮会跳转两个页面
  3. IBM院士:拯救地球—工程师站出来!
  4. camunda工作流开发实战------03 hello world
  5. java mysql数据库回退_数据库事务及Java中的处理
  6. Mybatis分页方式及实现原理
  7. (转载)支付宝对账单URL解析,不保存文件
  8. php 时间相差 小时 分钟,php程序时间相差8个小时的解决办法
  9. 你亲爱的App为何被拒?
  10. php微信小程序毕业设计 php家政服务预约小程序毕业设计开题报告功能参考