首页 / 资讯中心 / 文章详情

水下图像增强:波长补偿与去雾算法原理及Matlab实现

水下图像增强:波长补偿与去雾算法原理及Matlab实现 ★ FEATURED ARTICLE
水下图像增强这块最近几年讨论热度一直没降过。很多人一上来就直接套大气去雾结果出来的图要么偏蓝、要么偏绿严重一点的还把暗部噪声放大到没法看。真正可行的一类思路是把波长补偿和去雾放在同一条处理链路里先用成像机理把颜色退化拉回来再在修正后的模型上做散射去除最后用 Matlab 做快速验证和调参。这篇文章是我自己复现这类算法时的一些记录包括物理模型、关键公式、实际代码、评价指标和踩过的坑适合正在做水下图像处理课题、或者想拿一个能跑的 Matlab 方案做对比实验的同学参考。1. 水下图像为什么需要“波长补偿”和“去雾”同时上阵1.1 水下成像退化的两个层次水下照片的退化本质上是光在水中传播时被水和水中微粒共同作用的结果。光从物体表面反射出来经过一段水程到达镜头这个过程中有两个主要损耗一是水对光的吸收二是水对光的散射。吸收把特定波长的能量直接吃掉了散射则把本来不该进入镜头的光也带进来形成一层“雾霾”。水对不同波长光的吸收程度差别非常大。红光在几米之内就会衰减掉大半绿光能传得远一些蓝光在清澈海水中衰减最慢。这也是为什么水下照片普遍发蓝或者发绿而且在浅水区域可能偏绿在较深区域偏蓝。这个现象不是白平衡能简单解决的因为它是物理层面的波长选择性衰减不是相机传感器偏色。去雾环节处理的则是另一件事。水中悬浮的颗粒、浮游生物会把环境光散射进成像光路就像空气中的雾霾一样产生一层覆盖全图的乳白色/蓝白色罩子。这个罩子会让远处场景变得模糊对比度下降色彩发灰。传统大气去雾算法里也有“大气光”和“透射率”的概念到了水下不能照搬因为水对每个波长的散射和吸收系数不一样。简单说水下退化是“颜色选择性衰减 多波长散射罩”的叠加效果。前者造成通道能量不平衡后者造成整体模糊和发灰。只做颜色补偿散射罩还在只做去雾颜色补偿又会被错误的透射率估计带偏。所以实用的做法是把两者放进同一条处理链路让颜色恢复和去雾互相配合。1.2 大气去雾算法在水下直接搬为什么不行大气去雾最常用的是暗通道先验核心是假设无雾图像的每个局部区域至少在某个颜色通道上存在亮度很低的像素。基于这个先验可以估计透射率和大气光然后反推清晰图像。但水下图像会让这个假设失效。水下场景的背景光不是灰白光而是蓝绿色红色通道在远处几乎为 0反而让暗通道检测不到真正的“暗”区域。换句话说水下图像的暗通道本身带有强烈的颜色偏向直接用标准暗通道算法估计出的透射率会在红色通道上严重失调。更本质的原因是大气模型通常假设三个通道共享同一个透射率 t(x)因为大气对不同波长的散射差异不算极端。但水下不是这样红色通道的透射率可能只有 0.1蓝色通道却有 0.6、0.7用一个统一 t(x) 去恢复三个通道结果就是把红色通道的噪声放大得一塌糊涂或者把蓝绿通道恢复过头。所以水下图像增强尤其是偏物理模型的算法一定要把“波长补偿”作为前置或内嵌步骤。所谓波长补偿就是根据每个通道的衰减差异把能量损失较多的红通道补回来。补偿完再套去雾模型透射率估计才有意义。2. 算法整体设计先补偿、再雾化、最后微调2.1 波长补偿的几种常见做法波长补偿说起来简单但实现路线有好几种选哪一个直接影响结果。第一种是全局灰度世界补偿。假设整个场景的平均反射率是中性灰在水下没有退化时RGB 三个通道的平均值应该基本一致。于是可以统计每个通道的全局均值以最大的均值通道为参考把另外两个通道乘上一个系数拉平。这种做法实现成本最低几行代码就能跑但问题也很明显如果场景本身大面积是某一种颜色或者光照不均匀全局平均会被带偏补偿后容易出现红色过饱和。第二种是基于背景光的补偿。先估计水下环境光把背景光看作水体的等价颜色用背景光中各通道的比例关系去补偿场景。这一般比纯灰度世界更稳因为它更贴近“水体颜色”而不是“景物颜色”。背景光偏蓝的图红色通道会被补偿得多一些背景光偏绿的图红色和蓝色通道都会调整。第三种是基于衰减系数的物理补偿。水下不同水质对应不同的衰减系数如果我们能大致估计出场景深度或者红色通道的透射率就能按指数衰减规律补偿。公式可以写成补偿系数与深度相关。这种方案最严谨但需要先估深度或衰减参数参数不准时容易把红色通道补成紫色。我自己在实际复现时用的是“先背景光补偿再加增益上限”的路子。背景光补偿负责大体色调校正增益上限负责防止红色通道在暗处被过度放大。这个组合对多数水下测试图都适用而且不会出现太离谱的颜色偏差。2.2 水下成像模型与去雾方程水下成像模型常用下面的表达式I_c(x) J_c(x) * t_c(x) A_c * (1 - t_c(x))其中 c 表示 RGB 三个通道I_c(x) 是观测到的像素值J_c(x) 是场景原本的辐射值A_c 是水体背景光t_c(x) 是对应波长的透射率。t_c(x) 通常写成指数衰减形式t_c(x) exp(-beta_c * d(x))beta_c 是第 c 个通道的衰减系数d(x) 是场景点到相机的距离。因为 beta_red 最大所以 t_red 最小红色通道的信息衰减得最快。这也是水下图像发蓝的数学模型来源。如果忽略波长差异把 t_c(x) 当成同一个 t(x)那么恢复公式就退化成大气去雾的形式。水下增强里常见的一个改进思路是先通过波长补偿把三个通道的衰减差异尽量抹平让 t_R、t_G、t_B 在数值上更接近然后再用一个统一的透射率去恢复。虽然理论上有简化但实际效果却比直接套大气去雾稳定很多。另一种做法是显式地估计每个通道的 t_c。这种做法需要估计三个衰减系数参数更多调参也更麻烦。对课题验证来说先补偿再统一去雾是性价比最高的方案跑得快效果可控也便于解释。2.3 整体处理流水线我采用的完整处理流水线如下读入图像并转成 double 类型范围归一化到 [0,1]。统计各通道均值结合背景光估计计算波长补偿增益。限制增益上限对图像做逐通道乘法补偿。在补偿后的图像上估计背景光。计算归一化暗通道估计初始透射率。用导向滤波对透射率做保边平滑。按去雾公式恢复场景辐射值。做适当的对比度和饱和度后处理。这条流水线的核心逻辑是先用补偿把“颜色偏”修掉再用去雾把“罩子”扯掉最后的后处理只是锦上添花。如果顺序反过来先做去雾再做补偿背景光估计往往会收到偏色影响得到的结果容易出现局部色斑。下表是我对比过几种顺序的直观经验处理顺序常见结果问题点先补偿再去雾色调自然远处场景清晰度提升补偿增益需要限制否则红噪点先去雾再补偿容易偏青背景光不稳定去雾时透射率受偏色干扰只补偿不去雾颜色变好了但整体发白发雾散射罩没有被移除只去雾不补偿图像变“干”了偏蓝绿色无法纠正颜色衰减这个对比不是严苛实验只是我复现测试集时得到的直观感受但已经能说明问题两个环节缺一不可前后顺序也很关键。3. Matlab 实现从主函数到关键模块3.1 数据读入与颜色补偿模块先看主函数。我习惯把所有步骤封装成一个增强函数方便批量测试不同图片。%% 主脚本跑单张图 clear; clc; close all; I im2double(imread(sample_underwater.png)); [J, A, t] uw_enhance(I); figure(Name, 水下图像增强效果); subplot(1, 2, 1); imshow(I); title(原图); subplot(1, 2, 2); imshow(J); title(增强后);这里的uw_enhance是我自己写的入口函数里面按流水线顺序调用各模块。如果想保存中间结果我一般会在每个模块前加一句imwrite这样方便定位是哪一步出了问题。颜色补偿模块我做得比较保守。先用全局均值算通道增益再对增益做上限限制function [Ic, gain] colorCompensate(I) % 波长补偿基于灰度世界假设限制最大增益防止红通道噪声爆炸 Ic I; mR mean(I(:, :, 1), all); mG mean(I(:, :, 2), all); mB mean(I(:, :, 3), all); m [mR, mG, mB]; % 水下通常蓝/绿通道衰减较弱取最大均值通道作为参考 ref max(m); gain ref ./ m; % 限幅不能无限制放大否则暗红通道噪声会变成彩色颗粒 gain min(gain, 3); gain max(gain, 1); for c 1:3 Ic(:, :, c) I(:, :, c) * gain(c); end % 重新归一化到 [0,1] Ic max(Ic, 0); Ic min(Ic, 1); end有人可能会问为什么不是让三个通道均值完全相等因为水下真实场景里红色衰减不是均匀的远处区域损失更多近处可能还好。全局增益会把近处本来正常的红色也过度放大导致画面发红发紫。限制增益是为了“宁少勿过”毕竟后面还有去雾和后处理来补充对比度。如果想让补偿更精细可以把增益从常数改为空间变化图。比如先估计一个大概的深度图然后让红色增益随深度增加而增加。这个版本我后面会在扩展方向里提一句初版不建议直接上。3.2 背景光估计模块背景光 A 是去雾模型的重要参数估计错了整个结果都会崩。常用方法是暗通道先验里的“最亮像素法”但在水下要小心。我的实现如下function A estimateAtmosphere(I, radius) % 基于暗通道估计背景光 % 取暗通道最亮的前0.1%像素再在这些像素中选原图最亮的点 dark min(I, [], 3); dark imboxfilt(dark, radius, Padding, replicate); N numel(dark); k max(round(N * 0.001), 1); [~, idx] maxk(dark(:), k); flat reshape(I, [], 3); cand flat(idx, :); [~, p] max(sum(cand, 2)); A cand(p, :); A reshape(A, 1, 1, 3); end这个思路是暗通道最亮的地方一般是远处水体区域那里透射率低背景光成分高。从这些候选点里再找原图最亮的点是为了尽量避免选到白鱼、白色礁石等高反射目标。实际操作中只用单个像素作为背景光仍然有风险。噪声较大时单像素值可能失真。更稳的做法是取候选点前 1% 像素的平均值。你可以根据图像噪声水平在单像素和平均值之间切换。如果测试图像里有明显的人工光源那背景光估计基本要改用手动区域选择法否则光源区域会被当成背景光。3.3 透射率估计与场景恢复有了补偿后的图像和背景光透射率估计就顺理成章了。先把补偿后的图像按背景光归一化取每个像素三通道的最小值作为暗通道然后做一个盒式滤波得到局部暗通道。初始透射率公式t(x) 1 - omega * min_c( I_c(x) / A_c )这里的 omega 一般取 0.9 到 0.95作用是保留一点点自然雾感避免去雾太狠看起来不真实。Matlab 代码如下function [J, t] uw_enhance(I) % 完整增强流程 omega 0.95; tmin 0.1; radius 15; % 第一步波长补偿 I_c colorCompensate(I); % 第二步估计背景光 A estimateAtmosphere(I_c, radius); % 第三步估计透射率 dark min(I_c ./ A, [], 3); dark imboxfilt(dark, radius, Padding, replicate); t 1 - omega * dark; % 限制透射率下限防止远处区域噪声爆炸 t max(t, tmin); % 导向滤波保边平滑 % 如果工具箱没有 imguidedfilter可以用 imgaussfilt 代替但边缘会糊 if exist(imguidedfilter, file) grayI rgb2gray(I_c); t imguidedfilter(t, grayI, NeighborhoodSize, radius, ... DegreeOfSmoothing, 0.01); else t imgaussfilt(t, 1.5); end % 第四步恢复场景辐射 J zeros(size(I_c)); for c 1:3 J(:, :, c) (I_c(:, :, c) - A(1, 1, c)) ./ max(t, tmin) A(1, 1, c); end J max(J, 0); J min(J, 1); end这里面有几个参数值得仔细说。第一个是tmin透射率下限。如果设成 0远处透射率接近 0恢复公式里(I_c - A)/t会把噪声无限放大。一般取 0.05 到 0.2。0.1 是一个常用平衡点既不会太雾也不会噪点太多。第二个是omega这个值是控制去雾强度的。0.95 偏彻底适合本身雾感很强的图如果雾感不强0.85 左右更自然。很多人忽略这一点直接用 0.95 跑所有图结果在本来就清晰的图片上产生过曝。第三个是radius暗通道滤波窗口大小。窗口太小暗通道估计不准会有很多亮斑窗口太大计算慢且透射率过度平滑。15 左右是折中值针对常见测试图效果不错。3.4 后处理对比度与颜色微调去雾恢复出来的 J有时候对比度仍然不够尤其是原本灰蒙蒙的图。这时可以补一步后处理。我常用的做法是限制对比度自适应直方图均衡化但要注意只对亮度分量做避免 RGB 三个通道分别拉伸导致颜色偏移。% 后处理转到 lab 空间对 L 通道做 CLAHE lab rgb2lab(J); lab(:, :, 1) adapthisteq(lab(:, :, 1), NumTiles, [8 8], ClipLimit, 0.02); J_new lab2rgb(lab); % 保险起见再做一次截断 J_new max(J_new, 0); J_new min(J_new, 1);用lab2rgb转换之后颜色饱和度和色相保持得比在 RGB 空间直接拉伸好。如果图像偏色已经由前面的补偿解决得差不多了这一步可以很小幅度甚至不做。要注意的是lab2rgb输出范围可能略超过 [0,1]所以要加截断。另外 CLAHE 的参数ClipLimit越大对比度越强但超过 0.05 容易出现噪点和Halo。我通常从 0.02 开始调。4. 实验效果评估量化指标怎么选、怎么看4.1 主观效果先过一遍 checklist算法好坏不能只看一张效果图。我每次跑完增强结果会先对着原图过一遍 checklist背景区域是否还偏色偏蓝可以理解但不应蓝到发黑。近处主体比如鱼、礁石、潜水员的颜色是否恢复正常远处区域是否还有明显的雾罩边缘是否出现白边或黑边多半是透射率估计出了问题。暗部是否有大量红绿噪点多半是补偿增益或 tmin 设置不当。整体亮部是否过曝多半是背景光估计偏小或去雾强度过大。这些主观检查能快速发现问题但不能替代客观指标。做课题或写报告时量化指标必不可少。4.2 客观指标PSNR、SSIM、UIQM/UCIQE如果有参考清晰图像比如用合成方式生成的水下退化图PSNR 和 SSIM 是最直接的指标。PSNR 衡量像素级重建误差数值越高越好但只对同一场景的退化-恢复对比有意义。SSIM 衡量结构相似性更贴近人眼对结构失真的感知。注意这两项指标在无参考图像的真实水下照片上无法计算只能做合成实验。对于真实水下照片要用无参考指标。水下图像处理里常见的两个是 UIQM 和 UCIQE。UIQM 是基于人类视觉系统的水下图像质量度量通常包含色彩测量、清晰度测量、对比度测量三个分量加权求和得到总分。UCIQE 则是基于色度标准差、对比度和饱和度均值构造的线性组合分数越高表示颜色越均衡、对比度越高。Matlab 里不一定有现成函数很多论文会给出公式或伪代码。我建议在投稿和写报告前先把这些指标的复现代码验证一遍确认计算方式与原始文献一致不然对比表格里的数字没意义。4.3 一个完整的评估小流程我自己跑实验时会准备一个很小的测试集一部分是真实拍摄的水下照片另一部分是用清晰陆地图像通过水下退化模型合成的样本。真实照片用于主观观察和无参考指标合成样本用于计算 PSNR/SSIM。% 计算PSNR/SSIM示例 ref im2double(imread(clean_land.jpg)); % 清晰参考图 degraded underwaterSimulate(ref); % 合成水下退化图 enhanced uw_enhance(degraded); % 增强结果 psnr_val psnr(enhanced, ref); ssim_val ssim(enhanced, ref); fprintf(PSNR %.2f dB, SSIM %.3f\n, psnr_val, ssim_val);这里underwaterSimulate是自己写的退化函数按水下成像模型随机设置衰减系数和背景光。通过调节退化强度可以测试算法在不同恶劣程度下的表现。在解读指标时要小心PSNR 高不代表视觉好SSIM 高也可能只是纹理保留好但颜色不自然。所以报告里应该同时放主观图和指标表格缺一不可。5. 常见问题与调参避坑实录5.1 红色通道过补偿画面发红发紫这是最常踩的坑。全局补偿把红色增益提上去之后原本深蓝背景里几乎没有红色信息放大后全是噪声。表现出来就是画面的蓝色区域变成紫色或者边缘出现彩色颗粒。解决办法有几个按优先级排序限制增益上限比如min(gain, 3)用暗部掩膜让增益在暗区自动减弱改用基于深度/透射率的空间变化补偿。还有一个技巧是补偿前先对红通道做轻微高斯滤波抑制噪声。如果画面发紫把增益上限从 3 降到 2 试试。5.2 背景光估计不稳定时亮时暗背景光选到白色物体时A 会过大恢复图像整体偏暗选到深色区域时A 过小恢复图像过曝。我复现时遇到过同一张图因为滤波窗口稍微变了一下A 值从 [0.7, 0.8, 0.9] 跳到 [0.2, 0.3, 0.4]效果天差地别。更稳的方法是不要只看单个最亮点取暗通道最亮的前 1% 像素在各通道的平均值。另一个思路是结合图像顶部区域如果图像是水平拍摄顶部通常水体更远可以作为背景光候选。如果背景光在相邻帧之间剧烈变化说明算法不稳定此时应该降低候选点比例或者做多帧平均。5.3 透射率下限没设好远处噪声爆炸tmin 太小时远处区域因为透射率接近 0恢复公式几乎在做除法把传感器噪声和无意义的颜色差异全部放大。结果就是图片远处的“雾”没了但变成了彩色噪点。tmin 设太大远处区域又会有残余雾看起来不干净。到底取多少取决于图像噪声水平。画质好的 RAW 图可以设 0.05手机 JPEG 图建议至少 0.1 到 0.15。5.4 透射率图出现方块伪影暗通道先验里的最小值滤波会带来四边形块效应。如果直接用初始透射率恢复前景边缘会出现类似马赛克的亮边或暗边。解决方式是加保边滤波比如导向滤波或双边滤波。Matlab 自带imguidedfilter效果不错。如果没有这个工具箱可以用imgaussfilt快速替代但边缘会变糊。对课题足够对高精度应用不太够。5.5 处理速度慢、内存爆掉高分辨率水下照片很容易把内存和耗时拉满。最直接的办法是压缩到目标尺寸再跑比如最长边 1200 像素。如果必须保持原分辨率可以分成小块处理但要注意块与块之间的背景光和透射率会不一致拼接处容易出现色差。我通常会在分块之间做 10% 的重叠区并取重叠区均值过渡。6. 工程化与扩展方向6.1 批量处理与并行化如果你手上有几千张测试图一张张跑不现实。Matlab 可以用imageDatastore读图配合parfor并行处理把增强结果批量写到输出文件夹。ds imageDatastore(input_folder, FileExtensions, {.png, .jpg}); outFolder output_folder; mkdir(outFolder); parfor i 1:numel(ds.Files) I im2double(readimage(ds, i)); J uw_enhance(I); [~, name, ext] fileparts(ds.Files{i}); imwrite(J, fullfile(outFolder, [name, ext])); end因为uw_enhance内部没有依赖循环变量以外的状态所以适合并行。注意parfor里调用自定义函数时要把函数放到路径中最好保证每个 worker 都能访问。6.2 从静态图像到视频流做水下视频增强时逐帧独立处理的后果是亮度闪烁。背景光 A 和透射率 t 在相邻帧之间跳动画面就一明一暗。解决办法是对 A 和 t 的全局统计量做时间平滑比如指数滑动平均。帧率越高平滑系数可以越小比如 0.3帧率低则用 0.6 到 0.8以免跟上不物体移动。这个方向不算难但做起来很考验工程细节。6.3 与深度学习结合的方向很多刚入门的朋友会问既然深度学习这么火为什么还要研究物理模型原因很现实水下干净参考图太难拿深度学习训练需要大量成对数据。物理模型最大的价值恰恰是可以生成合成退化数据给深度网络当训练样本。你可以用本文的退化模型把清晰陆地图像合成成不同水深、不同水质的退化图然后训练一个去噪/增强网络。也可以把传统算法增强后的结果作为伪标签做半监督训练。总之物理模型不但不过时反而是深度学习时代做数据生成和可解释性分析的好工具。最后再分享一个小技巧。我每次跑一个新数据集不会先埋头调参而是会把样本图缩略图拼成一张大图再看每张图的直方图和背景光估计结果。背景光和透射率统计是否稳定基本决定了整个算法的上限。建议你也把colorCompensate和estimateAtmosphere两个模块单独拆出来调试每一步中间结果都存成图片盯着看。看清了问题在哪再去动参数效率会高很多。
阅读完成 · 觉得有帮助?
咨询建站