做逆色调映射这几年,我最大的一个感受是:很多人把SDR转HDR想得太简单了,觉得无非是拉高亮度和对比度,稍微调一下曲线,出来的画面“鲜艳”一点就算成功。真正上手之后才发现,这活儿说白了是在跟丢失的信息作斗争——HDR视频技术里最难啃的硬骨头之一,就是怎么把已经压缩到普通动态范围(SDR)里的画面,重新“撑回”高动态范围(HDR),而且还得撑得自然、撑得有细节。这套技术有个专门的名字:逆色调映射(Inverse Tone Mapping)。
这篇文章不讲虚的,我会把逆色调映射到底是什么、业界有哪些主流路线、深度学习方案怎么建模、以及我实际处理SDR转HDR时踩过的坑和参数取舍,一次说透。
1. 逆色调映射的本质:SDR转HDR不是调亮,而是补全丢失的动态范围
1.1 从信号链路理解SDR与HDR的本质差异
要理解逆色调映射,得先搞清楚SDR和HDR在信号层面差在哪。
SDR内容按BT.709/BT.601标准编码,采用Gamma曲线,也就是电光转换函数大致是一条2.4次幂曲线。这套标准诞生于CRT显示器时代,亮度范围设计为大约0.1到100nit,极限也就到100nit出头。而HDR内容按BT.2100标准,主流使用PQ曲线(Perceptual Quantizer,对应SMPTE ST 2084),峰值亮度可以到1000nit、4000nit甚至10000nit,而且它是按人眼视觉感知特性量化亮度的,在10bit或12bit精度下,每一级亮度步进都小于人眼刚好能分辨的差(JND)。
常规的色调映射(Tone Mapping)是把HDR内容压缩到SDR显示设备能表现的范围,这是向下兼容。逆色调映射恰恰反过来,把SDR信号扩展成HDR信号,是向上“无中生有”。
问题就出在“无中生有”这四个字上。SDR内容的亮度上限就那么高,一个本该在HDR下爆发出上千nit高光的区域,在SDR编码里早就被硬裁剪成纯白255,或者被压制得非常平缓,里面的细节是实实在在丢失了。像素值255就是255,你把它乘以任何系数,都得不到能在HDR屏幕上表现出自然层次的高光渐变。逆色调映射本质上是个病态问题,丢失的信息不会自动回来,一切要靠算法用周围的信息去“猜”、去“重建”。
我经常用一个类比来解释这个过程:色调映射像是把阳光房里的所有家具搬进一间小黑屋,东西太多搬不下,就得丢弃一些;逆色调映射则是只给一张小黑屋里家具摆放的照片,让你推算出原来阳光房的落地窗、吊灯和窗外风景长什么样。所以这不是简单的数值运算,而是一个重建与推理过程。
1.2 为什么不能直接做数值放大
很多第一次接触SDR转HDR的人,第一个想法都是:把像素亮度乘个系数不就行了?比如把SDR的峰值亮度100nit映射到HDR的1000nit,按10倍放大。
实际操作一下就会发现三个问题。
第一,直接放大后暗部也跟着亮了,黑位抬升,整个画面发灰。SDR里编码为16/255的“黑”区域,放大后在HDR下可能变成了几十nit的“灰”,观感上完全没了对比度。第二,高光区域没有任何恢复。SDR里已经裁剪成纯白的部分,放大后还是纯白,没有过渡、没有层次,在HDR屏幕上反而更难看,因为HDR屏幕的动态范围让你一眼就能看出这坨白不正常。第三,很多人在Gamma编码域里直接做运算,这是概念性错误。SDR像素值跟物理亮度不是线性关系,sRGB编码本身带一个非线性转换,正确做法是先做去Gamma/去ELG,把数据线性化,在线性光照域里处理,最后再编码回PQ传输函数。直接在非线性的编码值上做乘除,相当于在已经扭曲的尺度上操作,出来的色彩和亮度关系必然是错的。
所以逆色调映射的完整管线通常长这样:解码SDR到线性RGB —— 在光照域设计扩展曲线或交给网络推理 —— 色域转换 —— 按PQ编码写HDR。每一步都有讲究,后面的章节我会逐段拆。
2. 三条技术路线:全局扩展、分层重建与深度学习的取舍
逆色调映射发展了十几年,主流方案大致能分成三类,各有各的适用场景,也各有各的硬伤。
2.1 全局扩展函数:最直接也最容易发灰的路线
全局扩展是整个领域最早期的做法,代表工作是Banterle等人在2007年前后提出的方案。核心思路很简单:构造一个从SDR亮度到HDR亮度的全局映射函数,对整帧所有像素做同样的变换。
比较常见的做法是先计算一个扩展因子,再结合曝光补偿。比如给定输入亮度L,输出亮度可以用类似L' = a · L^k的形式,或者用sigmoid函数把原本被压缩的亮部重新拉开。这种方法的优点非常突出:计算量小,可以做到实时,适合在播放器、视频转换棒这类低算力设备上跑,而且时间上很稳定,不会出现逐帧跳动。
但缺点也同样致命。它没有空间自适应性,整幅画面统一处理,亮的地方整体变亮,暗部也被同步提亮,高光区域该没层次还是没层次,画面看起来灰蒙蒙的,在HDR屏幕上反而暴露出更多缺点。我用全局方法做过几次试验,结论很明确:作为学术铺垫有价值,产线上基本不够用。
2.2 分层与曝光堆叠:局部细节的恢复尝试
既然全局方法不行,研究者开始考虑“局部处理”。一个自然的思路是:把图像拆成基础层和细节层,基础层承载大尺度的亮度变化,细节层保存边缘和小尺度纹理。扩展只作用在基础层上,细节层原地保留,这样处理完的高光区域能保住纹理,不像全局方法那样糊成一团。
还有一类跟“局部”沾边但容易混淆的方法:多曝光HDR合成,也就是曝光堆叠。拿同一场景的多张不同曝光时间的SDR照片,提取各自的暗部细节和高光细节,加权合并成一张HDR图。严格说这属于HDR重建,不是单图逆色调映射,因为它依赖多帧信息。但在实际项目里,“SDR转HDR”的工具箱经常把这些方法混在一起用,比如对着同一个静态场景拍三张不同曝光的照片,然后合成HDR,这套流程成熟而且稳定。只不过一进入视频领域,多层合成的方法就吃不开,因为视频要求帧间一致,不同帧的融合权重一旦变化就会闪烁。
2.3 基于学习的方法:从数据中学会“高光该长什么样”
2017年Eilertsen等人提出HDRCNN之后,逆色调映射开始进入深度学习时代。核心思路很简单粗暴:让卷积神经网络看大量由HDR母版生成的SDR版本,学会从SDR画面里预测出丢失的HDR高光渐变。
这类方法的优势非常明显,它不再依赖人工设计的先验规则,而是从数据里学出“什么样的边缘、什么样的纹理、什么样的几何结构,大概率对应一个多少亮的高光”。实测下来,深度学习方案在主观观感、高光自然度、细节恢复程度上都大幅领先前两代方法,尤其在处理像太阳、镜面高光、灯带这类“干净高光”时,推断出来的衰减曲线非常接近真实光照。
代价也很大:离线训练成本高,推理阶段在GPU上跑还勉强能用,CPU上实时基本别想;另外逐帧独立推理很容易产生时间闪烁。下面这张表是我在项目选型时经常拿出来的对比维度:
| 维度 | 全局扩展 | 分层/局部方法 | 深度学习方法 |
|---|---|---|---|
| 计算开销 | 低 | 中 | 高 |
| 高光细节恢复 | 弱 | 中 | 强 |
| 时间稳定性 | 好 | 中等 | 弱(需额外约束) |
| 适用场景 | 播放器实时转换、低端设备 | 静态图处理、离线修复 | 离线批量修复、高质量重制 |
3. 膨胀卷积与感知损失:深度学习逆色调映射建模细节
既然深度学习已经是目前效果上限最高的方向,我把它的建模细节展开讲讲。很多开源代码跑出来效果一般,问题往往不是模型本身,而是训练数据和损失函数没有对齐。
3.1 成对数据怎么造:渲染器加色调映射的模拟链路
深度学习训练需要成对的数据:输入是SDR画面,目标是HDR真值。问题在于,真实世界里你几乎不可能拿到严格配对的SDR和HDR版本——对着同一个场景同时拍SDR和HDR,传感器、镜头、曝光差异都会造成像素级不对齐。
业界最常用的做法是“渲染模拟链路”:用Blender Cycles或者Unreal Engine这类支持物理正确光照的渲染器,输出高动态范围的HDR画面当作真值,然后对这些HDR画面做一次色调映射,生成对应的SDR版本,再把这个SDR版本当作训练输入。这个模拟过程把“HDR真值是怎么经过色调映射变成SDR的”完全可控,而且色调映射的参数可以变着花样来,模型便能在多样性的SDR形态里学到鲁棒的特征。
我实际跑训练的时候还会额外加一步合成扰动:给SDR输入补一些轻微的量化噪声、色度采样偏移,模拟真实老片经压缩、解码后的脏数据效果。这一步对模型落地到实际素材时的泛化能力提升非常明显。
3.2 网络结构与感受野:为什么膨胀卷积管用
逆色调映射网络通常采用全卷积结构,输入一张SDR图,输出亮度扩展后的HDR图。HDRCNN使用的就是类似U-Net的编码器-解码器结构,中间用膨胀卷积增加感受野。
感受野在这里是个非常关键的问题。一个被裁剪成纯白的高光区,它的自然衰减可能横跨几百个像素,比如太阳周围的辉光、灯箱周围的泛光。如果网络只看得到局部几像素的上下文,它根本没法推断这个高光该有多亮、衰减到多宽。膨胀卷积的作用就是不降分辨率的情况下指数级扩大有效感受野,让网络能看到更大范围的亮度变化趋势。我自己的经验是,最后的特征层感受野至少得覆盖到输入尺寸的1/4以上,否则结果会显得“小气”,高光周围缺少舒展的过渡带。
编码器-解码器结构还有个隐藏优点:skip connection能把浅层的高频边缘信息直接传给重建层,这样高光边缘不会糊成一片,亮部和暗部的边界还能保持锋利。
3.3 损失函数要用HDR域思维:别在sRGB编码域里算L2
这是我反复踩过的一个坑。早期训练时为了省事,直接让网络预测sRGB编码域的像素值,然后用L2损失约束。训练跑完,指标还挺好看,但生成的画面怪怪的——高光区域总有一种“说不上来的糊”,暗部又容易出现色斑。
原因在于,sRGB编码域跟人眼感知的亮度差异不是线性的,编码域里的L2距离并不能代表视觉差异。正确做法是在线性HDR域算损失。通常先把网络输出的线性HDR值经过一次色调映射回到SDR域,再和参考SDR算空间域的感知损失;同时在线性HDR域也直接算L1/L2损失。HDR域损失负责约束整体的数值合理性,感知损失负责约束肉眼观感。这样一来,网络不再为了取悦编码域数值而牺牲视觉质量,高光的自然度和暗部的纯净度都上了一个台阶。
4. 谷歌浏览器HDR截图过曝:一次“伪逆映射”的真实事故
开头提到的“谷歌浏览器HDR截图过曝”,你看搜索热词都能发现它是高频困扰。这个现象表面上看跟逆色调映射没关系,但实际排查一圈就会发现,它本质上是动态范围转换链路出了问题,正好是理解逆色调映射方向感的好素材。
4.1 现象:屏幕正常,截图灰白死白
典型场景是这样的:系统是Windows,显示器支持HDR,系统设置里已经开启了使用HDR。浏览器打开一段HDR视频,屏幕上播放的观感完全正常,高光有层次、色彩也饱满。然后你随手用微信截图或者QQ截图想截一帧分享,截出来的图却整体发灰白,高光区域一片死白,像被暴力过曝了一样,跟屏幕上看到的完全两个画面。
更迷惑的是,同一时刻用Xbox Game Bar截图,或者按Win+PrtScr系统截图,得到的图有时是正常的,有时也过曝,取决于具体环境。很多人第一反应是显示器坏了,或者显卡驱动有问题,结果换台设备还是这样。
4.2 排查链路:从显示系统到截图工具的逐层定位
遇到这类问题,我的排查顺序是这样的:
- 先确认是“截图过曝”而不是“显示过曝”。让提问的人拍屏幕照片,如果拍屏幕的照片色彩正常、只有截图文件过曝,问题就锁定在截取/转换链路,而不是显示器。
- 确认系统HDR开关:Win + Alt + B能否切换HDR模式,打开了没有。如果系统HDR没开,浏览器不会进入HDR播放链路。
- 确认浏览器硬件加速已开启。Chrome的地址栏进chrome://settings/system,查看“使用硬件加速模式(如果可用)”是否打开。HDR播放依赖GPU合成,关掉硬件加速会让浏览器退回SDR处理。
- 在视频画面上右键打开统计信息,确认正在播放的是HDR内容。YouTube的话看“Stats for nerds”,如果编码显示VP9.2/AV1.10bit + PQ,基本就能锁定是HDR流。
- 对比不同截图工具的差异。微信/QQ这类第三方截图工具走的是老的GDI/DirectX捕获路径,拿到的是系统合成器里还没做显示适配的HDR帧缓冲;而Xbox Game Bar在HDR模式下截出的图是经过系统HDR处理的。
走到这一步,原因已经很清楚了:第三方截图工具抓取到的是PQ编码的高动态范围信号,但它在内部用sRGB/Gamma曲线去解析这个PQ信号,等于把PQ的数值当成了普通SDR亮度来输出。PQ曲线的数值分布是为感知均匀编码设计的,直接拿去按sRGB显示,高光部分当然会暴力溢出,这就是我们看到的灰白一片。
4.3 和逆色调映射的关系:错误的动态范围扩展随时在发生
这种过曝本质上是一种“错误的动态范围扩展”——把一个本应显示在HDR链路里的信号,生硬地拉回到SDR链路里,而且没做任何转换补偿。正常播放时,系统或者浏览器内部的显示映射模块会把HDR内容转换到屏幕实际支持的色彩空间和亮度范围;截图工具绕过了这套转换,等于拿错解码表去解另一套编码体系。
这一点恰恰说明了逆色调映射的核心难点:动态范围扩展不是简单的数值放大,任何扩展都要有合理的映射逻辑、要有感知依据,否则就会像截图过曝这样,把好好的画面搞废。反过来,如果你用播放器或者电视自带的“SDR转HDR”功能看普通SDR视频,遇到那种高光一片死白、画面发灰的情况,原因也大同小异——它内部的逆色调映射模块没做好,把高光区域扩展过头了。所以下次再看到“SDR转HDR效果发白”,你基本可以断定是逆色调映射的扩展强度失控,不是显示设备的问题。
5. 实战调参:从素材摸排到目标亮度设定的完整流程
前面讲了原理,接下来讲讲我自己处理SDR转HDR项目时的完整流程和参数取舍。
5.1 先分辨素材:原生SDR还是降级SDR决定处理思路
拿到一个待处理的SDR视频,第一件事不是直接丢进模型,而是先判断素材类型。我把它分成两类:
原生SDR素材:这是最大的坑。老电影、老剧集、大部分电视节目,从拍摄到后期都是SDR流程,画面里本来就不存在HDR级别的动态范围信息,高光区域更是早就被压平或剪裁掉了。处理这类素材,逆色调映射只能靠先验“编造”高光,目标是编得自然、有层次,而不是去还原某个真实存在的HDR场景。
降级SDR素材:有些视频是从HDR母版经过色调映射转成SDR播出的。这类素材的高光区域往往保留着微弱但可辨识的梯度变化,没有完全剪平,专业术语叫“近裁剪区域信息”。处理这类素材,逆色调映射更像是“还原”,网络有机会重建出接近母版的动态范围。分辨方法很简单:看高光区域的渐变。用专业工具或者直接放大看,如果太阳、灯泡、高光反射周围有细腻的亮度过渡,大概率是降级素材;如果是一整块死白没有过渡,基本是原生SDR。
处理策略完全不同:原生SDR要克制,扩展强度低一些,尽量保住整体观感;降级SDR可以激进一点,目标是尽可能把丢失的层次拉回来。
5.2 目标峰值亮度、扩展强度与色域映射的设定
参数设定上,最核心的是三个:目标峰值亮度、扩展强度、色域映射策略。
目标峰值亮度应该根据最终播放设备来定,而不是盲目奔着4000nit去。如果是制作给1000nit显示器看的版本,目标峰值就设在800到1000nit附近;如果面向手机屏幕这类峰值亮度有限的设备,可以压到600nit左右。设定太高,画面为了迁就峰值会把中灰拉低,整体偏暗,暗部细节还容易丢。
扩展强度一般取0.8到1.2之间。0.8意味着扩展保守,适合原生SDR老片;1.0是正常档;超过1.0适合低照度素材,比如夜景演唱会,原本暗部密度很高,加大扩展能把氛围提起来。超过1.2我基本不用,因为暗部噪声会被放大到肉眼可见的程度,画质得不偿失。
色域映射也不容忽视。SDR素材是BT.709色域,HDR目标是BT.2020。主流做法是用标准矩阵把RGB转换过去,同时适当提升饱和度补偿。需要注意的是,BT.2020色域远大于BT.709,直接转换容易导致肤色偏红、绿色过艳,所以我一般会把肤色区域单独保护起来,或者采用一种带亮度适应的色域映射算法,让高光区域的色彩不那么“烧”。
5.3 一版可直接上手的SDR转HDR处理流程
如果你手头有N卡,且想快速跑通一条SDR转HDR流程,下面这条管线可以作为起点:
- 用FFmpeg或MediaInfo检查输入的色彩空间。确认像素格式、色深、色彩原色,了解素材的编码状态。
- 解码到16bit float线性RGB。这是所有正确颜色处理的前提,绝不能在Gamma编码域做扩展。
- 应用逆色调映射模型。如果是离线高质量处理,我会优先用训练好的深度学习模型推理亮度扩展层;如果是直播推流这种实时需求,才退回全局或分层算子。
- 做色域转换:BT.709到BT.2020。注意转换矩阵要带颜色适应,必要时手动微调饱和度。
- 编码输出,用x265写HEVC 10bit,带上HDR元数据。我常用的参数是这样:
x265 --input-depth 16 --input-csp i444 --colorprim bt2020 --transfer smpte2084 --colormatrix bt2020nc --master-display G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,50) --max-cll 1000,400 -o output.hevc input.y4m这里的关键参数是把传输函数设为smpte2084(PQ),色彩原色设为bt2020,并写入Mastering Display和MaxCLL/MaxFALL元数据。这些信息对播放器正确显示HDR至关重要,很多自己压出来的HDR视频在播放器里发灰,就是因为少了这些元数据。
6. 翻车重灾区:光晕、断阶、肤色偏移与时间闪烁
最后这部分,是我觉得比原理更值钱的部分——逆色调映射实际处理时最容易翻车的几个地方,以及怎么修。
6.1 光晕和断阶:边缘伪影与量化台阶的来源
光晕大概是最常见的伪影。表现是强明暗交界处出现一圈发亮的边,像是物体最外圈被谁用画笔描了一遍光。原因分两类:一类是局部扩展时边缘像素被错误拉高,另一类是深度学习模型在高对比边缘处产生了过冲。修复思路通常是做边缘检测,把扩展系数在高梯度区域降下来,或者用导向滤波来约束扩展值的空间平滑性。
断阶(banding)是另一个高频问题。SDR素材本身是8bit,扩展到HDR后如果扩展函数的斜率在暗部区域过大,原本连续的亮度台阶就会被放大成肉眼可见的横条纹。我一般会做两步:一是在扩展曲线设计时对暗部区域压低斜率,二是对输出做轻微的抖动或带噪。很多播放器也有去带噪选项,但那是给SDR信号用的,在HDR上还是从源头解决比较靠谱。
6.2 时间闪烁与噪声放大:逐帧模型的软肋
时间闪烁是深度学习方案最被人诟病的一点。网络对每一帧独立推理,同一像素在不同帧里的亮度可能忽高忽低,这种闪烁尤其容易出现在大面积高光区域,人类视觉对这类低频亮度波动非常敏感,比空间模糊还难受。解决办法大致分三类:训练时加入时序约束,让网络看到相邻帧;推理后用光流做时域滤波;或者对高光区域做一个时间上的滑动窗口平均,再混合回原始帧。我实际用的最多的是光流约束加后滤波结合,效果好,但代价是处理速度变慢。
噪声放大也容易出问题。SDR素材在暗部本来就有一定的传感器噪声或压缩噪声,扩展之后噪声能量被连着放大,原本不太明显的噪点会变成密密麻麻的“彩虹点”。所以很多逆色调映射流程里都有一个前置去噪步骤,以及在扩展层面对暗部加以更温和的增益。
6.3 质量核验与综合修复建议
处理完了不能只看一帧,我通常会从三个维度检查。
主观上,我会在一台峰值亮度符合目标的HDR显示器上逐场景检查,重点看三样东西:高光区域有没有层次和过渡,肤色有没有偏移,暗部有没有发灰。客观上,如果是实验项目,可以用HDR-VDP2这类感知质量指标做数值评估,但说实话这个指标在预测主观满意度上还没那么可靠,我还是更相信逐场景人工复核。最后还有一个经常被忽视的点:把处理出来的HDR版本重新做一次色调映射回SDR,看看画面是否还保有原来的对比度关系。如果回映射后观感跟原始SDR差很多,说明扩展时把画面原有的亮度结构破坏了,需要回头调参数。
做逆色调映射,我这些年下来最深的一条体会是:所有参数都要服从画面的“亮度叙事”。一个画面为什么会亮、亮到什么程度、周围怎么衰减,这些在真实光照里是有逻辑的。技术哪个强、模型哪个新,最后都要回归到一个问题——一个完全没有接触过原始HDR场景的观众,会不会觉得这个画面是自然可信的。处理老片修复项目时,我甚至会把原作摄影风格也纳入参数考虑,逆色调映射不是为了把每帧都弄成高亮炫技,而是让对比度和动态范围服务于内容本身。操作上我的建议是:小项目先跑全局或分层方案快速出效果,画面不对劲再上深度学习;大项目则反过来,直接上模型,但参数从保守档起步,优先保证不翻车,再逐步逼近理想效果。