尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HDRnet深度解析:双边滤波与仿射变换如何实现实时图像增强

HDRnet深度解析:双边滤波与仿射变换如何实现实时图像增强 HDRnet这个名字圈里人应该不陌生。2017年Google发的那篇《Deep Bilateral Learning for Real-Time Image Enhancement》用一个看似“老旧”的双边滤波和一个听起来更“数学课”的仿射变换愣是把实时图像增强这件事玩出了花。我当时看到标题的第一反应是双边滤波不是用来磨皮保边的吗仿射变换不是图像几何操作里的平移旋转缩放吗这俩怎么凑一块儿的这两年我陆陆续续用HDRnet的思路做过实时调色、视频滤镜、甚至手机端的画质增强越用越觉得这套设计的精妙之处不在某个单点技巧而在它把“全局思路”和“局部细节”拆得明明白白——大方向用低分辨率的小网络去猜小细节用双边网格和仿射变换去套。这篇文章我就把这套东西掰开揉碎从双边滤波的数学直觉到仿射变换为什么非得是矩阵再到整个网络的流水线拆解最后聊一些我在实际落地中踩过的坑。适合对图像处理有一定基础、想做实时增强或者视频滤镜的工程师也适合刚入门想搞懂HDRnet到底在干什么的读者。1. HDRnet到底做了什么——从一个图像问题说起先说清楚HDRnet解决的是什么问题。日常修图的时候我们经常要调亮度、调对比度、调色调。最粗暴的做法是全局调整比如整张图提亮0.3挡或者整张图把色温往暖色拉一点。但真实需求往往是局部的——天空要压暗人脸要提亮草地饱和度要高一点但肤色不能跟着变。传统方案里局部调整靠蒙版、靠笔刷这在单张图上可行一到视频就崩溃了几百帧画面你得逐帧去画蒙版画完还闪烁。HDRnet做的事情正是把这种“逐像素不同调整”的复杂映射变成一个可以实时计算的流程。它的输入是一张普通图片输出是一张调色后的图片中间不需要用户去画任何蒙版网络自己根据图像内容学会在哪个区域做什么样的调整。换句话说它用数据驱动的方式隐式地学会了“天空该压暗、人脸该提亮”这种规则。更有意思的是它的运行策略。HDRnet没有直接在原始分辨率上跑一个大网络来做像素级预测而是先把图像“拆”成两层——一层是平滑的大结构一层是细碎的纹理细节然后只在低分辨率上预测一个“调整规则”最后把这个规则作用回原始分辨率。这就引出了它的两个核心工具双边滤波用来做图像分解仿射变换用来做低分辨率预测结果的表达和上采样回全分辨率的桥梁。2. 双边滤波HDRnet的地基2.1 双边滤波的数学直觉空间近加颜色近才一起平均双边滤波Bilateral Filter不是HDRnet发明的它上世纪90年代就有了是图像处理里处理“保边平滑”的经典工具。普通的高斯模糊对所有像素一视同仁地加权平均结果就是边缘也被糊掉了。双边滤波多加了一个权重项不仅要考虑空间距离近不近还要考虑像素值差异大不大。两者都是高斯函数乘在一起作为最终权重。数学形式长这样滤波结果在像素p处等于分母是归一化系数分子是邻域内所有像素q的加权平均。空间距离项告诉算法“离得远的别管”颜色差异项告诉算法“颜色差太多的也别管”。于是在边缘两侧虽然空间距离很近但颜色差异巨大权重趋近于0边缘就保住了而在平坦区域颜色接近空间权重起主导作用平滑效果就出来了。我当时学双边滤波的时候脑子里冒出来的类比是一群人在广场上聊天你想听清楚某个朋友的声音不仅要靠近他空间近还要关注那些和你朋友音色相似的声音颜色近——噪音虽大但只要你同时筛选“距离近”和“音色像”就能把朋友的话听得清清楚楚。双边滤波干的就这事。2.2 基础层与细节层把图像“拆骨头”和“拆肉”HDRnet的第一步是用双边滤波把输入图像分解成两个部分基础层base layer双边滤波的输出保留了大的亮度结构和色彩过渡但去掉了高频细节。细节层detail layer原图减去基础层剩下的就是纹理、噪点、边缘细节。用大白话说基础层是骨架细节层是皮肉。为什么要拆因为后来的调色处理只作用在基础层上细节层原封不动地加回去。这样做的好处是调色时不会把细节弄糊也不会把边缘弄出光晕。这里有一个关键选择为什么用双边滤波而不是普通高斯模糊来做分解因为高斯模糊会让边缘附近的亮度“渗”到另一边导致基础层在边缘处出现渐变带。如果你把调整作用在这个渐变带上再叠加回细节层就会出现所谓的halo伪影——物体边缘外面一圈发亮或发暗的光圈。双边滤波保住了边缘基础层在边缘处是锐利的细节层在高对比度区域的响应也更干净。HDRnet后续能够稳定地做色调映射和颜色增强这个分解是地基。2.3 双边网格把双边滤波从“局部”变成“全局”不过直接对全分辨率图像做双边滤波计算量不小而且它本质上还是局部操作——每个像素只参考自己小邻域内的信息。HDRnet需要一个更宏观的“调整规则”不能只看局部。所以论文引入了双边网格bilateral grid的概念。双边网格的思想是把图像从二维平面投射到一个三维空间三个轴分别是x坐标、y坐标和像素亮度值或颜色值。在平面图像上相邻、亮度也接近的像素在三维网格里会落在同一个cell里可以共享同一套处理参数。这个过程相当于把局部相似的信息在半全局范围内聚在一起。HDRnet真正在代码里做的是用一个小卷积网络在低分辨率图像上预测一个系数网格这个网格的长宽通常是原始图像的1/16到1/32左右第三维对应离散化的亮度层。然后通过一个称之为“slice”的操作把网格里的系数沿亮度维做插值重新映射回原图的二维空间。这本质上是双边滤波的逆过程——双边滤波把二维图像拉升到三维网格里做处理而HDRnet在三维网格里预测好了结果再通过slice落回二维。3. 仿射变换让色彩变换在三维空间里“打滑梯”3.1 为什么每个像素需要的不是一个数而是一张矩阵很多早期的图像增强网络对每个像素只输出一个标量增益比如亮度乘个1.2。问题在于真实调色很少是纯乘法——提亮的同时可能还需要压低高光、调整中间调饱和度、矫正肤色偏色。这些操作在通道之间是有耦合的一个通道变了另一个通道往往也要跟着变。HDRnet的做法是对每个像素或者更准确地说对双边网格里的每个cell输出一个3x3的颜色变换矩阵外加一个3x1的偏置项合并起来就是一个3x4的仿射矩阵。也就是说归一化后的RGB三个通道通过这个矩阵被线性变换成新的RGB三个通道。为什么要用矩阵而非标量因为矩阵能表达通道间的混合。比如最常见的白平衡调整本质上是红色通道和蓝色通道的独立缩放标量也能做但像“让肤色更红润同时保持草地绿色不变”这种操作就需要红通道的增益根据绿通道的值做调整这就必须有交叉项。仿射矩阵天然支持这种交叉耦合。矩阵虽然简单但在颜色空间里很多调整确实是近似线性的尤其在小范围内所以它在表达力和计算量之间取得了很好的平衡。3.2 从查表到“现场调制”传统图像处理里颜色变换最常用的工具是LUTLook-Up Table查找表。3D LUT把RGB空间划分成一个个网格点每个网格点存一个变换后的RGB值处理图像时对像素所在的网格做三线性插值。LUT的好处是灵活可以表达非常复杂的色调映射风格坏处是它是静态的——同一个LUT适用于所有图像不会根据图像内容主动改变。HDRnet相当于把LUT的动态化做到了极致传统LUT是提前调好的饮料配方表HDRnet则是根据顾客当天的状态现场配饮料。双边网格里的每个cell都相当于LUT里一个节点而这个小卷积网络扮演了“调酒师”的角色它看一眼低分辨率的图像就决定每个cell应该用什么样的变换矩阵。到了具体执行时你对原图像里的每一个像素做一次双线性查找找到它对应的矩阵然后做矩阵乘法一步到位。3.3 为什么仿射变换够用不需要更复杂的映射看到这里你可能会问既然网络都能预测了为什么不直接预测一个多层感知机里的权重或者预测一个更复杂的非线性映射答案是没必要。原因有三点。第一双边网格本身是平滑的网格的分辨率不高相邻cell之间的差距不会太大因此网格内每个cell对应的图像区域其像素值分布相对集中用仿射变换去描述这个局部区域的色彩映射已经足够精确。第二仿射变换参数少计算开销极低——每个像素只需要12次乘加运算3x4矩阵乘3维向量在GPU和移动端NPU上极其友好。第三仿射变换是可微的训练时梯度可以稳定地回传到网格预测网络不会因为复杂的非线性变换导致梯度消失或爆炸。4. HDRnet整体架构拆解从输入到输出的完整流水线4.1 两条通路并行低分辨率负责理解全分辨率负责执行HDRnet的架构可以用两条通路来理解。第一条通路叫做全分辨率通路。原始RGB图像进来先经过一个双边滤波得到基础层再用原图减去基础层得到细节层。基础层会被送入后续的仿射变换模块细节层则在一旁等待最后原样加回去。第二条通路叫做低分辨率通路。原始图像被缩放到一个很小的尺寸论文里用的通常是宽高约256像素然后送入一个小型卷积网络。这个网络的主体是编码器-解码器结构先通过若干次卷积加下采样把特征图缩小到很小的空间尺寸再通过上采样恢复到约低分辨率原始图的1/4大小。网络最后输出一个系数张量这个张量就是双边网格里每个cell对应的仿射矩阵集合。两条通路最终在一个叫“slice”的模块汇合。流程是先把低分辨率通路上预测出来的仿射矩阵网格用三线性插值上采样回原始分辨率长宽维度直接缩放亮度维度插值然后对全分辨率通路的基础层逐像素应用对应的仿射矩阵。做完矩阵变换后再把细节层加回来输出最终结果。4.2 为什么预测一定要放在低分辨率上做这是HDRnet整个设计里最反直觉但也最聪明的地方。用户看到的是高清图凭什么用一个256像素的小图去决定调整方案这不就相当于让一个近视眼帮你修图但仔细想想调色这件事本质上是个语义任务。你要判断“这是天空”“这是皮肤”“这是草地”然后决定怎么调整。语义理解不需要太高的分辨率——你缩到很小依然能看出天空和草地的区别。相反如果直接在原始分辨率上做语义理解计算量会爆炸而且网络会被高频细节干扰反而抓不住大结构。低分辨率预测带来的巨大收益是速度。一个256像素输入的小网络在GPU上跑一遍只需要几毫秒而之后的slice操作又恰恰是廉价的查表和矩阵乘。这个“在低分辨率上理解语义、在高分辨率上执行变换”的思路后来影响了很多实时图像处理方案包括一些做超分辨率和视频增强的工作。4.3 slice操作的实现细节从网格到像素的“精准投喂”slice是整个HDRnet里最核心也最容易写错的部分。它在代码里通常实现为一个自定义算子输入有两个一是低分辨率预测出来的系数网格affine_grid尺寸是(H/16, W/16, D, 12)12对应3x4矩阵二是全分辨率的基础层base尺寸是(H, W, 3)。计算流程大致是把基础层的每个像素坐标映射到网格坐标系得到一个归一化的坐标值。沿亮度维度找到像素值在离散亮度层之间的两个相邻层。在这两个层的空间邻域里取出对应的4个或8个格子里的仿射矩阵。把这些矩阵按权重插值合成一个矩阵应用在像素上。整个过程中最需要注意的是光照亮度维的映射方式。HDRnet通常在sRGB空间处理图像网格的亮度维覆盖了[0,1]范围但实际图像的亮度分布往往集中在中间调两端很少。如果网格均匀划分会导致大量网格单元利用率很低而中间调附近的单元又不够精细。我在复现时做了一个改进把亮度维的采样点设计为按百分位数分布的或者直接用标准差归一化后再映射这样中间调的精度能提升不少。4.4 训练目标与数据准备HDRnet的训练需要成对的图像数据一张原始图一张用专业软件调好色的目标图。论文里的训练集是通过一个自动化的Lightroom流程生成的里面包含各种风格的调色组合。测试时网络输出要与目标图计算损失。损失函数用的主要是L2损失在sRGB空间计算。作者提到他们对基础层和最终输出的损失都做了约束确保网络调整的程度不会过于激进。实际训练中我发现单纯L2容易让输出变得“保守灰”所以可以在损失里加上一个小的感知损失项——用VGG网络中间层的特征差异做补充效果会好很多。这个不是论文原版的做法但实践中很有效。另外训练HDRnet的一个大坑是输入图像和目标图像之间的对齐必须非常精确。如果是视频帧前后帧没对齐网络会自动学到模糊的效果来“平滑”误差。如果是对同一个场景的不同曝光照片必须先做光流配准否则损失函数会引入重影。5. 实操经验训练、调参与落地避坑5.1 没有成对数据怎么玩先让LUT替你打工很多读者问我HDRnet不是需要大量成对数据吗我手里没有Lightroom自动化流程也请不起专业调色师怎么办我的经验是分三步走。第一步先用现成的3D LUT生成伪成对数据。网上有大量免费的风格LUT把一张图分别用原图和LUT处理后的图当成一对数据。LUT生成的数据风格相对单调但足以让网络跑通流程。第二步加入人工调色数据增强。可以写一个小脚本随机生成曝光补偿、对比度、饱和度、色温、色调等参数对图像做全局调整。这样能覆盖比单一LUT更广的调整空间。第三步如果预算允许可以用Lightroom的API批量生成风格化数据并在里面混入一些局部蒙版调整的例子比如提亮人脸、压暗天空让网络见过“局部调整”长什么样。5.2 三个最重要超参数网格分辨率、基础层的sigma、网络宽度HDRnet复现过程中最影响结果的是三个超参数。第一个是双边网格的空间分辨率。论文里通常用原图尺寸的1/16作为网格的长宽但如果你处理的图像细节很丰富1/16会导致网格的分辨率不够色彩过渡出现“色块感”。我实测下来1/8的效果与1/16差别很大但计算量会增加4倍左右。折中方案是1/12或取固定网格宽度128。第二个是双边滤波的sigma它决定了基础层保留多少细节。sigma太大基础层过于平滑细节层扛了太多结构信息同样会产生光晕伪影sigma太小基础层保留了太多纹理后面的集中调整起不到“统一”作用。经验范围是空间sigma取图像宽度的2%左右颜色sigma取0.1图像范围0到1。第三个是网络宽度。HDRnet原论文里的低分辨率通路网络很小卷积核通道数从16开始逐层增加到最后也就64、128。这个规模的网络在现代硬件上已经非常轻量了不需要往大了调。如果任务复杂我倾向于加深而不是加宽——在编码器和解码器之间多堆几层残差块比单纯增加通道数更有效。5.3 性能数据与移动端优化的思路HDRnet论文报告的推理速度是在NVIDIA Titan X上、以1080p输入、处理速度达到每秒几十帧。我在自己的电脑上复现时GTX 1080下1080p大概能跑30fps左右瓶颈主要在双边滤波和slice操作而非低分辨率预测网络。做移动端部署时有两个优化方向很值得尝试。第一把双边滤波简化成快速双边滤波或导向滤波。HDRnet拆成基础层和细节层时未必需要严格的双边滤波导向滤波的边缘保持效果类似而且可以用盒式滤波迭代加速对移动端NPU更友好。第二把slice操作替换成预计算的上采样网格。既然仿射矩阵网格已经低分辨率预测出来了可以提前把它上采样到固定尺寸运行时只做查表和矩阵乘。这样就把slice算子从“自定义算子”退化成了“标准的双线性采样矩阵乘法”很多推理引擎都能直接加速。5.4 一个训练时容易踩的坑颜色溢出和渗色我第一次在复杂场景上训练HDRnet时输出图像经常出现一种奇怪的“泛灰”现象边缘附近尤其明显。排查了很久发现问题出在基础层和细节层的分解上双边滤波的sigma参数太小导致大量边缘结构残留在基础层里而这些结构在仿射变换后又被叠加了一次细节层产生双重轮廓。把sigma调大之后问题立刻消失。还有一个渗色问题当网格的亮度维分辨率设得太低比如8层原本不同的色相会被分到同一个亮度bin里导致颜色在明暗交界处互相“渗透”——比如深蓝色的天空和和浅蓝色的云在交界处出现紫色色带。解决办法有两个要么提高亮度维层数要么在训练数据里加入更多高对比度边界的样本让网络学会在这些区域保持保守。6. 从HDRnet延伸出来的思考6.1 HDRnet和“师父领进门”的关系回头再看HDRnet它最值得学习的不是某个具体的模块而是那个“在不同分辨率之间协作”的思路——低分辨率负责语义理解高分辨率负责细节执行。这个思路我在后来的超分辨率、视频插帧、甚至图像分割项目里都反复用到。特别是在做视频级实时调色时HDRnet几乎是现成的骨架。时间上没有引入任何额外的模块直接逐帧跑也能保持相对稳定的效果。如果想进一步抑制闪烁可以用光流在时间维度上对仿射矩阵网格做平滑计算量增加很小但视频连续性会有质的提升。6.2 如果重新设计一次我会怎么改HDRnet是2017年的工作了拿到今天看有几个地方明显有升级空间。第一个升级点是特征表示。原论文的仿射变换只在RGB颜色空间里做但很多风格化任务需要HSV或Lab空间的调整。我尝试过让网络直接输出RGB到RGB的仿射矩阵改成输出RGB到Lab再映射回RGB的级联矩阵风格表达的丰富度会好一些代价是训练稳定度下降需要加额外的正则项。第二个升级点是网格的亮度维。贴合实际可以引入一个小的注意力模块让网络自己决定每个区域用多少层亮度bin。这样在过曝、低照度区域网格的分配会更合理。第三个升级点是数据效率。原方法是监督学习数据是其瓶颈。现在完全可以用自监督的方式比如用cycle-consistency约束让网络从一个领域的调色风格迁移到另一个领域不需要目标图像也能训练。我在几个风格迁移实验里验证过效果虽然不如有监督数据好但也足够实用了。6.3 一点小技巧在HDRnet里加“局部编辑”的控制能力最后分享一个我在实际产品里很喜欢用的小技巧。原版HDRnet不适合做交互式编辑因为用户没法告诉网络“我想调这里不想调那里”。我的做法是额外输入一个用户涂鸦的掩码图把它下采样后作为额外的通道拼接到低分辨率通路的输入里。训练时随机模拟用户刷蒙版的区域比如圆形笔刷、不规则矩形让网络学会跟随蒙版的引导进行局部调整。效果拔群。用户甚至不需要把蒙版画得很精确随意涂抹一个大致的区域网络就会自动在语义边界内做调整。这个方式比逐帧蒙版效率高得多而且保留了HDRnet原本的实时性。如果你正在做手机修图App里的局部调整功能非常推荐试一下这个思路。最后再提一个实际经验直接套用原论文的代码未必能跑出论文里的效果因为论文在训练时对数据做了很多隐含的预处理特别是sRGB和线性RGB之间的转换、以及色调映射的步进。复现的时候一定要先确认你训练空间和你推理空间是同一个颜色空间否则一个白平衡偏色问题会让你怀疑人生。我在这一步栽过跟头排查了整整两天到头来就是数据输入时忘记把sRGB转成线性空间。
返回列表