HDR转SDR实践之旅(六)传递函数与色差矫正

4,436 阅读10分钟

前言

HDR视频其实已经被传递函数的OETF压缩过了,如下图所示HDR视频存在传递函数HLG,HDR转SDR需要先用传递函数的EOTF解压缩,显示前还要用传递函数的OETF压缩回去,其实普通视频和App开发也要这么处理,然而大多数开发对它不了解导致大多数应用(微信、Mac等)在缩放、模糊、半透明混合等都存在色差问题,本篇文章讲的就是传递函数的正确认知以及如何用传递函数实现色差矫正。如果你觉得有所收获,来给HDR转SDR开源代码点个赞吧,你的鼓励是我前进最大的动力。

image.png

什么是传递函数

摄像头感应自然场景中光的变化用电信号存储下来,显示器把电信号转换成屏幕光信号展示出来,这个传递光的过程就叫传递函数。传递函数包含三个变换,相机采集线性光信号压缩成暗部细节更多的非线性电信号叫做光电转换(OETF),屏幕显示时解压缩的过程叫做电光转换(EOTF),修正相机环境和屏幕环境的亮度差异叫做光光转换(OOTF)。OETF、EOTF、OOTF中O是光optical的缩写、E是电electro的缩写、TF是传递函数transfer funciton的缩写,如下图所示就是传递函数的整个流程。

传递函数.png

如下图所示OETF、EOFT、OOTF其实就是三条曲线函数,OETF和EOTF叠加后会变成一根直线。

image.png

为了排除外界光照的影响使显示效果更好还要叠加OOTF曲线, 最终变成一根略微向下凹的曲线,譬如视频OETFf(x)=x0.5f(x) = x^{0.5},显示器EOTFf(x)=x2.4f(x) = x^{2.4},最终眼睛看到的OOTF曲线就是0.5×2.4=1.20.5×2.4=1.2

image.png

从上图中也可以看出来光电转换和电光转换的区别

传递函数作用
光电转换(OETF)1. 线性光信号变成非线性电信号
2. 颜色变亮,函数曲线向上凸
电光转换(EOTF)1. 非线性电信号变成线性光信号
2. 颜色变暗,函数曲线向下凹

色差问题

传递函数听起来这么抽象,不做视频开发不了解好像也没什么吧?传递函数不仅存在视频中还存在图形界面中,使用错误会导致色差问题,互联网的大部分程序都存在色差问题,因为视频进行OETF处理后颜色非线性的,在非线性空间编辑颜色会导致色差问题。

image.png

用上图测试缩放、半透明混合、高斯模糊三种效果会发现色差问题

图片缩放色差问题

image.png

放大后出现黑色分界线,用测色计测出中间色是暗色,原图其实是没有的。

正常情况下放大后红绿还是分明的,如下图所示 image.png

放大是用插值实现的,插值后的颜色看起来很暗,如下图所示。

放大变暗.png

半透明混合色差问题

<LinearLayout
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:layout_height="match_parent">

    <View
        android:background="#ff0000"
        android:layout_width="200dp"
        android:layout_height="200dp">
        </View>

    <View
        android:layout_marginLeft="-100dp"
        android:background="#8000ff00"
        android:layout_width="200dp"
        android:layout_height="200dp">
    </View>

</LinearLayout>

image.png

Android中红色方块上放半透明绿色方块出现上图中错误的颜色(不符合生活中的常识)。

半透明色混合是通过混合公式合成的,合成的颜色很暗,如下图所示。

混合变暗.png

高斯模糊色差问题

高斯模糊后正常情况中间区域是红绿渐变

redgreen2.png

然而Mac上设为桌面后打开LaunchPad却发现变得黑乎乎

image.png

微信上设为朋友圈封面也变得黑乎乎

1391676044176_.pic.jpg

为什么会这样呢?

image.png

上图是高斯模糊流程,为了解说方便用平均数替换高斯模糊,右边的颜色看起来很暗,这也是微信和Mac设置高斯模糊变成黑乎乎的原因。

色差问题矫正

色差问题的原因

要知道为什么存在色差问题,首先需要梳理一下显示流程。

image.png

上图是视频显示流程,录制时经过Gamma编码,显示时会用Gamma解码,其实就是x经过x0.45x^{0.45}后再用x2.2x^{2.2}还原成x。

注意:

  1. Gamma函数 f(x)=xγf(x) = x^{γ}(γ是Gamma的希腊字母)是传递函数的一种,如下图所示不同标准的γ不一样,曲线也不一样,γ根据显示器所在环境亮度不同取1.8~3.5,全文用BT709标准γ=2.2来讨论问题。Gamma编码就是Gamma函数f(x)=x12.2=x0.45f(x)=x^{\frac{1}{2.2}} =x^{0.45},Gamma解码就是Gamma函数f(x)=x2.2f(x)=x^{2.2},两个函数互为可逆。Gamma函数中的Gamma不是Gamma射线是代号,Alpha、Beta、Gamma在开发中不就是版本号吗,Gamma函数自然可以理解成颜色显示前的最后一个版本调色流程。

    image.png

  2. 为什么显示流程要先Gamma编码再Gamma解码(先x0.45x^{0.45}再用x2.2x^{2.2}),这么绕不能直接用x吗?这后面会提到,先说一下结论因为拍摄时用Gamma编码x0.45x^{0.45}提亮颜色后暗处细节就能多存一点,而Gamma解码x2.2x^{2.2}是为了还原前面的Gamma编码让显示效果和拍摄时一样。

  3. 显示器刚开始只有CRT显示器,CRT显示器内部其实是没有Gamma解码的,内部的电子枪发射到荧光粉上的电子会丢失很巧合地符合了Gamma2.4曲线(Gamma解码),而LED显示器发明以后为了兼容以前的视频人为在屏幕上添加了Gamma解码函数。

