☰
水下图像增强实战:波长补偿+暗通道先验的Matlab实现
2026/10/23 2:37:56 网站建设 项目流程

第一次拿到水下拍摄的照片时,我一度以为是镜头进水了——整张图蒙着一层墨绿色的雾气,珊瑚礁的轮廓几乎消失在背景里。后来才明白,这不是设备问题,是水本身在做“滤镜”。水对光有选择性吸收,红色成分在很浅的水深就被消耗干净,蓝绿光却能走得更远;悬浮颗粒又把剩余光线散射到各个方向,成像设备最终只能记录下偏蓝的底色和灰蒙蒙的层次感。

这个问题带来的直接后果是:颜色没法用,细节看不清。拿这种图去做目标检测或生物分类,算法基本只能靠运气。我做的这个项目就是围绕水下图像增强展开的,核心思路正是标题里那两条技术路线——先用波长补偿把被水体吸收掉的颜色通道拉回来,再用去雾算法处理散射造成的模糊。文章会给出完整的Matlab实现过程、每个模块的设计理由,以及我在实验过程中踩过的一些坑。

1. 水下图像的病根:光在水里衰减与散射有什么不同

1.1 吸收主导的颜色失真:为什么丢掉的总是红光

水下图像的颜色问题,根源在于水对可见光的选择性吸收。不同波长的光在水中的衰减系数差异很大,红光(600~700nm)衰减最快,绿光次之,蓝光最慢。常说的“水体呈蓝色”,就是因为蓝光穿透能力最强,而红光的能量在几米水深内就基本耗尽。

这个现象在增强算法里意味着两件事:

  • 红色通道的信息是最稀缺的,需要重点补偿;
  • 蓝绿色通道信息相对完整,但也会随着深度增加而逐渐衰减,不能完全当作“正确”的基准。

很多初学者以为水下图像偏蓝只是因为白平衡不对,做个自动白平衡就能解决。我一开始也这么干过,结果发现自动白平衡把整张图拉成了偏紫红色,远处区域尤其严重。根源在于:水下的场景根本不是“灰世界”,蓝光是背景光的主体,红光是信号最弱的成分,简单的白平衡假设不成立,必须针对通道衰减做差异化处理。

1.2 散射主导的雾化:对比度下降的来源

除了吸收,水中还充满了悬浮颗粒物,包括泥沙、浮游生物和有机碎屑。这些颗粒对光的作用分两种:

  • 前向散射:物体反射光在到达相机的路上被颗粒偏折,导致锐利的边缘变模糊;
  • 后向散射:环境光被颗粒反射进镜头,形成一层没有纹理的“光幕”,直接叠加在图像上。

这就是水下图像发灰、发雾的直接原因。对比度下降不是因为物体本身反差小,而是因为每个像素上都叠加了一层来自不同方向的背景散射光。去雾算法要做的事情,说白了就是把这一层“光幕”估算出来、减掉,再按距离补偿透射率衰减。

1.3 两条技术路线的分工与关系

把吸收和散射分开来看,技术路线就很清晰了:

  • 波长补偿处理的是“颜色失真”,目标是恢复通道间的能量平衡;
  • 去雾处理的是“对比度下降”,目标是去除空间上不均匀的散射光幕。

这两个问题不是孤立的。颜色越偏,暗通道估计出来的大气光就越偏向蓝绿色,去雾结果容易保留色偏;反过来,如果只做颜色补偿而不去雾,画面会从“蓝灰色”变成“色彩更饱满但依然模糊”的图像。所以完整的水下增强流程,应当把两者串起来做。

2. 波长补偿怎么做:从灰度世界到自适应红通道增益

2.1 先补色再去雾,顺序为什么几乎不能颠倒

我在项目里最先试的顺序是“先去雾、再颜色补偿”,理由是先去雾能提升对比度,再做颜色校正似乎更自然。实验对比下来,这个顺序确实能做,但效果不太理想。

原因在于去雾算法的第一步是估计大气光A。当图像严重偏蓝时,暗通道中最亮的像素往往来自蓝绿色区域,估算出的A_c三个分量本身就偏斜,透射率图的估计也会跟着偏移。结果就是:去雾后图像依然带着蓝绿色偏,而且因为透射率估计不准,容易出现局部光晕。反过来,先做波长补偿,先把三通道的统计特性拉得比较均衡,再去估计大气光,透射率图就稳定得多。

从工程角度看,调参也更容易:颜色补偿的参数只有一个增益范围,先去雾时颜色和透射率互相纠缠,调起来很折磨人。

2.2 灰度世界假设的Matlab实现

波长补偿的实现方式很多,最基础也最容易上手的是灰度世界假设。它认为自然场景中三个颜色通道的像素均值应该接近中性灰,所以各通道的补偿增益就等于均值反比。

