资讯动态

水下图像增强实战:波长补偿与暗通道去雾的Matlab方案

发布时间:2026/10/22 0:06:28 来源:尧图企业网站定制
水下图像增强这个方向我陆陆续续做过好几个版本从最简单的直方图均衡化一路做到基于物理模型的复原最后停在波长补偿去雾这套组合方案上。先说结论这套方案在真实水下照片上的效果比单纯做直方图或单纯套用暗通道先验要稳很多色彩还原和对比度恢复都能兼顾而且用Matlab实现起来也不复杂非常适合作为水下图像处理研究的起步框架。这篇文章就把整个研究过程、算法拆解、代码实现和踩过的坑一次性说清楚。1. 水下图像为什么总是一层“蓝绿色雾”要理解波长补偿和去雾为什么有效得先搞清楚水下图像变差的两大物理根源光的衰减和散射。RG B三色光在水中的衰减系数差异非常大。红光的波长长能量低在水里几米之内就被吸收得差不多了绿光能传几十米蓝光波长更短传播能力最强。所以你在浅水区拍到的照片通常还带点绿色调到了十几米深度拍出来就是一片蓝这就是光学选择性吸收导致的偏色问题。而这个偏色问题有多严重我曾经拿一段在清澈水域拍摄的视频做分析在5米深左右红色通道的像素均值已经跌到蓝色通道的四分之一以下整张图的颜色信息几乎只剩蓝色域。另一个问题是散射。水中悬浮的微粒、浮游生物会把光线朝各个方向反射其中一部分反射光直接进入镜头形成类似雾天的大气光掩盖效果。这就导致画面发白、发灰对比度急剧下降远处的物体细节完全糊掉。这个现象学模型叫做Jaffe-McGlamery模型它把相机接收到的光分成三部分直接透射分量、前向散射分量和后向散射分量。后向散射其实就是“水里的雾”前向散射则是物体轮廓边缘模糊的根源。所以本质上水下图像增强要做两件事第一把被水吸收掉的红色通道能量补回来这就是波长补偿第二把悬浮粒子造成的散射效应去掉恢复本来被“雾”遮住的细节和对比度这就是去雾。两个问题属于不同的物理过程处理手段也不一样单靠某一种算法很难同时解决。这也就解释了为什么很多直接套用大气去雾算法的代码在水下数据集上效果很差。大气去雾算法假设的“暗通道”在陆地场景里是黑色阴影、深色物体但在水下场景中由于波长选择性吸收即使没有阴影蓝色通道在远处区域也会非常微弱这会被暗通道先验误判为“浓雾区域”结果就是透射率估计严重偏移增强后的图像反而更蓝更脏。波长补偿先改变三通道的能量分布相当于把水下图像拉回接近大气成像的模型假设再去雾才会有效。2. 波长补偿先把颜色底子修回来波长补偿的核心逻辑很简单既然红色衰减最快那我就根据蓝色或绿色通道的能量来估算红色损失了多少把它补回来。但不能老老实实直接加一个固定值那样会在原来的低照度区爆出一堆杂色噪声。我在这套实现里用的是一种基于统计信息的自适应补偿思路很容易理解。先对图像的RGB三通道分别提取亮区统计。这里有一个关键动作不能简单地取全图最大值作为参考亮度因为水下图像往往存在高光区域比如水面反射光、潜水员的气泡反光这些高光严重失真用它做基准会把整张图带偏。稳妥的做法是取全图最亮像素的0.1%位置比如取99.9分位值然后在这些位置对应的小邻域内求平均得到A_r、A_g、A_b三个背景光估计值。有了背景光估计就能计算补偿系数了。我用的补偿式并不复杂R_out R_in (A_g - A_r) * (1 - R_in / A_r) * k_r G_out G_in (A_g - A_g) * ... # G通道作为基准不需要补偿 B_out B_in - (A_b - A_g) * (1 - B_in / A_b) * k_b # B通常过强要压制这个公式的意思很直观红色通道的损失量在亮区和暗区是不均匀的暗区几乎全是噪声你给它补得再多也没有意义反而把噪声放大了。所以补偿强度做一个归一化权重越靠近背景亮区的像素补偿越多越暗的像素补偿越少。同理蓝色通道往往是过饱和的需要对它做衰减而不是增强否则最后整张图会偏紫偏红。k_r和k_b是两个可调增益我实测下来k_r在0.6到0.9之间效果比较好k_b在0.5左右比较安全太大容易制造紫色伪影。这套补偿做完的效果非常明显。红色通道的均值会被拉回和蓝色、绿色接近的水平整幅图的色偏从“严重偏蓝绿”变成“轻微偏冷”。这一步是后面所有处理的地基如果地基没打好后续去雾再强也只能是在一张病态颜色的图上做文章。做波长补偿还有一个容易被忽略的细节先做色彩补偿还是先做亮度均衡顺序不同结果差异很大。我一开始先把RGB三通道各自做直方图均衡化再去做补偿结果三个通道的亮度分布被独立拉伸后用于计算衰减比例的统计量全部失效。正确顺序应该先补偿、后做亮度和对比度调整整个过程里涉及全局统计的操作尽量放到补偿之后再跑。3. 去雾环节暗通道先验的水下改良解决了颜色问题接下来处理散射引起的雾感。这里我用的是暗通道先验Dark Channel Prior, DCP的改良版核心逻辑保留但做了几个针对水下场景的修改。标准的DCP假设在绝大多数非天空的局部区域里至少有一个通道的亮度值趋近于零利用这个暗通道图可以估计透射率。大气散射模型是I(x) J(x) * t(x) A * (1 - t(x))其中I是观察到的模糊图J是要恢复的清晰图A是全局背景光t(x)是透射率。推导之后透射率可以表示为t(x) 1 - w * min_c ( min_Ω ( I_c(x) / A_c ) )这个w是保留少量雾气的常数通常取0.95避免恢复出来的图太假。问题在于直接把这个公式套到水下图像上min_c那一步在蓝色通道占比极大的区域会严重低估透射率。所以我先对上一步波长补偿后的图像做一次归一化让三通道的能量水平大致接近然后再计算暗通道。修正后的暗通道定义为补偿图像的各个通道分别除以对应的背景光估计值之后再取通道最小值。这个变化虽然只在公式里多了一步归一化但效果天差地别。用原始图像做暗通道时远处区域因为蓝色通道过度占优整片都被判为低透射率结果恢复出来的远景一片死黑。归一化之后远处区域的透射率就比较合理了远景的细节能拉回来不少。透射率图拿到之后不能直接用因为它基于局部块估计边缘会有严重的块效应。我的方案是先做一个下采样粗估计然后用导向滤波对透射率图进行细化。导向滤波的好处是可以保持物体边缘的锐利度同时让透射率在平滑区域内渐变自然。导向图直接用灰度化后的原图。这里有三个参数需要调下采样倍率、暗通道局部块半径和导向滤波的epsilon正则系数。暗通道局部块半径太小会导致透射率估计噪声大太大则会在物体边缘产生光晕。我经验值是块半径为8到15像素视图像分辨率而定epsilon取1e-3附近导向滤波半径是块半径的两倍左右。最终恢复公式是J(x) ( I(x) - A ) / max( t(x), t0 ) At0是透射率下限防止分母趋近于零导致噪声爆炸。标准大气去雾里t0取0.1但水下图像的噪声水平通常更高我建议取0.2到0.3宁可保留一点薄雾也好过放大噪点一片花。这个看似微小的数值对最终观感的影响非常大。4. 完整流程与Matlab实现细节整套算法的运行顺序我调过很多次最后固定为输入图像预处理、波长补偿、归一化暗通道透射率估计、导向滤波细化、去雾恢复、gamma亮度校正。下面给出Matlab框架可以直接按这个结构组织代码。function J underwater_enhance(I_rgb, params) % I_rgb: 输入RGB水下图像uint8类型 % params: 结构体包含各项参数 I double(I_rgb) / 255; % 1. 分割通道并估计背景光 R I(:,:,1); G I(:,:,2); B I(:,:,3); [A_r, A_g, A_b] estimateBackground(I); % 2. 波长补偿 k_r 0.75; k_b 0.5; R_c R (A_g - A_r) .* (1 - R ./ (A_r 1e-6)) .* k_r; G_c G; B_c B - (A_b - A_g) .* (1 - B ./ (A_b 1e-6)) .* k_b; I_c cat(3, R_c, G_c, B_c); I_c max(0, min(1, I_c)); % 3. 计算归一化暗通道和透射率 I_gray rgb2gray(I_c); blockSize 12; omega 0.95; dark computeDarkChannel(I_c ./ cat(3, A_r, A_g, A_b), blockSize); t 1 - omega * dark; % 4. 导向滤波细化 r 24; eps 1e-3; t_refined guidedfilter(double(I_gray), t, r, eps); t_refined max(t_refined, 0.25); % 透射率下限 % 5. 去雾恢复 J_r (R_c - A_r) ./ max(t_refined, 1e-6) A_r; J_g (G_c - A_g) ./ max(t_refined, 1e-6) A_g; J_b (B_c - A_b) ./ max(t_refined, 1e-6) A_b; J cat(3, J_r, J_g, J_b); J max(0, min(1, J)); % 6. 亮度校正 J imadjust(J, [0.05 0.95], []); end背景光估计函数我用的是分层搜索思路是把图像分割成四个子块优先选择方差最小且亮度均值最大的子块递归几次后定位到一块相对平坦的亮区在该区域内取特定分位数的均值作为背景光估计。这个函数实现不复杂但比直接取最亮像素稳健得多白墙、白色游泳圈这类高光目标不会误导估计。function [A_r, A_g, A_b] estimateBackground(I) % 四叉树分层搜索背景光 img I; for i 1:8 h size(img, 1); w size(img, 2); b1 img(1:floor(h/2), 1:floor(w/2), :); b2 img(1:floor(h/2), floor(w/2)1:end, :); b3 img(floor(h/2)1:end, 1:floor(w/2), :); b4 img(floor(h/2)1:end, floor(w/2)1:end, :); blocks {b1, b2, b3, b4}; scores zeros(4, 1); for j 1:4 block blocks{j}; blockGray rgb2gray(block); scores(j) mean(blockGray(:)) - std(blockGray(:)); end [~, idx] max(scores); img blocks{idx}; end brightR img(:,:,1); brightG img(:,:,2); brightB img(:,:,3); A_r quantile(brightR(:), 0.95); A_g quantile(brightG(:), 0.95); A_b quantile(brightB(:), 0.95); end暗通道的计算很容易在Matlab里用ordfilt2实现这是最小滤波器的一种快速近似。local_min ordfilt2(grayPatch, 1, ones(blockSize))就能拿到局部最小值不需要写双层for循环。初学者最容易在这里写嵌套循环一张720p的图能跑半分钟换成ordfilt2后基本一秒钟内出结果。导向滤波的实现我直接写了一个标准函数没有依赖第三方工具箱核心代码大概二十多行。在Matlab里也可以直接调用imguidedfilter速度更快一些前提是安装了Image Processing Toolbox。做研究验证用内置函数没问题但如果你想彻底搞清楚每个环节的机理手写一遍导向滤波会对后面改进算法帮助很大。参数这块我把调试过程中比较有效的经验值整理了一下这些值在大多数真实水下照片上都能给出不错的结果。参数推荐值作用调参方向k_r 补偿增益0.6~0.9控制红色通道补偿强度图越蓝取值越大k_b 蓝通道压制0.4~0.6控制蓝色通道衰减图越绿取值偏大暗通道块半径8~15透射率估计的局部范围块太大有光晕omega 保雾系数0.9~0.95保留一定自然雾感调小更强烈t0 透射率下限0.2~0.3抑制暗部噪点噪声大调高导向滤波半径块半径x2透射率图平滑程度边缘保持不佳时调小epsilon1e-3导向滤波边缘权重过高会模糊边缘这里重点说一下t0这个参数。很多人习惯跟大气去雾一样设成0.1我一开始也是这么做的结果在水下比较暗的图片上暗部恢复之后会出现明显的色块噪点特别是一片深蓝的背景区域直接变成密密麻麻的红蓝噪点。后来把t0调到0.25虽然远处的物体轮廓没那么锐利了但整体画面的干净程度完全不是一个级别。这个参数宁可保守一点水下场景的噪声问题远比陆地场景严重。5. 效果怎么说主观视觉和客观指标搭配着看算法做出来到底行不行我的经验是主观视觉和客观指标两条腿走路不能偏废。主观上主要看三个维度颜色是否自然有没有变成诡异的品红色或荧光绿、背景层次是否分明近景清晰的同事远景不再糊成一团、物体边缘是否有明顯光晕。客观指标方面我主要用四个UCIQE水下颜色质量评估、UIQM水下图像质量度量、PCQI基于补丁的对比度指数和信息熵。UCIQE衡量的是色调均衡度、饱和度与对比度的加权组合UIQM侧重色彩恢复和清晰度PCQI专门用来衡量局部对比度提升效果。这四个指标各有所长但都没有脱离一个基本事实它们都是基于人类视觉系统的简化度量最终还要回到人眼主观评价上来。我曾经在一组真实水下礁石照片上做了对比实验分别测试了直方图均衡化、标准DCP去雾和本文这套波长补偿去雾方案。从指标数字看直方图均衡化在信息熵上提升很大因为它的本质是把灰度分布拉平但主观观感很糟糕整张图发灰发红远处物体反而更看不清了。标准DCP去雾在对比度指标上好看但色彩完全没有纠正仍然是一片蓝绿部分区域出现严重色偏。我这套方案在UCIQE和PCQI上都有显著提升信息熵虽然没有直方图均衡化那么夸张但颜色的自然度和细节的可辨识度是最高的。算法UCIQEPCQI信息熵主观观感原图0.420.586.7偏蓝绿对比度低直方图均衡化0.550.717.8颜色失真发灰标准DCP去雾0.490.826.9对比度好但色偏严重波长补偿去雾0.670.857.5色彩自然层次清晰这组数据不是用来证明我的算法最强而是想说明一个方法论指标和视觉必须互相印证。如果某个指标很高但图像观感很差基本可以判断这个指标被特定失真形态骗了。比如直方图均衡化的信息熵高就是因为引入了大量色噪声导致灰度分布的随机性增加这种信息量对图像的可用性没有任何帮助。另外要强调测试数据的选择。我见过不少人拿一组水下图片调好参数实验结果就全部用这一组图跑完了这其实缺乏说服力。水下图像的退化程度跟水质、深度、光照条件强相关同一套算法在清澈浅水中和浑浊深水中的表现可能天差地远。有条件的话尽量收集三类典型场景偏蓝的深海照片、偏绿的沿岸水质照片、浑浊的低能见度照片分别测试算法的鲁棒性。6. 常见问题、调试心得和后续可做的扩展这套方案实现起来不难但真正调到效果好并不容易有几个坑我替大家先踩了。第一个问题是背景光估计被白色物体欺骗。如果画面里有一块白色石头或白色鱼群四叉树搜索很容易把它们当成背景光因为它们的亮度高且方差小完全符合背景光的筛选条件。这样会导致背景光估计值偏高补偿量加剧最后整张图过曝偏白。解决办法是加一层判断对候选块做颜色饱和度检测纯白色物体在RGB三通道上的分布很均匀而水的背景光在深浅之间有明显差异把三通道均值比过于接近的候选块排除掉。第二个问题是补偿过度导致偏红。淡水场景和海水场景的衰减特性有区别海水对红光的吸收尤其剧烈补偿增益需要大一点但淡水中的红光衰减相对温和用一个针对海水调的k_r值去处理淡水照片红色通道会明显过冲整张图变成粉红色调。我现在一般会根据图内绿色通道的占比做一个粗略的水质估计绿通道均值占比高就适当降低k_r蓝色占比高就保持较强的补偿。这种自适应策略不算复杂但对效果提升非常明显。第三个问题是透射率图估计在浅色大面积区域比如沙地、白色鱼体出现光晕。水下场景里的浅色区域不满足暗通道先验假设透射率被低估恢复后边缘出现白色光带。缓解办法是加大暗通道块半径同时注意导向滤波的epsilon不要调太小否则边缘的透射率跳变太剧烈。第四个问题在处理大分辨率图像时暴露出来的运行速度偏慢。去雾那一串滤波操作在高分辨率下耗时明显我建议对超过两百万像素的输入先做降采样估计透射率再上采样回原分辨率这样能在几乎不影响视觉效果的前提下把耗时砍掉一半左右。导向滤波本身是O(N)复杂度但降采样能显著减小规模。我在实际调试中发现归一化暗通道计算里除以背景光这一步对背景光估计的准确度非常敏感。背景光稍微偏差一点归一化后的暗通道就会出现大面积极值区域透射率图直接失真。后来我在除以背景光之前给每个通道加了一个微小的扰动保护量就是代码里的1e-6虽然数值很小但能避免分母过小造成的数值不稳定在边缘区域扩散。如果后续你想在这个基础上继续深入有几个方向我觉得很有价值把这套算法的参数做成基于图像统计特征的自动估计现在是手动调参批量处理不同来源的图像时很不方便另一个方向是结合深度图做像素级的衰减估计波长补偿的精度会进一步提升但这需要额外获取景深信息把问题从二维图像增强变成三维场景复原复杂度会上一个台阶还有个我比较感兴趣的思路是把这个增强结果作为预处理模块接到目标检测或语义分割网络前面看增强是否能提升下游任务的精度这需要搭建一套图像处理加深度学习评价的完整框架。最后分享一个我在处理视频序列时的小技巧单帧增强时参数每帧单独估计结果往往会出现亮度闪烁。如果后续扩展成视频增强可以对背景光估计做时间域上的平滑也就是用指数滑动平均的方式更新帧间参数让增强效果在画面上稳定流动而不是一帧一帧跳变。这个技巧花了我不止一个晚上才摸出来参数稳定的视觉贡献通常比单帧的最优增强更重要。这个项目最让我有成就感的时刻是拿着处理前后的图片放在一起做目视对比时原本一片蓝绿水下暗色礁石上那些橘红色小鱼在增强后的画面里终于露出了本来颜色。那一刻你会真真切切感觉到手里这套数学公式和代码不只是在调参数拉直方图而是在对抗物理世界里的光学限制。把波长补偿和去雾这两条线拧成一股绳确实是在水下图像增强这条路上走得通的一条实在路子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