那么为什么上述流程在显示时明明是正常的,而在诸如高斯模糊、放大、半透明混合等编辑流程时却出现了色差问题呢?

image.png 上图可在Gamma函数色差问题可视化查看,根据上图你会发现红线始终位于绿线下面,红线表示编辑流程的颜色,绿线表示预期的颜色,这就是为什么上述编辑流程颜色比较暗的原因。先x0.45x^{0.45}x2.2x^{2.2}对显示没有影响,对编辑却有影响,经过x0.45x^{0.45}后再编辑时颜色已经不是线性了,再用x2.2x^{2.2}还原自然有问题了,这就是色差问题的本质,非线性空间编辑颜色

色差问题矫正流程

既然色差问题是因为Gamma函数产生的非线性空间导致的,那么自然也能用Gamma函数矫正色差。视频拍摄时会用Gamma编码,屏幕显示会用Gamma解码,那么编辑时只要把逆过程插入到显示流程中就行,如下图所示其实就是先用Gamma解码把非线性变成线性后编辑,上屏前再用Gamma编码,也就是先x2.2x^{2.2}x0.45x^{0.45}。色差矫正包括了Gamma解码和Gamma编码,这也是为什么Gamma编码不用常规叫法Gamma矫正的原因,Gamma矫正很容易误导成只用Gamma矫正就能实现色差矫正,Gamma矫正之所以叫Gamma矫正,不是因为它矫正了色差而是因为它矫正了暗部细节问题。

image.png

注意: 上述色差矫正流程在实际开发中有两个策略,个人觉得下方第一种策略比较好,遵循最小改动的同时避免不需要色差矫正时速度变慢。

  1. 默认不做色差矫正,只在图像处理(融合、模糊等)下使用
  2. 默认开启色差矫正

把上述流程代入高斯模糊中,如下图所示

image.png 注意: 上面只画了编辑环节的Gamma解码和编码,显示过程中屏幕本身还会Gammma解码的。

细节丢失问题

前面提到传递函数是为了解决暗部细节丢失发明的,那么为什么会丢失细节呢?如下图右边的色带就是量化带来的细节丢失问题。本质原因是高精度的物理亮度量化后会丢失精度,而传递函数利用物理亮度和视觉亮度的关系(视觉对低亮度数据更敏感)重新分配亮度占比,对低亮度的数据多分配一点,高亮度的数据少分配点。

image.png

物理亮度和视觉亮度的关系

image.png

上图用照度计测量15盏LED灯的亮度,每盏LED中都放有不同程度的减光片,最终测出每盏灯的亮度如下图所示。

image.png

注意:

  1. 第一行数字表示视觉看到的亮度,亮度呈线形变化,每盏灯的视觉亮度相差一样
  2. 第二行数字表示物理测量出的亮度,亮度呈倍数上升,每盏灯的物理亮度相差2倍左右

上述实验说明了视觉中线形变化的亮度对应物理世界中倍数上升的亮度,而物理世界才是真实才是线形的,也就是说视觉是对数的,如下图所示。 v2-e3f307e8cefc4c1117c6afe78250bfa6_hd.jpg

量化物理亮度带来的问题

物理亮度用电信号存储时需要量化(8位只能保存0-255),如下图所示

image.png

注意:

  1. 黑色到白色的亮度变化叫做灰阶,红框所示物理中性灰是64视觉中性灰是128
  2. 蓝框所示物理灰阶时存在暗部细节变少的问题,中性灰左边只有64位,右边却有192位。
  3. 为了解决物理灰阶量化时暗部细节变少的问题,只要把物理灰阶的中性灰向右移动变成视觉灰阶就可以了,这个变化就是光电转换,在屏幕显示时需要把视觉灰阶还原成物理灰阶,这个变化就是电光转换。
  4. 同样大小的视觉灰阶比物理灰阶暗,但是把物理灰阶变成视觉灰阶却是变亮。细细品味一下,物理灰阶64比视觉灰阶64暗,物理灰阶64变成视觉灰阶128虽然在不同色阶中看起来亮度没变,但是在同个色阶64变成128就变大变亮了。在实际开发中有时会纠结使用光电转换还是电光转换,记住光电转换会把颜色变亮,电光转换会把颜色变暗。

细节丢失问题本质上是低精度存储高精度数据导致的,而传递函数其实就是利用视觉特性重新分配亮度占比的压缩算法,压缩亮区域扩展暗细节,如果牺牲带宽直接用高精度浮点数存储根本就不需要传递函数,其实传递函数和缩小图片道理是一样的,都是为了解决高频信号(高精度)用低频信号(低精度)来表示带来的细节丢失问题。

问题思考

下面2个问题留给大家思考

  1. 如何在APP开发中解决缩放、半透明混合、高斯模糊的色差问题
  2. 视觉是对数的,那为什么视频的传递函数却用幂函数不用对数函数呢

系列文章