function I_out = wavelength_compensation(I_in) % 基于灰度世界假设的波长补偿 I = im2double(I_in); R = I(:,:,1); G = I(:,:,2); B = I(:,:,3); mean_r = mean(R(:)); mean_g = mean(G(:)); mean_b = mean(B(:)); % 以绿色为基准,计算红、蓝通道的增益 gain_r = mean_g / (mean_r + eps); gain_g = 1.0; gain_b = mean_g / (mean_b + eps); % 增益限制,防止过激补偿 gain_r = min(gain_r, 1.8); gain_r = max(gain_r, 0.8); gain_b = min(gain_b, 1.5); gain_b = max(gain_b, 0.7); I_out = cat(3, R * gain_r, G * gain_g, B * gain_b); I_out = min(I_out, 1.0); end

这段代码背后有个很实际的考虑:红通道的均值在严重偏蓝的图像里可能只有0.05左右,按照灰度世界算出的增益会非常大,乘上去之后所有偏红的区域都会溢出。所以必须把增益限制在合理范围,宁可补不到位,也不要补出满屏的红噪声。

2.3 红通道增益要限制,避免补出噪声

我在早期版本里没有设计增益上限,直接让我手头一张水箱测试图变成了一张偏红的图,暗部全是紫色斑块。问题就出在红色通道的低灰度区域原本的信噪比就很低,过度增益把这些噪声成倍放大。

后来我改成了自适应方式,不再只依赖全局均值,还加入百分位拉伸:

function I_out = red_channel_boost(I_in) I = im2double(I_in); R = I(:,:,1); % 以红通道的5%~95%百分位为低高端点,线性拉伸 low_r = prctile(R(:), 5); high_r = prctile(R(:), 95); R_stretched = (R - low_r) / (high_r - low_r + eps); R_stretched = min(max(R_stretched, 0), 1); % 再叠加上一步的全局增益 mean_r = mean(R_stretched(:)); gain_r = min(0.5 / (mean_r + eps), 1.8); I_out = I; I_out(:,:,1) = R_stretched * gain_r; I_out(:,:,1) = min(I_out(:,:,1), 1.0); end

选择5%和95%分位数而不是0%和100%,是为了避开极亮和极暗的离群像素。水下的高光区域往往来自鱼鳞反光、气泡或者白色珊瑚,如果让这些点决定拉伸范围,会导致整体亮度被压得很暗。

2.4 极浑浊水体下的特殊处理

还有一种情况值得单独说明:非常浑浊的水体里,红色通道可能几乎全是0,均值接近零。此时无论灰度世界还是百分位拉伸,都会因为分母过小而失效。我的处理方式是先判断红通道均值是否小于整体亮度均值的十分之一,如果满足,就直接把红色通道替换为绿色通道和蓝色通道的加权混合,同时降低增益强度。

这种做法的逻辑是:反正红色信息已经丢失,强行补偿带来的只有噪声,不如用蓝绿通道推测一个“合理的红色通道”。实际效果虽然不能恢复真实颜色,但至少能让画面不出现大面积紫色噪点。

3. 去雾模块:暗通道先验在水下的关键修正

3.1 大气散射模型与暗通道先验

去雾模块的理论基础是大气散射模型:

I(x) = J(x) * t(x) + A * (1 - t(x))

其中I是观测到的图像,J是清晰图像,A是大气光,t(x)是透射率。t(x)越接近1,说明光线衰减越少,画面越清晰;越接近0,说明场景被雾覆盖得越严重。

暗通道先验的核心观察是:在自然界的无雾图像中,绝大多数局部区域内至少有一个颜色通道的值非常低、接近零。所以可以通过暗通道估算透射率:

J_dark(x) = min(min(J_c(y))),y在x的邻域内

估计透射率的近似公式为:

t(x) ≈ 1 - ω * min_c( I_c(y) / A_c )

其中ω一般取0.95,目的是保留一部分残余雾感,避免图像被处理得过于锐利、不自然。

3.2 大气光估计的水下修正:不要只找最亮像素

原有暗通道先验在处理普通雾天图像时,会从暗通道图里挑前0.1%的最亮像素,然后取这些像素在原始图中的最大值作为A。这个做法在水下场景里很容易翻车。

原因是水下图像的最亮像素可能来自白色珊瑚、鱼鳞反光甚至气泡,这些是高反射物体,而不是水体散射导致的“大气光”。如果把它们当成A,透射率的估计就会在物体边缘出现异常,结果在反光区域周围形成一圈假轮廓。

