背景
在3月份开始更新HDR转SDR实践之旅,当时代码还没写只是建了库,没想到更新了几篇文章这么受欢迎,代码还没上传已经有几十个赞了,还有小伙伴提了个issue每个月催我更新代码,这个领域资料太少了,边学边写,现在总算完成了,不辜负大家的等待,如果你觉得有所收获,来给HDR转SDR开源代码点个赞吧,你的鼓励是我前进最大的动力。
现有功能实现如下,供大家一起学习一起上进
- 输出模式(直接输出到Surface、经过OpenGL中转)
- 视图模式(无缝切换SurfaceView和TextureView)
- 多种纹理来源配置(Auto、YUV420Buffer、外部纹理OES、Y2Y)、纹理位深配置(8位、10位、16位)
- HDR转SDR CubeLut配置,PQ转SDR12种、HLG转SDR4种
- HDR转SDR Shader配置,该Shader支持对PQ视频和HLG视频进行色度矫正、色调参考、色调映射、色域转换、Gamma压缩
- 色调映射已支持Android8的Tonemap、Android13的Tonemap、BT2446A、BT2446C、Hable
- 色域转换已支持BT2020转BT709Clip、Compress、adpative_l0_cusp
- 10个测试视频无缝切换
这篇文章是本系列的第8篇文章,主要讲HDR转SDR中最重要知识点色调映射。
什么是色调映射
HDR视频的高亮度内容在低亮度显示器上还能保留图像细节和颜色,这门技术就叫做色调映射(Tone mapping)。色调映射本质上就是模拟相机把HDR内容从高亮度映射到低亮度从而实现压缩动态范围,如下图所示调整亮度同时改变了图像对比度和动态范围,从而让图像效果更好。
色调映射分为三种全局、局部、时域,全局色调映射速度快在视频处理中比较常用,后文主要围绕这个展开。
色调映射 | 解释 | 算法 | 优点 | 缺点 |
---|---|---|---|---|
全局 | 直接映射每个像素的亮度 | 曲线函数、直方图、自适应对数 | 1. 实现简单运行速度快 2. 适合用在视频中 | 偏主观,不同视频效果不一定好 |
局部 | 不同区域的亮度映射不一样 | 快速双边滤波、Retinex | 1. 效果好 2. 适合用在图片中 | 1.复杂度高运行速度慢 2.容易出现光晕失真现象 |
时域 | 根据连续帧得到亮度的时域相关性 | 动作域的光流预测 | 1. 消除失真 2.防止视频闪烁 | 单独效果比局部色调映射弱,适合在局部后运行 |
全局色调映射
全局色调映射是调整暗部和亮部的曲线函数。Tonemap operator收集了常见的Tmo色调映射操作符(Tone mapping operator),如下图所示所有曲线有个普遍的特点就是S型,前半部分就是"toe"防止暗的部分太亮,后半部分就是"shoulder"把亮部映射到接近最大亮度,中间部分就是"mid tone"接近线性。 色调映射曲线本质上就是一个缩放函数起到调节亮度的作用,传递函数也起到了调节亮度的作用,它和色调曲线的区别是传递函数不改变范围,色调曲线改变了范围。
缩放规则
直接对RGB的3个通道做色调映射会导致颜色失真,为了映射前后保持色调一致需要对RGB进行统一缩放,缩放步骤如下所示:
- 放大成绝对亮度(也可以是参考白值为1的相对亮度,为了表述方便用绝对亮度)
- 把RGB的亮度和参考白的亮度代入色调曲线相除得到色调映射后的亮度
- 色调映射后的亮度除以色调映射前的亮度得到缩放值
- RGB乘以缩放值就是色调映射的RGB
- 归一化RGB
RGB亮度有多种表示方式,缩放方法自然也有多种,根据BT2408的附录五有以下5个缩放方式,MaxRGB在代码实践使用更多一点。
方法 | 公式 | 优点 | 缺点 |
---|---|---|---|
MaxRGB | <math xmlns="http://www.w3.org/1998/Math/MathML"> r g b ∗ T o m ( M a x ( r g b ) ) T o m ( W ) ∗ M a x ( r g b ) rgb*\frac{Tom(Max(rgb))}{Tom(W)*Max(rgb)} </math>rgb∗Tom(W)∗Max(rgb)Tom(Max(rgb)) | 不会产生超出目标色域的颜色 | 牺牲亮度保留色度、可能会产生没有饱和度变化的伪影 |
RGB | <math xmlns="http://www.w3.org/1998/Math/MathML"> T o m ( r g b ) T o m ( W ) \frac{Tom(rgb)}{Tom(W)} </math>Tom(W)Tom(rgb) | 不会产生超出目标色域的颜色 | 饱和度可能过低 |
YRGB | <math xmlns="http://www.w3.org/1998/Math/MathML"> r g b ∗ T o m ( Y ) T o m ( W ) ∗ Y rgb*\frac{Tom(Y)}{Tom(W)*Y} </math>rgb∗Tom(W)∗YTom(Y) | 保留色度 | 产生超出目标色域的颜色 |
YCbcr | float Y2 = Tom(YCbCr.x)/Tom(W) YCbCr.yz *= min(YCbCr.x / Y2, Y2 / YCbCr.x) YCbCr.x = Y2; YCbcr To RGB | 去饱和功能、包含YCbCr的色调 | 产生超出目标色域的颜色 |
ICtCp | float I2 = Tom(ICtCp.x)/Tom(W) ICtCp.yz *= min(ICtCp.x / I2, I2 / ICtCp.x) ICtCp.x = I2 ICtCp To RGB | 感知色差空间、去饱和功能 | 产生超出目标色域的颜色 |
注意:
- 为了方便公式中省略了放大成绝对亮度和归一化的步骤
- W表示参考白的颜色值
- 上述公式和BT2408的附录五略微有点不同,本质是一样的
曲线
色调曲线 | 解释 | 优点 | 缺点 |
---|---|---|---|
reinhard | reinhard的论文"Photographic Tone Reproduction for Digital Images"中提到了该公式 | 简单 | 灰暗 |
hable | John hable在神秘海域2中提出曲线拟合艺术家处理后的图片 | 艺术家的角度调整效果 | 两个多项式相除,计算量相对其他曲线慢一点 |
aces | aces本身是美国电影艺术与科学学会为解决颜色空间转换问题而发明的,Krzysztof Narkowicz为了使用aces用曲线拟合简化了代码 | 暗处亮处的细节保留较好,色彩鲜艳 | 较aces原本的算法偏鲜艳 |
Android8 | Android8.0开始使用的色调映射 | Hermitian曲线插值 | Android8比Android13的色调映射亮一点 |
Android13 | Android13.0开始使用的色调映射 | PQOETF曲线拟合 | Android13比Android8的色调映射暗一点 |
BT2446A | BT2446推荐的A方法用来转换HDR、SDR | 适合电影电视剧,使用YCbCr转换 | 反复HDR和SDR转换相对于BT2446C有色差 |
BT2446C | BT2446推荐的C方法用来转换HDR、SDR | 适合直播内容,使用Yxy转换,适应肤色 | 肉眼感觉比BT2446A亮一点 |
reinhard
公式可视化
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T M O ( x ) = x ⋅ ( 1 + x x w 2 ) 1 + x = x 1 + x + ( x x w ) 2 1 + x TMO(x) =\frac{x \cdot (1+\frac{x}{x_w^{2}})}{1+x} = \frac{x}{1+x}+\frac{(\frac{x}{x_w})^2}{1+x} </math>TMO(x)=1+xx⋅(1+xw2x)=1+xx+1+x(xwx)2
注意:
<math xmlns="http://www.w3.org/1998/Math/MathML"> x w {x_w} </math>xw代表色调映射白色时X的值,也就是说x的范围从 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 0 , 1 ] [0,1] </math>[0,1]变成了 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 0 , X w ] [0,X_w] </math>[0,Xw],除以1+x是为了让Tom(x)的范围变成 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 0 , 1 ] [0,1] </math>[0,1],加上最右边 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x x w ) 2 (\frac{x}{x_w})^2 </math>(xwx)2是为了让曲线的后半部分不要太暗,个人感觉平方和Gamma2.0曲线的道理一样都是调节暗部和亮部
hable
公式可视化
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T o m ( x ) = a x 2 + b ⋅ c ⋅ x + d ⋅ e a x 2 + b x + d ⋅ f − e f Tom(x)=\frac{ax^{2}+b\cdot c\cdot x+d\cdot e}{ax^{2}+bx+d\cdot f}-\frac{e}{f} </math>Tom(x)=ax2+bx+d⋅fax2+b⋅c⋅x+d⋅e−fe
注意:
- 注意 <math xmlns="http://www.w3.org/1998/Math/MathML"> a = 0.15 , b = 0.50 , c = 0.10 , d = 0.20 , e = 0.0 f , f = 0.30 a = 0.15, b = 0.50, c = 0.10, d = 0.20, e = 0.0f, f = 0.30 </math>a=0.15,b=0.50,c=0.10,d=0.20,e=0.0f,f=0.30
- hable曲线本身是拟合曲线,参数控制曲线中的变化,可以用上述公式可视化改变参数试试就明白了
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = S h o u l d e r 强度 b = L i n e a r 强度 c = L i n e a r 角度 d = T o e 强度 e = T o e 角度分子 f = T o e 角度分母 e f = T o e 角度 \begin{aligned} a &= Shoulder强度 \\ b &= Linear强度 \\ c &= Linear角度 \\ d &= Toe强度 \\ e &= Toe角度分子 \\ f &= Toe角度分母 \\ \frac{e}{f} &= Toe角度 \\ \end{aligned} </math>a b c deffe=Shoulder强度=Linear强度=Linear角度=Toe强度 =Toe角度分子 =Toe角度分母=Toe角度
aces
该公式是为了方便使用ACES颜色转换体系中的效果采用曲线拟合而来的,据说虚幻4用的就是这个
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> T o m ( x ) = a ⋅ x 2 + b ⋅ x c ⋅ x 2 + d ⋅ x + e Tom(x) =\frac{ a \cdot x^2+b\cdot x} {c\cdot x^2+d \cdot x+e} </math>Tom(x)=c⋅x2+d⋅x+ea⋅x2+b⋅x
注意:
- 注意 <math xmlns="http://www.w3.org/1998/Math/MathML"> a = 2.51 , b = 0.03 , c = 2.43 , d = 0.59 , e = 0.14 a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14 </math>a=2.51,b=0.03,c=2.43,d=0.59,e=0.14
- 如果发现上述公式的颜色过于饱和,还可以使用拟合度更接近ACES转换的曲线ACESFitted
Android8
个人理解和Android13其实思路是一样,按几个点插值形成的曲线进行调整
(x0,y0) x0=10,y0=17,其实就是暗部线性插值
(x1,y1) x1=y1等于屏幕最大亮度的0.75,也是线性插值
(x2,y2) x2在x1和输入最大亮度的中间,y2在y1和屏幕最大亮度的中间,然后用Hermitian曲线进行插值,Hermitian在BT2309中也有使用到
Android13
个人理解
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
个人理解,代码地址
- RGB转换YCBCR
- Y亮度转换为感知线性空间
- 在感知域中对Y应用拐点函数
- 转换回伽玛域
- YCBCR转换回RGB
BT2446C
把RGB转成xyY,然后把xyY中的Y用公式进行调整,最后再转换回来,代码地址
公式个人理解是这样的,其实就是两个点进行插值
- (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 - (HDR参考白,YSDRwp)表示SDR和HDR的高光拐点,按ln进行插值
系列文章
- HDR转SDR实践之旅(一)流程总结
- HDR转SDR实践之旅(二)解码10位YUV纹理
- HDR转SDR实践之旅(三)YUV420转YUV公式
- HDR转SDR实践之旅(四)YUV转RGB矩阵推导
- HDR转SDR实践之旅(五)色域转换BT2020转BT709
- HDR转SDR实践之旅(六)传递函数与色差矫正
- HDR转SDR实践之旅(七)Gamma、HLG、PQ公式详解
- HDR转SDR实践之旅(八)色调映射
- HDR转SDR实践之旅(九)HDR开发资源汇总
- HDR转SDR实践之旅(十)SDR转HDR逆色调映射探索