HDR转SDR实践之旅(八)色调映射

2,706 阅读6分钟

背景

在3月份开始更新HDR转SDR实践之旅,当时代码还没写只是建了库,没想到更新了几篇文章这么受欢迎,代码还没上传已经有几十个赞了,还有小伙伴提了个issue每个月催我更新代码,这个领域资料太少了,边学边写,现在总算完成了,不辜负大家的等待,如果你觉得有所收获,来给HDR转SDR开源代码点个赞吧,你的鼓励是我前进最大的动力。

效果预览

现有功能实现如下,供大家一起学习一起上进

  1. 输出模式(直接输出到Surface、经过OpenGL中转)
  2. 视图模式(无缝切换SurfaceView和TextureView)
  3. 多种纹理来源配置(Auto、YUV420Buffer、外部纹理OES、Y2Y)、纹理位深配置(8位、10位、16位)
  4. HDR转SDR CubeLut配置,PQ转SDR12种、HLG转SDR4种
  5. HDR转SDR Shader配置,该Shader支持对PQ视频和HLG视频进行色度矫正、色调参考、色调映射、色域转换、Gamma压缩
  6. 色调映射已支持Android8的Tonemap、Android13的Tonemap、BT2446A、BT2446C、Hable
  7. 色域转换已支持BT2020转BT709Clip、Compress、adpative_l0_cusp
  8. 10个测试视频无缝切换

这篇文章是本系列的第8篇文章,主要讲HDR转SDR中最重要知识点色调映射。

什么是色调映射

HDR视频的高亮度内容在低亮度显示器上还能保留图像细节和颜色,这门技术就叫做色调映射(Tone mapping)。色调映射本质上就是模拟相机把HDR内容从高亮度映射到低亮度从而实现压缩动态范围,如下图所示调整亮度同时改变了图像对比度和动态范围,从而让图像效果更好。

image.png 色调映射分为三种全局、局部、时域,全局色调映射速度快在视频处理中比较常用,后文主要围绕这个展开。

色调映射解释算法优点缺点
全局直接映射每个像素的亮度曲线函数、直方图、自适应对数1. 实现简单运行速度快
2. 适合用在视频中
偏主观,不同视频效果不一定好
局部不同区域的亮度映射不一样快速双边滤波、Retinex1. 效果好
2. 适合用在图片中
1.复杂度高运行速度慢
2.容易出现光晕失真现象
时域根据连续帧得到亮度的时域相关性动作域的光流预测  1. 消除失真
2.防止视频闪烁
单独效果比局部色调映射弱,适合在局部后运行

全局色调映射

全局色调映射是调整暗部和亮部的曲线函数。Tonemap operator收集了常见的Tmo色调映射操作符(Tone mapping operator),如下图所示所有曲线有个普遍的特点就是S型,前半部分就是"toe"防止暗的部分太亮,后半部分就是"shoulder"把亮部映射到接近最大亮度,中间部分就是"mid tone"接近线性。 色调映射曲线本质上就是一个缩放函数起到调节亮度的作用,传递函数也起到了调节亮度的作用,它和色调曲线的区别是传递函数不改变范围,色调曲线改变了范围。

image.pngimage.png

缩放规则

直接对RGB的3个通道做色调映射会导致颜色失真,为了映射前后保持色调一致需要对RGB进行统一缩放,缩放步骤如下所示:

  1. 放大成绝对亮度(也可以是参考白值为1的相对亮度,为了表述方便用绝对亮度)
  2. 把RGB的亮度和参考白的亮度代入色调曲线相除得到色调映射后的亮度
  3. 色调映射后的亮度除以色调映射前的亮度得到缩放值
  4. RGB乘以缩放值就是色调映射的RGB
  5. 归一化RGB

RGB亮度有多种表示方式,缩放方法自然也有多种,根据BT2408的附录五有以下5个缩放方式,MaxRGB在代码实践使用更多一点。

方法公式优点缺点
MaxRGBrgbTom(Max(rgb))Tom(W)Max(rgb)rgb*\frac{Tom(Max(rgb))}{Tom(W)*Max(rgb)}不会产生超出目标色域的颜色牺牲亮度保留色度、可能会产生没有饱和度变化的伪影
RGBTom(rgb)Tom(W)\frac{Tom(rgb)}{Tom(W)}不会产生超出目标色域的颜色饱和度可能过低
YRGBrgbTom(Y)Tom(W)Yrgb*\frac{Tom(Y)}{Tom(W)*Y}保留色度产生超出目标色域的颜色
YCbcrfloat Y2 = Tom(YCbCr.x)/Tom(W)
YCbCr.yz *= min(YCbCr.x / Y2, Y2 / YCbCr.x)
YCbCr.x = Y2;
YCbcr To RGB
去饱和功能、包含YCbCr的色调产生超出目标色域的颜色
ICtCpfloat I2 = Tom(ICtCp.x)/Tom(W)
ICtCp.yz *= min(ICtCp.x / I2, I2 / ICtCp.x)
ICtCp.x = I2
ICtCp To RGB
感知色差空间、去饱和功能产生超出目标色域的颜色

注意:

  1. 为了方便公式中省略了放大成绝对亮度和归一化的步骤
  2. W表示参考白的颜色值
  3. 上述公式和BT2408的附录五略微有点不同,本质是一样的

曲线