我做了一个小修正:先按暗通道排序选出候选像素,然后在这部分像素中,不用最大值,而是用99%分位数来估计每个通道的A。同时增加一个约束条件:候选区域中蓝色通道亮度与整体亮度的比值不能超过1.2倍。如果超过,说明该区域水体主导成分过强,也会对A的估计造成干扰。

function A = estimate_atmospheric_light(I, Jdark) [h, w, ~] = size(I); num_pixels = h * w; num_candidates = max(round(num_pixels * 0.001), 1); Jdark_vec = Jdark(:); [~, idx] = sort(Jdark_vec, 'descend'); candidate_idx = idx(1:num_candidates); A = zeros(1, 3); for c = 1:3 channel = I(:,:,c); cand_vals = channel(candidate_idx); % 用99%分位数代替最大值,避免反光点干扰 A(c) = prctile(cand_vals, 99); end end

这个改动看起来很小,但对结果影响很大。我第一次用原版估计A,结果白色珊瑚附近的区域被去雾算法错误地增强成发暗的色块,后来换了分位数估计,这种假轮廓就消失了。

3.3 透射率估计、导向滤波细化和下限保护

透射率的计算思路和标准暗通道先验类似,但在通道归一化时要用修正后的A。具体做法是:每个通道除以对应的A_c,然后取局部窗口内的最小值作为粗糙透射率。

function t = estimate_transmission(I, A, omega, patch_size) I_norm = I ./ reshape(A, 1, 1, 3); dark_current = min(I_norm, [], 3); % 用形态学腐蚀实现局部最小值 dark_local = imerode(dark_current, strel('square', patch_size)); t = 1 - omega * dark_local; t = min(max(t, 0.1), 1); % 下限保护 end

这里有两个容易忽略的细节。第一个是局部最小值窗口的选择:窗口太小,透射率图会出现大量空洞;窗口太大,透射率过度平滑,边缘细节丢失。我测试下来,patch_size取15是比较稳的起点,针对大分辨率图像可以适度提高。

第二个细节是透射率下限。t的下界我固定在0.1。原因从模型公式就能看出来:在恢复清晰图像时,有一步是(I - A) / t,如果t趋近0,这一项会爆炸式放大,噪声会盖过所有细节。这也是很多水下增强结果看起来“脏脏的”主要原因。

大气光的估计相对稳定,透射率图却需要进一步细化。直接用局部最小值窗口会导致明显的块状伪影,最常用的解决方式是导向滤波。Matlab内置的imguidedfilter可以直接调用,引导图用原图的灰度版本即可,保边效果好,计算速度也能接受。

gray = rgb2gray(I); t_refined = imguidedfilter(t, gray, 'NeighborhoodSize', 25, 'DegreeOfSmoothing', 0.1);

3.4 清晰图像恢复

得到细化的透射率后,最终恢复公式为:

J_c(x) = (I_c(x) - A_c) / max(t(x), t0) + A_c

在代码里,我习惯把除法改成逐像素操作,并且对G和B通道做一次轻微的Gamma校正,让画面明暗更自然:

function J = recover_scene(I, A, t_refined, t0) t_t = max(t_refined, t0); J = zeros(size(I)); for c = 1:3 J(:,:,c) = (I(:,:,c) - A(c)) ./ t_t + A(c); end J = imadjust(J, [0 0.01; 0.99 1], []); end

4. 完整的增强流程与效果评价

4.1 总处理流程与Matlab主函数

把两个模块串起来的整体流程并不复杂:读取图像 → 波长补偿 → 暗通道估计 → 大气光估计 → 透射率估计与细化 → 场景恢复 → 后处理。

function result = underwater_enhance(img_path) I = imread(img_path); I_comp = wavelength_compensation(I); % 计算暗通道 Jdark = dark_channel(I_comp, 15); % 估计大气光 A = estimate_atmospheric_light(I_comp, Jdark); % 估计透射率 t = estimate_transmission(I_comp, A, 0.95, 15); t_refined = imguidedfilter(t, rgb2gray(I_comp), ... 'NeighborhoodSize', 25, 'DegreeOfSmoothing', 0.1); % 恢复图像 J = recover_scene(I_comp, A, t_refined, 0.1); result = J; end

这段主函数已经在我的测试集上跑通了大部分场景。需要提醒的是,Matlab自身的uint8转换机制有时会让人踩坑:如果I_comp是double类型但值域在0~1,那么后续的颜色补偿和暗通道计算都没问题;但如果中途不小心转回了uint8,除法运算会自动取整,亮暗层次会有损失。处理过程中尽量统一使用double和0~1范围。

4.2 无参考评价指标怎么选

水下图像增强的效果评价,不能只靠肉眼。用PSNR和SSIM需要一张“无失真参考图”,但真实水下场景根本没有标准答案,只有受控的合成数据集才能算这些指标。

