1. 项目概述为什么要做水下图像增强以及这个项目的核心思路讲实话水下图像增强这个方向看着不算热门但真正接触过水下视觉任务的人都明白它是很多下游工作的前置条件。无论是水下机器人对目标物的识别、海底管道巡检、渔业资源调查还是水下摄影爱好者的后期处理第一关都是把原始的水下照片“还原”成接近真实色彩、细节清晰的图像。这个项目的标题非常直白——“基于波长补偿与去雾的水下图像增强研究Matlab代码实现”。拆开来看它做的是两件事一是波长补偿解决颜色偏蓝偏绿的问题二是去雾解决散射导致的对比度下降问题。最终产物是一份完整的Matlab代码实现能够对水下退化图像进行端到端的增强。我为什么觉得这个题目有意思因为很多课设、毕设级别的图像增强项目只会拿一个现成算法比如直方图均衡化套上去输出一张对比度拉高的图就完事。但这个项目里明确提到了“波长补偿”和“去雾”两个核心机制说明它不是简单调亮度而是根据水下光学衰减的物理特性去做补偿再结合大气散射模型的思想做去雾处理。这个思路在有物理依据的基础上做工程化效果上限要高得多。适合谁来看这个项目如果你是做图像处理相关课程设计、毕业设计的学生或者你是刚接触水下视觉的工程师想快速拿到一套可运行的Matlab代码作为基线方案那么这篇文章应该能帮上忙。我会把项目拆开讲清楚水下图像为什么退化、波长补偿到底在补偿什么、去雾模型怎么落到实现上、Matlab代码怎么写最顺手以及我实际调试中踩过的坑。2. 水下图像退化的根源物理模型决定了算法选型在讲算法之前必须先把“为什么水下图像会变差”这件事讲透。因为很多人直接跳进代码看那些参数调来调去却不知道每一步在干什么。其实水下图像退化几乎是数学公式可以描述的。2.1 波长选择性衰减蓝绿光的“统治”水下环境的第一个杀手就是水对不同波长光线的吸收能力不同。太阳光进入水面之后红色光波长约600-700纳米在不到5米左右的深度就会衰减得非常厉害橙色次之黄色再次之绿色光穿透力稍好蓝光约450-500纳米穿透力最强。这也是为什么浅水区拍出来的照片偏绿稍微深一点就整体偏蓝而红色物体在水下拍出来几乎成了灰黑色。波长补偿要做的就是根据光线在水下的传播特性对不同通道R、G、B施加不同的增益补偿。其中红色通道衰减最严重补偿量最大绿色通道次之蓝色通道相对好一些补偿量最小。如果只是简单地把三个通道乘以同一个系数那结果只是整体变亮并不能解决色偏反而会更假。所以这个项目把“波长补偿”单独拎出来说明它在通道处理上是有讲究的。实操中的常见方法是计算红色通道与绿色/蓝色通道之间的统计差异比如均值比值。如果红色通道的均值明显偏低就把红色通道按比值放大。但这种全局缩放有个风险如果图片里某一块本身就没有红色物体强行放大红色通道会造成该区域色彩溢出。所以更稳的方法是在Lab色彩空间或者HSV色彩空间里做调整只调整色相和饱和度相关分量而不单纯在RGB三通道上暴力缩放。不过为了简化实现很多Matlab代码采用白平衡思路的变体也就是灰度世界假设的改进版本——考虑到水中衰减不均加上深度信息相关的先验。2.2 散射与雾化效应水中颗粒物造成的“雾气”第二个杀手是水中的悬浮颗粒对光线的散射。入射光到达物体表面后会反射反射光在到达相机的路径上会被颗粒散射掉一部分同时周围环境光也会被散射进入相机视野。这些散射效果叠加在一起就让图像看起来蒙了一层“雾”对比度大幅度下降。这和大气中的雾霾成像模型本质上是一样的。大气散射模型通常写成I(x) J(x) * t(x) A * (1 - t(x))其中I(x)是观测到的退化图像J(x)是清晰图像想要恢复的目标t(x)是透射率图介质对光线的穿透能力A是全局大气光值水域中的背景光强。所以标题里说的“去雾”实际上是在套用这个模型来反演清晰图像J(x)。经典的去雾流程有三步估计全局背景光A、估计透射率图t(x)、通过公式反推出J(x)。暗通道先验算法就是这个思路的代表但它最初是为大气雾设计的直接用在水下场景会有问题——因为水下红色通道天然弱暗通道先验在水下经常失效。这个项目既然单独写了“波长补偿”和“去雾”两个模块大概率是先做波长补偿来修正色偏再在修正后的图像上做去雾两个步骤串成一条流水线。这也是水下增强的主流做法之一。3. 核心算法拆解波长补偿与去雾到底怎么落地这一节我把它拆成两部分讲先讲波长补偿的几种实现方式再讲去雾模型的工程实现。每个部分我会结合Matlab代码逻辑说明尽量让没有图像处理基础的读者也能看明白每一步在算什么东西。3.1 波长补偿的实现策略通道增益与色彩平衡波长补偿最朴素的实现是计算每个通道的均值然后把三个通道的均值拉到同一个水平上。比如原图三个通道均值分别为 r_mean、g_mean、b_mean那么红色通道的增益就是 g_mean / r_mean或者取三个通道均值的比例。这个思路在简单场景下有效但有一个致命缺陷如果图像中有大面积蓝色背景水下很常见那么蓝色通道均值天然偏高把红色拉到蓝色那么高红色通道会溢出。所以更合理的做法是引入一个“最大参考值”的概念。比如水下白平衡算法UDCP会先通过暗通道估计背景光再用背景光做归一化。还有一种实用的做法是限制补偿系数不允许任何一个通道的增益超过某个阈值比如1.5倍超出部分用曲线映射代替线性缩放。我在这个项目里采用的策略是先对R、G、B三个通道分别做高斯滤波得到低频分量计算低频分量的均值然后以绿通道为基准分别计算红、蓝通道与绿通道的比例把比例限制在合理范围比如0.6~1.8之间超出边界的用线性映射拉回最后将原图的三通道乘以对应增益再做一次饱和度微调。因为绿通道在水下通常介于红蓝之间作为基准比较稳定。蓝色通道因为衰减少一般增益接近1甚至小于1红色通道增益最大。这种策略比起无脑灰度世界假设要稳得多。如果是在Matlab里写核心代码可以精简成这样function enhanced wavelengthCompensation(img) % 提取通道 R double(img(:,:,1)); G double(img(:,:,2)); B double(img(:,:,3)); % 低频分量估计 h fspecial(gaussian, [15 15], 5); Rl imfilter(R, h, replicate); Gl imfilter(G, h, replicate); Bl imfilter(B, h, replicate); % 以绿通道为准计算增益 gRef mean(Gl(:)); gainR gRef / (mean(Rl(:)) eps); gainB gRef / (mean(Bl(:)) eps); % 限制增益范围 gainR min(max(gainR, 0.6), 1.8); gainB min(max(gainB, 0.6), 1.8); % 应用增益 Rc R * gainR; Bc B * gainB; Gc G; % 绿色不变 % 防止溢出 enhanced cat(3, Rc, Gc, Bc); enhanced uint8(max(min(enhanced, 255), 0)); end这段代码只是基础版本实际项目中我还会加一步饱和度补偿因为红色通道被放大后整体色彩容易发红需要适度降低饱和度来保持自然。不过核心思路就是通道增益加限制这两个点就是波长补偿的灵魂。3.2 去雾模型的工程实现暗通道先验与透射率修正去雾部分我建议依然采用暗通道先验Dark Channel Prior, DCP作为主框架因为它是目前工程落地最成熟、Matlab实现资料最多的方案非常适合做基线。暗通道先验的观察是在无雾的户外图像里绝大多数局部区域非天空区域的某个颜色通道会有极低亮度值这个最低值趋近于0。所谓暗通道就是对每个像素取RGB三通道最小值再对局部区域做最小值滤波得到的暗通道图。在有雾图像里暗通道值会显著抬高雾越浓抬得越高。基于这个差异可以估算透射率。但水下场景有个麻烦红色通道本来就衰减严重所以暗通道会天然偏低这与有雾图像暗通道偏高的假设相冲突。这就是为什么不能直接用原始DCP必须要先做波长补偿。补偿之后的图像红色通道被拉回正常水平暗通道才符合假设。所以这个项目里两步算法的先后顺序是有物理依据的不先做波长补偿直接去雾结果往往会发灰、发蓝因为透射率估错了。透射率估计公式为t(x) 1 - ω * min_y∈Ω(x) ( min_c ( I_c(y) / A_c ) )其中ω是保留系数一般取0.95用来保留一点雾气感避免增强过度。A_c是全局背景光通常取暗通道图中亮度最高的前0.1%像素对应原图的RGB值平均。求出t(x)之后恢复图像就是J(x) (I(x) - A) / max(t(x), t0) A其中t0是下界比如0.1防止除数为0导致噪点放大。Matlab里的简单实现可以这样写function [J, t] dehaze(img, isUnderwater) % img: 经过波长补偿后的double类型图像0-1归一化 % isUnderwater: 水下场景可做透射率修正 h fspecial(average, 7); % 窗口大小根据图像尺寸调整 dark min(img, [], 3); dark imfilter(dark, h, replicate); % 估计全局背景光 [hImg, wImg] size(dark); nPixels hImg * wImg; nTop max(floor(nPixels * 0.001), 1); darkVec reshape(dark, nPixels, 1); [~, idx] sort(darkVec, descend); topIdx idx(1:nTop); [rIdx, cIdx] ind2sub([hImg wImg], topIdx); A zeros(1,3); for channel 1:3 chVec img(:,:,channel); A(channel) mean(chVec(sub2ind([hImg wImg], rIdx, cIdx))); end % 估计透射率 A_rep reshape(A, 1, 1, 3); normImg img ./ A_rep; darkNorm min(normImg, [], 3); darkNorm imfilter(darkNorm, h, replicate); t 1 - 0.95 * darkNorm; % 水下修正如果红色通道透射率过低适当抬升 if isUnderwater tR t; tG t; tB t; % 这里可以做通道分离透射率估计篇幅所限不展开 end % 恢复 t0 0.1; t max(t, t0); J zeros(size(img)); for channel 1:3 J(:,:,channel) (img(:,:,channel) - A(channel)) ./ t A(channel); end J uint8(max(min(J, 1), 0) * 255); end上面这段代码是标准DCP流程。但注意我留了一个水下修正的接口实际中如果只用单一透射率图水下红色通道恢复会过度因为相比蓝色通道红色通道的透射率应该更低衰减更大。这时候需要做通道级透射率估计也就是对R、G、B分别计算暗通道和透射率。不过这会增加计算量对于视频序列还会产生闪烁问题。在静态图像任务里通道级透射率效果更好。4. 实操过程与Matlab代码实现细节从零搭一套完整增强流水线这一节我会给出完整可跑的Matlab代码并逐段讲解。为了让读者能直接复制运行我采用结构化脚本形式并且把两个核心算法封装成函数。我会讲清楚每个参数怎么调、为什么这么调以及我自己在实验过程中观察到的现象。4.1 完整工程目录与脚本组织建议的工程目录如下underwater_enhance/ ├── main.m ├── utils/ │ ├── wavelength_compensation.m │ ├── dehaze.m │ ├── post_process.m │ └── evaluate_metrics.m ├── data/ │ ├── input/ │ └── output/ └── results/main.m作为入口负责读图、调用预处理、增强、后处理和保存。模块化的好处是后面想替换算法或者调整参数不用动整个流程只改对应函数就行。4.2 主脚本main.m完整实现% main.m clear; close all; clc; % 读入图像 imgPath data/input/coral_underwater.jpg; % 替换为自己的图像路径 img imread(imgPath); if size(img, 3) ~ 3 error(仅支持RGB图像); end % 显示原图 figure(1); imshow(img); title(原始水下图像); % 第一步波长补偿 imgComp wavelength_compensation(img); % 第二步去雾增强 imgDehaze dehaze(double(imgComp) / 255, true); % 第三步后处理饱和度与对比度调整 imgFinal post_process(imgDehaze); % 显示中间结果 figure(2); imshow(imgComp); title(波长补偿后); figure(3); imshow(imgDehaze); title(去雾后); figure(4); imshow(imgFinal); title(最终增强结果); % 保存结果 imwrite(imgFinal, results/enhanced_result.jpg); % 计算评估指标PSNR / SSIM / UIQM metrics evaluate_metrics(img, imgFinal); disp(metrics);这个脚本结构非常清晰适合用来做实验。我自己在做类似课题时也会把原始图像、每步中间结果全部存下来方便对比调试。别嫌麻烦后期写报告的时候你就知道这些中间图的宝贵了。4.3 波长补偿函数优化版本前面给了一个简化版这里给一个实际项目里我常用的优化版本function result wavelength_compensation(img) % 将图像转成double且范围0-1 img double(img) / 255; % 分离通道 R img(:,:,1); G img(:,:,2); B img(:,:,3); % 计算每个通道的全局均值 R_mean mean(R(:)); G_mean mean(G(:)); B_mean mean(B(:)); % 计算各通道增益以最大均值的通道作为基准 means [R_mean, G_mean, B_mean]; [maxMean, maxIdx] max(means); gain means(maxIdx) ./ (means eps); % 限制增益范围防止过度补偿 gain min(max(gain, 0.8), 1.6); % 应用增益 R_new R * gain(1); G_new G * gain(2); B_new B * gain(3); % 校正后可能溢出做截断 enhanced cat(3, R_new, G_new, B_new); enhanced max(min(enhanced, 1), 0); % 可选饱和度微调 hsv rgb2hsv(enhanced); hsv(:,:,2) hsv(:,:,2) * 0.9; % 略微降低饱和度 result hsv2rgb(hsv); end这个版本的增益计算是把最大均值通道作为基准。水下场景中蓝色通道均值通常最高所以基准大概率是蓝色通道红色通道增益最大绿色通道稍微大一点。限制范围0.8到1.6是经验值如果你处理的图像很暗或者很亮这个范围需要自己测试调整。4.4 去雾函数完整版本含透射率优化再给出一个更完整的去雾实现里面加入了导向滤波的透射率细化。因为直接用最小值滤波得到的透射率图会有块状效应恢复出来的图像边缘会出现光晕。导向滤波是解决这类问题比较经典的手段好在Matlab R2015b之后的版本里有一个内置函数imguidedfilter可以直接使用。function [result, tRefined] dehaze(img, isUnderwater) % img: 读取图像double类型范围0-1 if size(img, 3) 1 img repmat(img, [1 1 3]); end [h, w, ~] size(img); % 暗通道估计 dark min(img, [], 3); winSize max(3, round(min(h, w) / 100) * 2 1); % 自适应窗口 se strel(square, winSize); dark imerode(dark, se); % 最小值滤波 % 全局背景光估计取暗通道前0.1%亮点的均值 nPixel h * w; darkVec reshape(dark, nPixel, 1); topN max(round(nPixel * 0.001), 1); [~, idx] sort(darkVec, descend); topIdx idx(1:topN); A zeros(1, 3); for c 1:3 imgCh img(:,:,c); A(c) mean(imgCh(topIdx)); end % 透射率估计 ARep reshape(A, 1, 1, 3); normImg img ./ (ARep eps); darkNorm min(normImg, [], 3); darkNorm imerode(darkNorm, se); t 1 - 0.95 * darkNorm; % 导向滤波细化透射率 grayImg rgb2gray(img); tRefined imguidedfilter(t, grayImg, NeighborhoodSize, [15 15], DegreeOfSmoothing, 0.001); % 水下场景的通道修正 if isUnderwater % 对红色通道的透射率做额外修正因为红光衰减更快 tRefined tRefined * 1.1; % 经验修正参数也可按需调整 tRefined min(max(tRefined, 0.2), 0.95); end % 恢复 t0 0.1; tRefined max(tRefined, t0); result zeros(size(img)); for c 1:3 result(:,:,c) (img(:,:,c) - A(c)) ./ tRefined A(c); end result max(min(result, 1), 0); end关于水下修正那一行tRefined * 1.1我解释一下水下红光的衰减比蓝绿光快得多如果透射率图是整体估计的红色通道的透射率会被高估看起来好像雾更薄导致恢复的红色通道过度增强。所以简单方法就是把透射率图整体调亮一些提高透射率下限或者单独对红色通道降低透射率。不过这个经验值在不同水质条件下差异很大这也是为什么水下增强算法没有万能解的原因之一。我在做这个项目时通常会准备几张不同水质的图片校准参数。4.5 后处理技巧让结果更自然去雾后的图像经常存在对比度不足或者颜色饱和度偏高的问题。后处理的目的就是让结果看上去“自然”。我常用的后处理流程有三步合并通道后转成HSV对V通道做自适应直方图均衡化adapthisteq提高局部对比度对S通道做轻微的压缩避免颜色过浓使用Lab空间中的L通道做对比度拉伸然后转回RGB。具体函数function out post_process(inImg) % inImg: uint8图像 hsv rgb2hsv(inImg); hsv(:,:,3) adapthisteq(hsv(:,:,3), NumTiles, [8 8], ClipLimit, 0.02); hsv(:,:,2) hsv(:,:,2) * 0.85; % 减小饱和度 inOut hsv2rgb(hsv); lab rgb2lab(inOut); L lab(:,:,1); Lmin min(L(:)); Lmax max(L(:)); L (L - Lmin) / (Lmax - Lmin) * 100; lab(:,:,1) L; out lab2rgb(lab); out im2uint8(out); end注意im2uint8会自动处理范围问题但如果你用的是老版本Matlab建议手动用max/min截断。5. 训练与验证方式用什么指标评价增强效果图像增强不像分类任务有唯一正确答案评价相对主观。但做研究必须要有量化指标否则报告里除了放对比图很难说服别人。这个项目我建议使用三类指标5.1 无参考评价指标水下图像没有标准参考图所以最常用的是无参考指标。UIQM水下图像质量度量专门为水下图像设计基于色彩度、清晰度和对比度三个维度综合得分越高越好。UCIQE水下彩色图像质量评估也是专门的水下指标衡量色偏、饱和度与对比度分值范围0到1。PCQI基于补丁的对比度质量指标。如果你有参考图像比如用合成退化方式生成测试数据可以用PSNR和SSIM。5.2 有参考评价指标PSNR是峰值信噪比数值越大表示失真越小SSIM是结构相似度越接近1越好。这两个指标在Matlab里都能直接调用函数。psnrVal psnr(enhanced, reference); ssimVal ssim(enhanced, reference);5.3 主观对比光有数据还不够至少要做一组主观对比图。我习惯的排版方式原图、波长补偿图、去雾图、最终增强图四张图放在同一行统一缩放这样才能直观看出每一步的作用。报告中可以配合柱状图展示UIQM的升高幅度。6. 常见问题与排查技巧实录Matlab调参实战这一部分我整理了我实际调试这个项目时遇到的几个经典问题每个问题都给出了排查思路和解决方法希望对做类似工作的你有参考价值。6.1 增强后图像整体偏红这是最常遇到的问题。波长补偿阶段红色通道增益过大把原本偏蓝的图像拉成了偏红。解决办法有三个方向降低红色通道增益上限比如从1.8降到1.2使用更平滑的补偿方法比如先把图像转换到Lab空间只调整a通道红色-绿色轴的分布而不直接操控RGB在去雾阶段调低水下修正系数让透射率不过度抬升。我在实际测试中发现偏红往往不是补偿阶段的问题而是去雾阶段透射率过小暗通道低估导致红色通道在恢复公式中被过度放大。所以优先调整去雾透射率的下限和修正系数比几遍调增益更有效。6.2 增强后图像出现黑斑或白斑这种斑点通常出现在暗光区域或者高光区域。主要原因是透射率图估计不准确该区域透射率过小导致除噪放大。解决思路提高透射率下限t0从0.1改到0.2甚至0.3增加导向滤波的平滑程度增大DegreeOfSmoothing对图像先做一次中值滤波再去估计暗通道减少噪声干扰。6.3 图像边缘有光晕光晕问题几乎可以断定是透射率图分辨率不足导致的。最小值滤波的窗口如果太大会在边缘产生块状感。解决方法是使用导向滤波细化透射率这是我强烈推荐的一种方法也是当前主流DCP代码几乎必带的一步或者改用双边滤波处理透射率图。在Matlab里imguidedfilter很好用如果不希望依赖工具箱自己实现导向滤波也不难网上有很多现成代码可以参考。6.4 背景区域颜色不正常比如大片蓝色海水区域被增强后变成紫红色。这是因为背景区域并非真正的“雾”而是水体的固有颜色。去雾算法默认把背景当作雾来去除自然会把蓝色压掉露出原本的“灰白色”。解决方法是把透射率下限设置得稍微高一些或者在估计背景光A时只选暗通道图中亮点集中区域的平均值而不是全图最大点。也可以引入景深先验对远处背景区域做更保守的增强。6.5 程序运行太慢如果是MATLAB原生循环处理每个像素会慢到无法接受。要尽量向量化用imfilter、imerode这类内置函数替代循环。暗通道估计用imerode非常快比逐像素遍历快几十倍。另外如果图片太大可以先用imresize缩小到0.5倍处理观察效果后再用全尺寸能大幅缩短调试时间。6.6 常见问题速查表现象可能原因首选解决方案整体偏红红色增益过大降低gainR上限整体偏蓝波长补偿不足提高gainR或加深透射率修正边缘光晕透射率过粗加导向滤波暗部噪点透射率下限过低t0提到0.2背景色彩怪背景光A估计不准改用加权平均运行太慢循环过多用内置滤波函数7. 项目扩展方向从静态图像到视频级增强的思考这个项目的完成度已经可以覆盖静态图像增强。但如果要做延伸我建议往两个方向想一是视频增强。水下视频逐帧增强容易产生闪烁因为每一帧的全局背景光和透射率估计都会变化。要解决闪烁需要在时间维度上做平滑比如对透射率图做时间域滤波或者把图像序列的参数估计结果做加权平均。这个方向如果做出来很能体现工程能力。二是深度学习的单向增强。用这个传统算法生成伪标签再训练一个轻量级深度网络去逼近增强结果。一方面速度可以更快另一方面伪标签有一定物理基础不是纯随机的数据增强。Matlab里也可以调用深度学习工具箱但实际训练还是建议切到PythonMatlab用于传统算法验证很合适。还有一个小扩展是目标检测的结合。增强之后接一个目标检测或分割模型量化对比增强前后检测精度提升了多少这也是论文里常见的实验设计很容易做出数据支撑。8. 最后谈一点我个人的实操体会做这个项目的过程中我最大的体会是水下图像增强没有“放之四海而皆准”的固定参数每个水体的光照、浊度、深度都不一样换一张图可能就要重新微调。我在经验中学到的是写代码时一定要把参数集中设计成可配置变量不要让参数散落在函数各处。这样当你拿到一张新测试图时只需要在配置文件里调几个值就能快速得到可接受的结果。另外一个心得是中间过程的可视化非常关键。不要只保存最终结果波长补偿后的图、透射率图、背景光估计结果这些都该输出出来。看透射率图能帮你快速定位问题是出在背景光估计还是透射率计算。很多新手遇到效果不好就盲目改参数其实看看透射率图问题往往一目了然。最后如果你是一个准备拿这个项目做毕设或者课设的学生我建议一定要在文档里把“波长补偿为什么先于去雾”这个问题讲清楚这是评委最喜欢问的切入点。然后就暗通道先验在水下场景的失效原因去做展开分析哪怕实现里只是做了修正也足以体现你对物理模型的思考深度。祝各位把这个项目跑通做好做得漂亮。
阅读完成 · 觉得有帮助?