水下图像增强这个方向,我盯着它挺久了。第一次读到这篇论文的标题时,我正被一堆水下图像搞得头疼——要么整张图绿得发慌,要么对比度低到没法看,传统的去雾算法套上去总差点意思。这篇论文的做法很直接:用深度CNN配生成对抗机制,把水下图像一键拉回正常颜色和对比度,而且在结构思路上给后续研究留下了不少可借鉴的东西。
这篇论文做的核心事情,简单说就是训练一个生成器,输入是典型的水下退化图像,输出是增强后的清晰图像。为了保证输出不是“看着还行但细节崩了”,它一口气塞了两个判别器:一个盯全局色彩和纹理,一个专门盯局部小块区域。同时损失函数里还加了基于经典VGG网络的感知损失,用高层语义特征来约束生成结果。这套组合拳下来,增强图像既保住了内容结构,又兼顾了纹理真实感。
如果你是在做水下机器人视觉、海洋渔业观测、水下考古影像处理,或者纯粹是想看看生成对抗网络在图像复原领域怎么落地,这篇论文都值得认真读一遍。下面我把论文的思路、损失设计、训练要点和我的复现经验一条条拆开讲。
1. 水下图像增强到底在解决什么问题
1.1 为什么水下图像总是绿油油的
水下成像的退化原因,物理上讲就三件事:吸收、散射、颜色衰减。
可见光在水里传播时,不同波长的光衰减速率完全不一样。红光波长长,能量低,在水里没走几米就被水分子吸收得差不多了;绿光和蓝光波长短,穿透能力强,能走得更远。这就是为什么大多数水下照片都自带青绿色滤镜。往深了潜,蓝绿色也扛不住,整个画面就只剩昏暗的一团。
散射则是悬浮颗粒物干的好事。海水里并不干净,泥沙、浮游生物、有机碎屑到处都是,光线照到这些颗粒上会被反弹、折射,最后进入镜头的除了目标反射光,还混了大量杂散光。这就像大雾天开车开远光灯,眼前白茫茫一片,图像细节被彻底淹没。
这两个因素叠加,就形成了水下图像的三大典型症状:
- 颜色偏色严重,通常是蓝绿色主导,红色通道信息几乎丢失
- 对比度极低,暗部细节基本看不见,灰度直方图挤在很窄的范围里
- 整体模糊,边缘和纹理信息被散射抹平,类似轻微离焦
这些退化不是均匀分布的,同一个画面里,近处的物体颜色可能还算正常,远处的背景已经蓝成一团。这也是为什么很多传统全局处理方法都容易翻车。
1.2 经典去雾方法为什么水土不服
很多刚接触这个方向的人会想:水下图像不就是“雾蒙蒙”的吗?直接用大气去雾算法行不行?答案是行,但效果很一般。
以暗通道先验为代表的去雾算法,数学上依赖于一个假设:户外无雾图像里至少有一个颜色通道的强度接近零。这个假设在晴天户外的确成立,但水下环境的光谱分布完全不一样,红光通道早就被水吸收干净了,暗通道先验算出来的透射率会严重偏移,直接套用会导致输出色偏更严重,甚至产生伪影。说白了,它是为“空气中悬浮小颗粒”建模的,水下的大颗粒散射和波长选择性吸收,它根本没建模。
另一种思路是直方图均衡化和白平衡。前者能拉伸对比度,但它是全图做全局变换,水下图像的退化随深度和位置变化,全局处理不可能兼顾所有区域。白平衡假设图像中存在“灰色参考点”,水下图像里这个假设几乎不成立,硬套的结果就是颜色被矫枉过正,出现一种塑料感的失真色调。
所以水下图像增强真正需要的,是一个能够学习水下场景特有退化模式、并能逐像素有条件地进行色彩和细节恢复的模型。这就把方向指向了数据驱动的深度学习,尤其是生成对抗网络这种擅长从数据中学分布映射的方案。
2. 论文核心思路:让生成器学会“去水下化”
2.1 整体框架:生成器加双判别器
这篇论文的框架属于生成对抗网络的一种应用变体,设计上有一个很清晰的思路:把“从水下退化图到清晰图”的映射交给一个生成器网络去学,同时用两个判别器不断“挑刺”,逼着生成器把图修得越来越像真实的自然光照下的图像。
整个结构可以拆成三块:
- 生成器G:输入退化水下图像,输出增强图像
- 全局判别器:判断整张图到底是“真实的清晰图像”还是“生成器吐出来的图像”
- 局部判别器:随机从图像中裁切若干小块,逐个判断每个小块是真是假
为什么拆两个判别器?这个设计很有讲究。如果你只用全局判别器,模型会倾向于修正整体颜色统计量,比如把平均色调拉回正常,但局部纹理细节可能依然糊成一团。反过来,如果只看局部块,模型可能在色块分布上放飞自我,全局颜色变得混乱。两个判别器一起上,一个管宏观风格,一个管微观结构,正好互补。
选生成对抗网络而不是普通回归CNN,也有一个很实际的理由:普通CNN用像素级损失训练,目标函数把输出推向所有可能结果的“平均”,所以图像会显得平滑、发糊。生成对抗网络多了一个不断进化的判别器当对手,生成器只有输出足够真实、足够锐利的纹理才能赢下博弈,这种对抗性压力正是细节恢复所需要的。
2.2 生成器的设计逻辑
生成器本身走的是典型的编码器-解码器路线。编码器部分通过卷积逐级下采样,把输入图像压缩成高维特征图,这一步的本质是提取“这棵水草长什么样、这条鱼的轮廓在哪”等语义信息。解码器部分通过上采样逐步恢复分辨率,输出增强后的图像。
这里有一个细节很关键:单纯的编码-解码结构会把很多高频细节弄丢,所以通常会在中间加残差连接或跳跃连接,把浅层的边缘信息直接拼到解码器对应层。论文虽然没在结构层面做特别花哨的改动,但把重点放在了损失函数上。这也是我觉得这篇论文最有价值的地方——它证明了在不算复杂的生成器架构下,损失设计对增强效果的影响可能比堆叠网络层数更大。
生成器最后一层一般会用tanh激活函数,把输出像素值压缩到一个固定的范围,和图像归一化区间对齐。中间层则配合卷积、批归一化和ReLU的组合,下采样步长取2,上采样用转置卷积或双线性插值加卷积,两种方式我实测下来效果差异不大,转置卷积更容易出现棋盘格伪影,用插值更稳一些。
2.3 双判别器如何分工
全局判别器拿到的输入是完整图像。它通过下采样卷积,最终输出一个标量,表示输入图像属于“真实清晰图像”的概率。它关注的重点是整体风格:色温是否自然、明暗对比是否合理、全局纹理是否真实。
局部判别器的输入不是整张图,而是从图像中随机采样的固定尺寸小块。做法通常是从输出图像和真实参考图中各随机裁出若干个块,比如16x16或32x32大小,再输入判别器。因为块很小,判别器被迫关注局部细节纹理:这块区域有没有模糊?边缘是不是锐利?有没有奇怪的伪影?
这种“全局风格加局部细节”的分工思路,如今已经被大量图像生成与复原工作借鉴。当时能想到用局部判别器来弥补整体均值的短板,确实是有洞察力的设计。实际使用中,两个判别器可以共享一部分底层卷积层,减少参数量和训练开销,不过论文里更多是各自独立的网络,复现起来更直接。
3. 损失函数怎么设计:细节决定成败
CNN生成模型跑出来的效果,很大程度上由损失函数决定。这篇论文把损失分成了三个部分:对抗损失、感知损失,以及常规的内容约束。它们各管一摊,少了谁都会出问题。
3.1 对抗损失:让生成图“看起来真实”
对抗损失是整个生成对抗网络家族的核心。判别器的任务是区分真假,生成器的任务是骗过判别器,两边的目标完全相反,于是形成动态博弈过程。
用公式表达,生成器的对抗损失可以写成:
L_adv = -E[log D(G(I))]
判别器的损失则是标准的二分类交叉熵,既要正确识别真实清晰图像为“真”,也要识别生成图像为“假”。训练时二者交替更新,判别器先变强,生成器再变强,循环往复。
在这篇论文里,全局判别器和局部判别器各自计算对抗损失,最后相加。我个人复现时的体会是:局部判别器对训练稳定性影响非常明显,如果局部块裁得太小,模型容易陷入高频噪声疯狂生成的模式,输出图放大一看全是噪点;如果裁得太大,又跟全局判别器的功能重叠了。建议从16x16起步,根据输出分辨率微调,多试几组再定。
对抗损失还有一个特点:它本身是没有“上限约束”的,理论上生成器可能为了骗过判别器而输出极端颜色。所以它必须搭配内容约束一起使用,这也是下一小节感知损失存在的重要原因。
3.2 感知损失:不要让像素值绑架生成结果
如果只用对抗损失,生成器经常会出现“自说自话”的问题:生成的图像可能整体观感还行,但内容和原图对不上号,比如鱼的位置歪了、珊瑚的结构被扭曲了。原因是对抗损失只关心“像不像真实图像”,不关心“内容对不对”。
为了把内容也锁住,论文引入感知损失,用预训练VGG网络提取图像特征,在特征空间里计算生成图与目标图之间的距离。VGG网络因为在大规模分类任务上训练过,它的高层特征向量天然携带了丰富的语义信息,两张图在VGG特征空间越接近,说明它们在语义内容上越一致。
感知损失的计算过程是这样的:
- 分别把生成图像和目标图像输入同一个预训练VGG网络
- 取若干层的特征图,一般用浅层和中层的组合
- 对同一层的特征图计算L1或L2距离,加权求和
为什么不在像素空间直接算L1损失?因为像素级L2/L1会把生成器朝“平均化”方向推。大类图像复原任务里都有这个现象:L2损失目标下,模型面对多个可能的合理解,会倾向于输出它们的加权平均,于是结果必然是模糊的。特征空间的距离则宽容得多,只要语义一致、边缘位置大致对上就放行,给生成器留出了“用更真实的纹理来满足约束”的余地。
3.3 权重配比:一切模型调参的归宿
总损失函数大致长这样:
L_total = L_adv_global + L_adv_local + λ * L_perceptual
λ是感知损失的权重系数。论文实验里,λ的取值会影响最终效果的风格倾向:λ太小,对抗损失占主导,输出图像可能颜色很鲜艳但内容失真;λ太大,图像结构稳定但纹理显得平滑,甚至有一种水彩画的涂抹感。
我当时复现做了多组对比,λ从0.5到100都试过,最终在1到10之间的结果最均衡。具体最优值跟数据集有关,我建议不要迷信某一固定值,而是跑完一组训练后把中间结果可视化,盯着看生成图有没有明显的伪影或模糊,再回头调权重。
还有个容易被忽略的细节:感知损失和对抗损失在训练初期不在同一个量级。如果直接相加,感知损失的梯度可能瞬间把方向带偏。我习惯在训练开始时让对抗损失的表现略占上风,比如先用较小λ,等判别器稳定后再把λ提上来,或者用学习率调度器控制两部分的相对步长。这个操作在项目日志里看起来不起眼,但对最终收敛质量影响很大。
4. 训练数据与实验设置
4.1 水下图像数据集怎么准备
训练这套模型需要两类数据:一类是真实水下拍摄的低质量图像,另一类是对应的清晰参考图。问题在于,真实水下环境里很难同时拿到“同一场景、不同水质”的成对图像,所以数据准备是整个流程最头疼的一环。
目前常见的方案有几种:
- 使用合成数据:基于水下成像模型,从清晰图像模拟不同深度、不同水质的退化效果,生成成对训练数据。好处是配对精准,坏处是模拟参数和真实水体分布总有差距
- 使用真实成对数据:在特定拍摄条件下,同一场景用不同设备或不同时间采集,人工对齐。数据量少但真实性强
- 使用不成对数据:借助循环一致性思想做无监督训练,作为补充或预训练手段
我个人的建议是:不要只依赖纯合成数据,否则模型容易过拟合到模拟参数的空间里,遇到真实水下图像时任性的水质分布还是会露馅。实际操作中,我会把合成数据和少量真实数据混在一起,并按场景多样性做筛选,避免某一种水体颜色在训练集里占主导。比如绿藻多的浅水和浑浊泥沙水各留一部分,蓝水深度场景也留一部分,这比盲目堆数据量更管用。
4.2 训练参数与收敛技巧
训练生成对抗网络的经典配置,这里基本都能套用:
- 优化器:Adam,学习率0.0002,beta1取0.5。偏置设低一点能让对抗训练更稳
- 批量大小:因为要同时跑生成器和两个判别器,显存压力大,批量大小通常取1到4
- 图像尺寸:论文及大多数复现使用256x256分辨率。过小细节恢复能力差,过大会让训练速度大幅下降
- 迭代次数:一般在100到200轮左右能看到明显效果,但判别器容易先收敛,需要随时监测
训练过程中要注意判别器和生成器的平衡。一个比较实用的检查手段是记录两者的损失曲线:如果判别器损失快速掉到接近0,说明它太强了,生成器一出生就被压得学不到东西;如果判别器损失一直在高位抖动,说明区分能力不足,生成器可能在自嗨。出现这些情况,可以考虑给判别器加谱归一化、降低判别器学习率,或者减少判别器的更新频率。
数据增强也不要省。随机水平翻转、随机裁切、小角度旋转变换以及适度的颜色抖动,都能有效扩充样本多样性。这里有一个小技巧:颜色抖动不能太夸张,否则模拟出的水下退化模式会被破坏,反而引入了不必要的噪声。
5. 实验结果怎么看:主观之外还要看指标
5.1 常用评价指标理解
水下图像增强的效果评价,远没有分类任务那样简单。因为真实水下图像很难拿到对应的参考图,有参考和无参考的指标都得看。
有参考指标里,最常用的是PSNR和SSIM。PSNR衡量像素层面的重建误差,数值越高越好;SSIM衡量结构相似性,接近1说明结构保持得好。但这两个指标都有各自的毛病:PSNR对全局色彩偏移的惩罚很敏感,一个轻微的色温变化都会让它大幅波动;SSIM对局部对比度更敏感,但不一定能反映人眼对“是否自然”的感受。
所以现在水下图像增强的工作普遍还会报告无参考指标,比如UIQM和UCIQE。UIQM从色彩、清晰度、对比度三个维度综合打分,UCIQE偏向色度信息和对比度的结合。这类指标不需要参考图,直接对增强结果评估,适合真实水下场景的测试。
读论文的实验表格时,一个很容易踩的坑是:某些指标提升很大,但视觉效果却变差了。这种情况往往是因为优化目标恰好和某个指标的计算方式契合,比如模型学会了把对比度拉满,但色彩失真了,PSNR可能还涨了。所以我的习惯是:指标可以辅助判断,但最终结论一定以肉眼对比为准,尤其是看大尺寸原图和局部放大图。
5.2 论文实验结果带给我的启发
论文展示的增强效果,最明显的改善有两处:一是色偏被大幅纠正,原本青绿色的画面恢复到接近自然光下的颜色;二是细节纹理的清晰度显著提升,水草边缘、鱼鳞纹理这些原本被散射糊掉的结构变得锐利。
跟当时的传统方法对比,基于CNN的方法在复杂场景下的稳健性是最明显的优势。传统方法每张图都需要调参,不同水质、不同深度可能需要完全不同的前置处理;而CNN模型一旦训好,推理时就是一张图直接前向跑一遍,速度快且不需要人工干预。这在工程落地上意义很大,比如水下机器人实时视频流的增强,传统算法很难同时满足泛化性和实时性要求,深度模型却很从容。
当然,论文方法也有局限:训练数据如果以浅水区和特定水质为主,模型在浑浊深水区的表现就会下滑;生成图像偶尔会出现细节被平滑的倾向。这些短板也意味着后续的改进空间很大。事实上,后来的不少水下增强工作都在这个框架上做文章——有的换更深的生成器,有的加注意力机制,有的用多尺度结构,但“双判别器加感知损失”这套组合的思想内核,在很长一段时间里都是绕不开的参考系。
6. 复现时的实操笔记与避坑指南
6.1 一个可以落地的代码骨架
如果打算从零复现,我会建议按下面这个顺序搭建:
- 加载预训练VGG19并冻结参数,只用它提取特征计算感知损失
- 搭建生成器网络:编码器卷积、残差块堆叠、解码器上采样,中间加跳跃连接
- 搭建两个判别器:全局判别器最后输出标量,局部判别器输入固定尺寸块,结构可以浅一些
- 定义总的训练循环,交替更新判别器和生成器
- 每固定轮数保存生成器权重,并用一张验证图做可视化对比
代码层面有几点提升效率的经验:感知损失的特征提取尽量在GPU上完成,但VGG参数一定要置为评估模式且不计算梯度,否则显存会被无谓地占满。局部判别器所需的随机裁切操作,最好用数据加载器自带的多进程完成,别在前向传播里写Python循环,否则一卡就是一堆。
还有一个细节:很多人在训练生成对抗网络时习惯把图像归一化到[-1, 1],用tanh作为生成器输出层的激活函数。这样输出图像的像素范围刚好和归一化区间吻合,梯度传播也更顺畅。如果不注意这一点,直接用sigmoid把输出压到[0,1],再配合归一化到[-1,1]的判别器输入,非常容易出现判别器无法收敛的情况。
6.2 我踩过的几个坑
第一个坑是局部判别器的patch尺寸。我一开始为了图快,把patch定成了8x8,结果生成出来的图像整体颜色是对的,但放大看全是高频噪声点,就像加了噪点滤镜。后来把patch改到32x32,并保证每张图裁多个块做平均,噪声问题才压下去。
第二个坑是感知损失的层选择。取太多深层特征会导致输出图像跟参考图像在语义上对齐得很好,但颜色微小的偏差被忽略,整体观感不够透亮。取太浅的层又变成了像素损失,模糊问题回归。我最后锁定在relu2_2和relu3_4两层各取一半权重,效果最稳。
第三个坑跟判别器的更新频率有关。我一开始生成器和判别器每次迭代同步更新,结果训练没几十轮就出现生成器直接把判别器“骗穿”的现象,损失曲线乱跳。后来改成判别器每更新两次,生成器才更新一次,并关闭了批量归一化的训练模式切换,训练过程才稳定下来。这个策略不一定最优,但在小数据集上确实更保险。
第四个坑在数据预处理。水下图像的亮度动态范围很大,有些图暗到黑乎乎一片。如果不做任何亮度和对比度的预归一化,模型会把“变亮”和“增强”混为一谈,训练出来的东西只是提亮滤镜。我在预处理里额外加了一步基于场景的直方图拉伸,只保证输入图像的亮度范围不至于溢出,效果好了很多。
6.3 常见问题速查
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 生成图像有大量高频噪点 | 局部patch尺寸过小、对抗损失权重过高 | 增大patch尺寸,降低对抗损失占比 |
| 输出颜色偏灰、发闷 | 感知损失权重过高 | 减小感知损失权重,让对抗损失多发挥 |
| 训练早期损失剧烈震荡 | 判别器与生成器不平衡 | 降低判别器学习率或减少其更新频率 |
| 输出内容与原图不一致 | 数据对齐不准确、感知损失权重过低 | 检查训练样本,适当增加感知损失 |
| 在真实水下图上效果下滑 | 训练数据以合成数据为主 | 加入真实数据微调,增加数据多样性 |
| 显存不够 | 双判别器加VGG同时占显存 | 减小批量大小、对VGG做梯度截断、用混合精度训练 |
以上这些坑,踩一遍比读十遍论文记得都牢。尤其是patch尺寸和损失权重,我后来在别的图像复原项目里也反复遇到,几乎成了每次复现前必查的两项。
最后分享一个我自己的体会。这篇论文最值得学习的并不是某个花哨的模块,而是它对“问题拆解”的思路——水下图像增强不是单一任务,它同时包含全局色彩恢复和局部细节重建,所以作者设计了两个判别器分别对应这两种需求;同时它又清楚对抗损失管不住内容保真度,所以引入感知损失来补位。这种“把任务拆细,再给每个子任务配一个合适的约束”的思路,拿到任何图像复原场景里都成立。
我后来在另一个图像增强项目里,也套用了“双判别器加感知损失”的框架,只不过把局部判别器的patch换成了带注意力机制的加权方式,效果比原版更均匀。如果你打算复现这个工作,建议先从最小配置跑通,再用验证集盯着输出,一步步把损失权重和patch尺寸调到顺手为止。这条路上没有一步到位的配置,只有不断试出来的经验值。