实际项目中更常用的是无参考指标:

  • UCIQE:综合色彩饱和度、色彩浓度和亮度对比度,专门面向水下彩色图像;
  • UIQM:基于色偏、清晰度和对比度的加权组合,也是水下场景常用的。

我在项目里以UCIQE为主,因为它对颜色恢复程度更敏感。波长补偿做得好不好,UCIQE的变化能直接反映出来,而只看PSNR的话,颜色轻微的偏斜有时候反而会得高分。

4.3 我的测试结果与参数基线

以我手头这批样本为例,它们的来源混杂了实验室水箱、近岸浅水区和公开图集,水质差异比较大。处理前的UCIQE大概在0.28到0.36之间,经过波长补偿和去雾处理后,普遍能到0.45以上,视觉效果上最明显的变化是颜色不再发蓝、远处细节能看清轮廓。

下面是给后续复现的人参考的参数基线:

参数推荐值作用
patch_size15暗通道窗口大小,控制透射率平滑程度
omega0.95保留残余雾感,数值越小去雾越强
t00.1透射率下限,防止噪声被放大
红通道增益上限1.8防止红色通道补偿过度
导向滤波窗口25透射率细化的平滑半径

这套参数在轻度浑浊、中等浑浊的水下图像上表现稳定。如果遇到背景以深海蓝色为主的图像,omega要适当降到0.90左右,因为蓝色背景的暗通道值比自然场景更大,默认参数去雾后会偏暗。

5. 调试时踩过的坑和可扩展方向

5.1 透射率下限调太低,噪声直接炸开

我最早为了追求“更明显的去雾效果”,把t0调成了0.01。结果暗部区域出现了密密麻麻的彩色噪点,特别是红色噪声,因为红色通道经过波长补偿后信噪比最低,一旦除以一个很小的透射率,噪声就被放大成颗粒状色块。

后来我认真看了透射率分布图才发现,暗部区域的t往往在0.2上下,低于0.1的像素占比很低。设置t0为0.1既能保护这些区域,又不会明显影响整体去雾效果。这个参数不是固定不变的,如果图幅很大、暗部占比高,建议提高到0.15。

5.2 蓝色主导场景里白平衡补红过头的问题

灰度世界假设在水下并不是万能的。当场景本身就是大面积的蓝色海水,比如深海环境,几乎没有暖色物体,灰度世界会认定“蓝色通道偏大”,于是把蓝色通道压下去、红色通道拉上来。

最后输出的图像变成了紫色调,看起来很奇怪。针对这类场景,我改用红色通道的自适应拉伸方案,不再对蓝绿通道做大幅度调整,同时把红通道增益上限从1.8降到1.4,效果会自然很多。

判断场景是否属于“蓝色主导”,比较简单的方法是看颜色补偿前后的色相分布变化。如果补偿后的图像红色占比异常增高,就说明这个场景不适合灰度世界假设。

5.3 从静态图像扩展到视频的平滑处理

静态图像的处理结果看起来不错,但直接按帧处理视频的话,问题就来了:水体流动、相机抖动都会让暗通道和透射率估计逐帧跳动,视频看起来会闪烁。

解决思路是加入时域平滑。对透射率图做滑动平均:

t_smooth(t) = α * t_now + (1 - α) * t_prev

α取0.2到0.4之间比较合适,α太小会导致响应迟钝,运动物体容易拖影;α太大又失去了平滑效果。大气光A也应该做类似的时间低通处理,否则水面的光线变化会让图像亮度周期性跳动。

5.4 性能优化:暗通道计算别用像素循环

第一次写暗通道的时候我用的是三层for循环遍历窗口,一张1080p图像跑了几十秒,项目进度直接卡住。后来改用形态学腐蚀,一次操作就完成了窗口最小值计算,速度快了几个数量级。

如果要在嵌入式设备或实时环境跑,还可以先把图像降采样计算透射率,再上采样回原分辨率用于恢复。透射率本来就是低频信息,缩小到四分之一计算,质量几乎不受影响。

我的体会

如果让我重做一遍这个项目,我会先从一张最典型的偏蓝图像开始,把波长补偿调出一个稳定的视觉结果,再去动去雾参数。原因很简单:去雾的透射率估计很大程度上依赖颜色补偿后的通道均衡状态,先稳定了颜色,后调透射率,调试顺序本身就能避免很多鬼影和光晕问题。

另外一个小技巧是:调参时尽量用缩略图和实时预览窗口,把中间结果(暗通道、透射率、大气光)可视化出来。只看最终图,出了问题根本定位不到是大气光估计错了,还是透射率下限不合适。把A和t图打开看一眼,很多疑惑当场就能解开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询