色调曲线解释优点缺点
reinhardreinhard的论文“Photographic Tone Reproduction for Digital Images”中提到了该公式简单灰暗
hableJohn hable在神秘海域2中提出曲线拟合艺术家处理后的图片艺术家的角度调整效果两个多项式相除,计算量相对其他曲线慢一点
acesaces本身是美国电影艺术与科学学会为解决颜色空间转换问题而发明的,Krzysztof Narkowicz为了使用aces用曲线拟合简化了代码暗处亮处的细节保留较好,色彩鲜艳较aces原本的算法偏鲜艳
Android8Android8.0开始使用的色调映射Hermitian曲线插值Android8比Android13的色调映射亮一点
Android13Android13.0开始使用的色调映射PQOETF曲线拟合Android13比Android8的色调映射暗一点
BT2446ABT2446推荐的A方法用来转换HDR、SDR适合电影电视剧,使用YCbCr转换反复HDR和SDR转换相对于BT2446C有色差
BT2446CBT2446推荐的C方法用来转换HDR、SDR适合直播内容,使用Yxy转换,适应肤色肉眼感觉比BT2446A亮一点

reinhard

公式可视化

TMO(x)=x(1+xxw2)1+x=x1+x+(xxw)21+xTMO(x) =\frac{x \cdot (1+\frac{x}{x_w^{2}})}{1+x} = \frac{x}{1+x}+\frac{(\frac{x}{x_w})^2}{1+x}

注意:

xw{x_w}代表色调映射白色时X的值,也就是说x的范围从[0,1][0,1]变成了[0,Xw][0,X_w],除以1+x是为了让Tom(x)的范围变成[0,1][0,1],加上最右边(xxw)2(\frac{x}{x_w})^2是为了让曲线的后半部分不要太暗,个人感觉平方和Gamma2.0曲线的道理一样都是调节暗部和亮部

hable

公式可视化

Tom(x)=ax2+bcx+deax2+bx+dfefTom(x)=\frac{ax^{2}+b\cdot c\cdot x+d\cdot e}{ax^{2}+bx+d\cdot f}-\frac{e}{f}

注意:

  1. 注意 a=0.15,b=0.50,c=0.10,d=0.20,e=0.02f,f=0.30a = 0.15, b = 0.50, c = 0.10, d = 0.20, e = 0.02f, f = 0.30
  2. hable曲线本身是拟合曲线,参数控制曲线中的变化,可以用上述公式可视化改变参数试试就明白了 image.png
a=Shoulder强度 b=Linear强度 c=Linear角度 d=Toe强度 e=Toe角度分子 f=Toe角度分母ef=Toe角度 \begin{aligned} a &= Shoulder强度 \\  b &= Linear强度 \\  c &= Linear角度 \\  d &= Toe强度  \\ e &= Toe角度分子  \\ f &= Toe角度分母 \\ \frac{e}{f} &= Toe角度  \\ \end{aligned}

aces

公式可视化

image.png

该公式是为了方便使用ACES颜色转换体系中的效果采用曲线拟合而来的,据说虚幻4用的就是这个

Tom(x)=ax2+bxcx2+dx+eTom(x) =\frac{ a \cdot x^2+b\cdot x} {c\cdot x^2+d \cdot x+e}

注意:

  1. 注意 a=2.51,b=0.03,c=2.43,d=0.59,e=0.14a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14
  2. 如果发现上述公式的颜色过于饱和,还可以使用拟合度更接近ACES转换的曲线ACESFitted

Android8

代码地址

image.png

个人理解和Android13其实思路是一样,按几个点插值形成的曲线进行调整
(x0,y0) x0=10,y0=17,其实就是暗部线性插值
(x1,y1) x1=y1等于屏幕最大亮度的0.75,也是线性插值
(x2,y2) x2在x1和输入最大亮度的中间,y2在y1和屏幕最大亮度的中间,然后用Hermitian曲线进行插值,Hermitian在BT2309中也有使用到

Android13

代码地址

image.png

个人理解
HLG是直接按屏幕最大亮度进行缩放
PQ是把输入的值按照给定几个点插值形成的曲线进行调整,插值按PQOETF进行拟合
(x1,y1) x1=y1等于屏幕最大亮度的0.65
(x2,y2) x2表示x1和x3之间的4.0/17.0 y2表示屏幕亮度最大亮度的0.9
(x3,y3) x3等于调整前的最大亮度,y3等于调整后的最大亮度即屏幕最大亮度

BT2446A

image.png

个人理解,代码地址

  1. RGB转换YCBCR
  2. Y亮度转换为感知线性空间
  3. 在感知域中对Y应用拐点函数
  4. 转换回伽玛域
  5. YCBCR转换回RGB

BT2446C

公式可视化

image.png

把RGB转成xyY,然后把xyY中的Y用公式进行调整,最后再转换回来,代码地址

公式个人理解是这样的,其实就是两个点进行插值

  1. (ip,YHDRip)表示SDR拐点,拐点前面的值按k1斜率插值
    ip表示SDR部分的最大亮度,由BT2408中写的HDR和SDR肤色的关系决定的也就是SDR的80%换算成100cd/m2也就是58.535cd/m2
    YHDRip是由斜率k1和ip的值算出来的,YHDRip = ip/k1=58.535/k1
  2. (HDR参考白,YSDRwp)表示SDR和HDR的高光拐点,按ln进行插值

系列文章