背景
在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 | 不会产生超出目标色域的颜色 | 牺牲亮度保留色度、可能会产生没有饱和度变化的伪影 | |
RGB | 不会产生超出目标色域的颜色 | 饱和度可能过低 | |
YRGB | 保留色度 | 产生超出目标色域的颜色 | |
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
注意:
代表色调映射白色时X的值,也就是说x的范围从变成了,除以1+x是为了让Tom(x)的范围变成,加上最右边是为了让曲线的后半部分不要太暗,个人感觉平方和Gamma2.0曲线的道理一样都是调节暗部和亮部
hable
注意:
- 注意
- hable曲线本身是拟合曲线,参数控制曲线中的变化,可以用上述公式可视化改变参数试试就明白了
aces
该公式是为了方便使用ACES颜色转换体系中的效果采用曲线拟合而来的,据说虚幻4用的就是这个
注意:
- 注意
- 如果发现上述公式的颜色过于饱和,还可以使用拟合度更接近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进行插值