水下相机拍回来的素材几乎每一帧都自带“雾霾蓝绿滤镜”——颜色发闷、对比度低、远处一片混沌这在水下机器人视觉、养殖监测、水下考古这些实际项目里都是绕不开的老大难。我之前在做某水下视觉项目时也卡在这块很久最后用“波长补偿 去雾”的组合思路在Matlab里搭出了一套可复现的增强流程效果比单纯套用陆地去雾算法要靠谱得多。这篇文章就把这套研究的思路、原理推导、Matlab实现细节和踩坑记录完整拆出来给正在做水下图像增强相关毕设、课设或者工程预研的朋友一条可以直接上手的路径。1. 先把问题看清楚水下图像到底是怎么“坏掉”的很多人上来就翻算法、找代码但我建议先花半小时搞清楚退化机制。搞懂“图像为什么变这样”远比你多调几个参数值有用得多。水下图像不是简单的“偏蓝”或者“变模糊”它的退化分为两条独立的链路颜色失真和对比度丢失。这两条链路背后是物理光学的问题处理策略也因此完全不同。1.1 波长衰减为什么红通道总是“先没了”光在水中的衰减与波长强相关。水的吸收系数对不同波长差异很大红光约620-750nm波长较长能量低很快被水吸收而蓝绿光约450-550nm穿透能力最强。换句话说你在水下3米处拍摄红色已经丢失了大量能量到了10米深度红色基本“归零”。所以水下照片普遍偏青蓝绿色不是因为白平衡没调好而是红色分量物理上就少。这个现象直接决定了算法设计的第一原则RGB三通道不能一视同仁地处理。你在陆地图像上用灰度世界假设那套把三通道均值拉到一致往往会把红通道拉爆出现一片紫色的伪彩块。原因很简单红通道信息本来就弱强行线性拉伸容易把噪声也放大。那怎么办正确的思路是“按场景距离增量补偿”。离相机近的物体红色损失少远处则损失大。严格做法需要深度图实践中可以近似为对红通道做局部均值差补偿再结合去雾后的透射率图做距离权重调节效果已经足够用。1.2 散射与悬浮粒子模糊和“面纱”的制造者除了吸收水中的悬浮颗粒泥沙、浮游生物、微小气泡会散射光线。散射分两类前向散射让目标边缘扩散、细节模糊后向散射则像一层发光的“雾帘”直接叠加在成像信号上拉低对比度。这在浑浊水域尤其严重表现为图像整体发灰发白像蒙了一层纱。水下图像增强的“去雾”部分本质上就是要去掉这层后向散射形成的背景光干扰。这和雾天图像的退化模型在数学上高度相似所以很多陆地去雾算法比如暗通道先验可以被迁移过来但参数和细节必须针对水下环境修正——这一点后面详细说。1.3 统一退化模型一张公式看懂水下成像链无论波长衰减还是散射它们都叠加在同一光学成像过程中。现在主流的建模方式是在大气散射模型基础上做扩展写成I_c(x) J_c(x) * t_c(x) A_c(x) * (1 - t_c(x))其中 c 是通道索引R/G/BI 是观测到的退化图像J 是待恢复的真实辐照度A 是背景光t 是透射率。透射率与距离、衰减系数的关系是t_c(x) exp(-β_c * d(x))这里的 β 是各通道衰减系数红通道最大蓝绿通道较小。正是这个 β_c 的不同导致了颜色失真和模糊的空间分布差异。一句话归纳要恢复水下图像就要尽可能同时估计 A、t_c 和 J_c 三个未知量。波长补偿管的是颜色维度的 A 和通道差异去雾管的是透射率和背景光维度。两者配合正好从两个方向上补齐信息。2. 算法核心波长补偿和去雾如何配合着打这一节进入核心思路。很多刚接触的读者以为这类算法就是“先做个白平衡再套个去雾”实际远没那么简单。组合的顺序、补偿量如何控制、去雾时暗通道怎样定义这些细节直接影响最终质量。2.1 波长补偿不是简单白平衡而是“按通道找回能量”水下图像各通道能量分布差异很大。常规做法是取绿通道或蓝通道作为基准计算目标通道与基准通道的均值差/中值差然后补偿回红通道。代码上大致是comp mean_g - mean_r; % 红通道平均损失了多少 comp max(comp, 0); % 防红通道反而更强 J_red min(img(:, :, 1) comp * alpha, 1); % alpha通常0.6-0.9alpha 就是补偿强度系数。为什么不是 1因为全量补偿会过度增强噪声且会破坏物体本身的色度。我实测下来alpha 取 0.7 左右比较稳能让照片恢复温暖色调又不至于发紫。另一个更细致的做法是分块补偿。将图像分成若干小网格每块独立计算红通道相对绿通道的缺失量补偿后再进行插值平滑。这样能处理同一画面内近处和远处目标红色损失不一致的问题。像某公开水下图像测试集里的招牌“深海鱼”图远距离红色鱼身和近距离蓝色背景同时出现时分块补偿的效果会明显优于整图统一补偿。2.2 暗通道先验引入水下别照搬要改两处暗通道先验Dark Channel Prior是经典陆地去雾方法核心假设是无雾自然图像局部小方块内至少有一个通道的亮度很低。基于此用最小滤波估计透射率和背景光。这个思路在水下照样成立但有两点必须改第一水下场景的背景光不是天空而是蓝绿色的水体后向散射光。所以背景光 A 的估计不能像陆地上那样简单取暗通道中最亮像素而是要在远离拍摄目标的区域中选。实践中我常加入一个简单的四叉树空间搜索先把图像分成四块选亮度方差最小的区域递归划分最后在该区域取蓝绿通道的高亮度像素均值作为 A。这能有效避免把白色鱼身或人工光源误判成背景光。第二暗通道本身可以在补偿后的图像上计算。因为红通道原本几乎无能量原始图像的暗通道会被红通道主导导致透射率估计错误。对红通道补偿之后再做暗通道得到的透射率图才更稳定。这也是这个算法强调“先补偿、后去雾”的原因。2.3 算法整体框架一张完整的处理链路我最终在项目中落地的流程如下供直接参照输入图像 → 转 double 类型并归一化 → 通道分离 → 波长补偿R通道按绿通道均值差补偿 → 计算暗通道 → 四叉树搜索估计背景光 A → 按通道估计透射率 t → 导向滤波平滑透射率 → 根据成像模型恢复图像 → 可选CLAHE或饱和度校正 → 输出增强图这套流程的好处在于每一步的输出都是下一步的输入逻辑清晰便于逐段调试。你可以在任意一步后输出中间结果查看能快速定位问题出在哪个环节。我在项目里就是这么干的单独把补偿后的图和透射率图、背景光值打印出来一眼就能判断算法走到了哪一步。3. Matlab完整实验流程与代码解读这一节是实操性最强的内容。我默认你已经有Matlab的Image Processing Toolbox基础如果完全没有强烈建议先跑通一个最低限度的Demo再继续。3.1 实验准备与数据选择建议Matlab版本建议用 2020 之后的版本图像处理工具箱中的 imfilter、strel、guidedfilter 等函数都齐备。测试素材方面建议准备三类图浅水区的偏绿素材、深水区的偏蓝素材、浑浊水域的低对比素材。不要只用一张“网红图”调参那样的结果没有泛化性。我自己的习惯是以 256 到 512 像素宽的图像先做算法验证参数稳定后再跑 1080p 原图。这样节省调试时间也能更快观察到参数变化的效果。3.2 核心代码实现细节解析下面给出主流程代码关键是每个模块我都标出了参数含义和调整依据。% 读入和归一化 I imread(underwater.png); I im2double(I); % ---------- 波长补偿 ---------- R I(:, :, 1); G I(:, :, 2); B I(:, :, 3); % 计算补偿量以分为单位的均值差 mean_R mean(R(:)); mean_G mean(G(:)); diff_RG mean_G - mean_R; % 补偿红色分量alpha为强度系数 alpha 0.75; R_comp R alpha * diff_RG; R_comp min(R_comp, 1); % 防过曝 I_comp cat(3, R_comp, G, B); % ---------- 暗通道计算 ---------- windowSize 15; dark min(min(I_comp, [], 3), ordfilt2(min(I_comp, [], 3), 1, true(windowSize)));暗通道计算这段容易踩坑。Matlab 没有直接的局部最小值函数常用 ordfilt2 来做。上面这行的写法是把三通道先取最小值再对灰度图做窗口半径为15的最小滤波。窗口大小很关键太大容易丢失细节边缘太小又会令透射率估计不连续。15 是对 512 宽图像的经验值如果你的图像分辨率更高可以将其调到 20-25 范围。背景光估计我强烈建议用四叉树而不要用暗通道前 0.1% 像素的像素值直接平均。水下场景的小块白色区域太多暗通道法非常容易把高光区域当成背景光。% ---------- 四叉树搜索背景光 ---------- I_gray rgb2gray(I_comp); finalRegion [1, 1, size(I_gray, 2), size(I_gray, 1)]; for iter 1:6 x finalRegion(2); y finalRegion(1); w finalRegion(3); h finalRegion(4); quad {I_gray(y:yh/2, x:xw/2), I_gray(y:yh/2, xw/21:xw), ... I_gray(yh/21:yh, x:xw/2), I_gray(yh/21:yh, xw/21:xw)}; avg cellfun((block) mean(block(:)), quad); [~, idx] min(avg); switch idx case 1, finalRegion [x, y, w/2, h/2]; case 2, finalRegion [xw/2, y, w/2, h/2]; case 3, finalRegion [x, yh/2, w/2, h/2]; case 4, finalRegion [xw/2, yh/2, w/2, h/2]; end end A mean(mean(I_comp(finalRegion(2):finalRegion(2)finalRegion(4), ... finalRegion(1):finalRegion(1)finalRegion(3), :))); % ---------- 透射率估计与恢复 ---------- omega 0.9; t 1 - omega * (dark / A(1)); % 简化用背景光均值 t max(t, 0.2); % 防止过小导致噪声放大 t imguidedfilter(t, I_gray, DegreeOfSmoothing, 0.01); % 导向滤波 J zeros(size(I_comp)); for c 1:3 J(:, :, c) (I_comp(:, :, c) - A(c)) ./ max(t, 0.2) A(c); end J max(min(J, 1), 0);代码中 t 的计算其实是简化版严格来说应该对每个通道分别估计因为各通道透射率衰减系数不同。但实际工程中发现分别估计容易产生色彩噪声视觉上反而不如单通道灰度透射率共用稳定。这是“理论上对工程上要做取舍”的典型例子。3.3 结果评价指标别只靠肉眼和感觉做研究不能光“看着好”要落到指标上。我常用的两个水下原生指标是 UIQM水下图像质量评价和 UCIQE水下彩色图像质量评价都有开源Matlab实现可以在论文里引用。另外 PSNR 和 SSIM 也建议算一下虽然它们需要参考图像但你如果有干净版本比如合成退化就能用上。一套完整的实验至少要有输入图、补偿中间图、透射率图、最终增强结果、UIQM/UCIQE数值表格。我自己的经验是UIQM 提升 20% 以上算合格提升30% 以上就是比较显著的改善。3.4 参数调优策略颜色优先对比度其次调参也有顺序讲究。我建议先固定去雾参数omega0.9t00.2单独调 alpha 补偿系数把颜色调到“看起来准确”然后再保持 alpha 不动调 omega 和窗口大小去优化对比度。不要同时改多个参数否则出了问题都不知道是哪一步引起的。另外观察中间结果特别重要。当你发现最终图出现紫色块时大概率是 alpha 调太高了如果图像灰蒙蒙但颜色正常那就是 omega 太小或背景光 A 位置选偏了。每一层都看得明白才谈得上调优。4. 实操中常见的故障与排查实录这部分价值可能比前面的代码框架更高因为是我在反复实验中被坑出来的经验。每一个现象都对应一个具体的调试方向。4.1 红通道补偿过度图像发灰发紫最典型的翻车现场补偿后整幅图蒙上一层紫色或者红色区域的饱和度异常高到失真。原因就是 alpha 值太大或者补偿后做了两次线性拉伸。我的处理办法是将 alpha 控制在 0.5-0.8 之间先看直方图再决定。补偿后检查红通道直方图的 99% 分位如果接近 1说明大量像素过曝。改用中值差替代均值差计算补偿量能降低对极端高亮噪声的敏感度。另外有个浅水脏水场景的特例水中其实含有大量黄色粒子这种场景下红通道并不总是“缺失”的如果盲目补偿会让画面发灰。这时候可以考虑根据绿色通道均值自适应决定要不要补偿——如果画面本身已偏黄绿补偿强度就要调低。4.2 透射率图出现白边或光晕表现为目标物体的边缘出现一圈“发光”感像是包围在雾里。产生原因是暗通道的窗口太小或透射率估计不够平滑导致边缘处透射率突变。解决办法是增大暗通道窗口尺寸比如从 15 调到 25保证使用了导向滤波imguidedfilter来平滑透射率图检查是否需要限制透射率的最小值。我通常设为 0.1-0.3太低的话噪声会爆炸。4.3 背景光估计漂移恢复结果整体偏色如果你在四叉树搜索后发现最终结果整体偏黄或者偏灰大概率是选择的背景区域本身就有偏色例如画面里有黄色礁石区。一个补救措施是在四叉树最终区域选取时不要取该区域三通道均值作为 A而是取该区域中亮度最高 1% 像素的均值。这样更能代表远处水体的散射光颜色。调到这里就该意识到背景光 A 的估计质量几乎决定了整个去雾结果的色调走向。4.4 Matlab 运行慢与内存占用问题处理一张 1080p 图像时暗通道计算和导向滤波的耗时还是能感觉得到的。如果嫌慢有几个实用技巧先用 imresize 将图像缩小到 512 宽标定好参数后再跑原图尽量用矩阵运算替代循环避免逐像素 for导向滤波的 DegreeOfSmoothing 不要设太大否则时间直接翻倍如果连续处理一批图像用预处理脚本统一参数并批处理省去重复调参的时间。还有一个隐藏性能瓶颈ordfilt2 在旧版Matlab上对大图很慢可以用 imerode对灰度图像做局部最小值替代速度能提升不少。说实话这类水下图像增强算法做到最后拼的往往不是“谁的公式更花哨”而是对每个中间环节的判断力——什么时候该补偿、补偿多少、背景光取哪里、透射率下限设多少这背后全是物理和经验。波长补偿加去雾这个组合在多数水下素材上都能给出比单一方法好看得多的结果而且每一步在Matlab里都能可视化出来特别适合做学习验证和改进。后续如果想继续深入可以考虑在补偿阶段引入深度估计做更精细的波长衰减补偿或者把增强结果接入目标检测评价其对识别率的增益。但先把这套流程跑通、跑透、能自己判断好坏是更值得也更基础的一件事。
阅读完成 · 觉得有帮助?