
简介一套基于暗通道先验的水下图像颜色校正仿真资源面向水下图像处理研究者与MATLAB初学者用于解决水下拍摄图像偏蓝偏绿、对比度低等颜色失真问题。资源实现暗通道先验、折射率计算、颜色校正三个核心子模块输入水下退化图像后可得到自然准确的恢复结果并附带图像质量评价过程帮助理解从参数估计到效果验证的完整链路。压缩包共16个文件以6个MATLAB脚本.m为主含核心算法与主测试程序配5张原始与校正对比图.jpg、2张算法流程图.png以及2份PDF文档和1个Markdown说明便于对照学习。资源包仅2.35MB轻量易用已有800人学习。读者可基于源码快速复现实验替换自己的水下图像进行测试也可结合文档理清暗通道透射率计算与自适应颜色校正的衔接细节是入门水下图像处理的实用参考。1. 水下暗通道不是简单的去雾underwaterCorrection 到底在修正什么拿到一张水下照片最先看到的是蓝绿色罩层、几乎消失的红色通道以及被散射光吃掉的对比度。把陆地暗通道去雾代码原封不动搬过来恢复结果通常偏紫红边缘还会出现灰黑色光晕比原图更不自然。原因是陆地去雾假设透射率与波长无关而水对不同波长的吸收系数差异极大红光衰减速度远高于蓝绿光。underwaterCorrection 这套 MATLAB 仿真资源把水下图像处理拆成暗通道先验、折射率计算、颜色校正三个子模块最后用图像质量评价验证效果。它适合两类人一是要在 MATLAB 里复现论文方法的算法工程师二是准备把水下图像处理原型迁移到 C 或 Python 管线的开发。这套代码真正有价值的地方在于展示了暗通道在水下哪一步开始失效以及 colorCorrect.m 里哪些参数不能随意改。2. 从陆地暗通道到水下透射率Idark.m 与 getTransmissivity.m 的差异点2.1 暗通道先验在陆地和水下分别意味着什么暗通道先验的表达式是Jdark(x) min_{y∈Ω(x)}(min_c J_c(y)) ≈ 0意思是自然清晰图像的局部窗口里总有一个通道的像素值接近零。成像模型写作I(x) J(x)t(x) A(1 - t(x))其中 J 是理想清晰图t 是透射率A 是全局背景光。陆地上雾滴直径远大于可见光波长散射对各波长差异不大t 可以当作三通道共享的标量矩阵。水下场景则不同水的折射率约 1.33但真正影响成像的是水对红光、绿光、蓝光的吸收系数差异。红光在 620 nm 后的衰减系数明显高于蓝绿光水下 5 到 10 米深度红色信号已经很弱所以相机捕获的 RGB 三通道各有不同的衰减系数透射率应当是三个独立的通道矩阵而不是一个能被三通道复用的矩阵。这是水下暗通道方法与普通去雾的本质区别。2.2 Idark.m 的窗口参数决定暗通道质量参考项目中Idark.m负责计算暗通道常见实现方式是用三通道逐像素最小值加滑窗最小值。MATLAB 里可以这样写function dark Idark(I, win_size) % I: 0-255 RGB 图像函数内部转为 double % win_size: 暗通道局部窗口边长一般取奇数 I im2double(I); % 逐像素取三通道最小值得到原始暗通道 dark min(I, [], 3); % 窗口若为偶数则加 1确保形态学结构元素中心有明确锚点 if mod(win_size, 2) 0 win_size win_size 1; end % 全局最小值实际上等价于局部窗口最小值 dark imerode(dark, strel(square, win_size)); end这段代码先用min(I, [], 3)把每个像素在 RGB 三通道中的最小值提取出来再用imerode对灰度暗通道做滑窗最小值操作。strel(square, win_size)构造正方形结构元素形态学腐蚀取窗口内最小值正好对应暗通道定义里的局部最小操作。窗口大小直接影响后续透射率估计窗口太小暗通道保留太多细节透射率图噪声大窗口太大场景边缘被过度平滑恢复图像会出现明显光晕。下表是我在实际图像上比较后认为比较稳妥的取值区间。输入图像分辨率推荐窗口现象640×4807 ~ 15小窗口保留细节适合纹理丰富场景1280×72015 ~ 25平衡细节与边缘平滑1920×1080 及以上25 ~ 35窗口太小会产生斑点状暗通道需要留意imerode处理的是double类型图像时默认补边方式相当于replicate边缘像素不会因为窗口越界被强制置零。如果输入图像是uint8建议先转double再做归一化否则暗通道数值会被截断透射率计算会整体偏移。2.3 getTransmissivity.m 中的折射率补偿逻辑陆地暗通道去雾中透射率公式是t 1 - omega * dark / A。水下则需要在透射率中加入折射率补偿常用做法是把水的折射率作为一个修正系数缩放透射率中的背景光贡献。参考项目里的getTransmissivity.m可以理解为function t getTransmissivity(I, A, opts) % opts.win_size: 暗通道窗口 % opts.omega: 去雾系数一般 0.85~0.95 % opts.n: 水的折射率常用 1.33 dark Idark(I, opts.win_size); % 背景光 A 取标量代表全局背景光强度 t 1 - opts.omega * (dark ./ max(A(:), 1e-3)); % 折射率补偿用折射率 n 修正远处像素的透射率衰减速度 if isfield(opts, n) ~isempty(opts.n) n opts.n; % 这是一个工程近似通过折射率放大衰减差异避免远处过亮 t t .* (1 - (1 - t) ./ (n^2)); end % 限制透射率范围防止分母接近 0 t min(0.95, max(t, 0.1)); end这里的折射率补偿不是严格的光学物理模型而是用一个大于 1 的系数拉伸透射率曲线让远处区域衰减更快。omega控制保留多少雾感值越高恢复越强但超过 0.95 容易放大噪声。透射率下限0.1很关键它保证颜色校正阶段分母始终大于零。若下限设置太高比如 0.3远处区域会被强行保留背景光恢复图像会显得灰蒙蒙若设置为 0靠近相机的暗部区域产生极大增益噪点会被放大成彩色颗粒。参数调节优先级建议是先固定omega 0.95再调win_size最后才动折射率n。3. colorCorrect.m 的通道补偿细节从逆向透射率到白平衡3.1 为什么要分通道做逆向透射率水下颜色校正的核心不是直接调整 RGB 像素值而是把成像模型逆推回清晰图像。模型是I J * t A * (1 - t)反解 J 需要每个通道各自对应的透射率。陆地去雾里 t 是单通道水下则必须用 H×W×3 的三维透射率矩阵因为红光和蓝绿光的衰减路径长度不同。参考项目colorCorrect.m的做法先对三通道逐个执行(I - A) / t A再对整体做自适应颜色校正。直接对整个 RGB 图像做一次除法是错误的这会让红色通道收到过小透射率后产生紫红色偏色。下面是一个与项目结构一致的分通道逆透射率实现function ccI colorCorrect(I, A, t, gamma) % I: double 类型 RGB 图像范围 [0,1] % A: 1x3 全局背景光 % t: HxWx3 透射率矩阵 % gamma: 1x3每个通道的 gamma 校正系数 I im2double(I); J zeros(size(I)); for c 1:3 % 逆透射率分母限制最小 0.15防止噪声放大 J(:,:,c) (I(:,:,c) - A(c)) ./ max(t(:,:,c), 0.15) A(c); end % 自适应颜色校正每个通道独立拉伸动态范围 lo stretchlim(J, [0.02 0.98]); ccI zeros(size(J)); for c 1:3 ccI(:,:,c) imadjust(J(:,:,c), [lo(c,1) lo(c,2)], [0 1], gamma(c)); end ccI max(0, min(1, ccI)); end这里max(t(:,:,c), 0.15)与透射率估计阶段的下限不同颜色校正阶段的下限应当更高。因为透射率估计时允许较低值保留背景信息但逆向除法中过低透射率会让暗部噪声乘上几十倍增益。stretchlim用于计算每个通道的截断区间截掉上下 2% 的极值防止个别高光或死黑像素主导后续拉伸。gamma是每个通道的校正系数红色通道常用小于 1 的值提亮蓝绿通道用大于 1 的值压暗这样能补偿水下红色缺失。参数常见取值实际含义t_min0.15逆透射率下限越大越保守stretchlim低截断0.01~0.05截掉暗部极值比例stretchlim高截断0.95~0.99截掉高光极值比例gamma(1)红色通道0.7~0.9小于 1 提亮红通道gamma(2)绿色通道0.95~1.05微调或保持不变gamma(3)蓝色通道1.0~1.2大于 1 压暗蓝通道3.2 全局背景光 A 的错误估计会让颜色校正整体失效颜色校正的第二个容易踩坑的点是 A 的计算。很多实现直接取整张图像最亮像素作为背景光这在水下会出问题阳光反射、浪花等非散射区域会被当成背景光导致校正结果整体偏灰偏暗。常见做法是在暗通道中选取最亮的前 0.1% 像素把这些像素在原图中的均值作为各通道背景光。function A estimateAtmosphericLight(I, dark, ratio) % I: 原始 RGB 图像double 类型 % dark: Idark 输出的暗通道 % ratio: 前多少个亮像素参与估计建议 0.001 n numel(dark); k max(round(ratio * n), 10); % 对暗通道排序取最亮的 k 个像素 [~, ind] sort(dark(:), descend); mask false(size(dark)); mask(ind(1:k)) true; A zeros(1, 3); for c 1:3 Ic I(:,:,c); A(c) mean(Ic(mask)); end end这里取最亮暗通道像素的原始值均值而不是直接取暗通道最大值。原因是暗通道最亮点可能只是单个噪声像素均值能抑制这种尖峰。k至少要 10 个像素否则在低分辨率图像里不稳定。背景光 A 的三通道数值差异如果很大说明水下光源色温偏移明显此时colorCorrect.m中的 gamma 补偿作用会更突出。若发现颜色校正后整体偏灰优先检查 A 是否明显低于图像实际最亮区域而不是去调窗口大小。3.3 颜色校正后的裁剪与伪色控制逆透射率计算过程中超出 [0,1] 范围的像素会被max(0, min(1, ccI))裁剪。这个裁剪操作会丢失动态范围所以必须在裁剪前完成直方图拉伸。imadjust内部已经做了线性映射但如果输入图像某个通道整体偏移严重比如红色通道均值在 0.2 附近输出很容易出现梯度断层。遇到这种情况我一般会先对红色通道乘以一个大于 1 的全局增益再进入colorCorrect.m而不是只依赖gamma。这样能保留更多红色通道内部层次避免校正后红色区域出现色阶断层。水下图像处理里伪色常常出现在深蓝色背景与物体边缘交界处可以在颜色校正后加 3×3 中值滤波只对色度通道操作不改亮度能明显减少边缘彩色噪声。4. 图像质量评价valueImages.m 里为什么不能只看 PSNR4.1 有参考指标在水下场景里的局限参考项目里的valueImages.m用于图像质量评价。水下图像没有真实无失真参考图PSNR 和 SSIM 这类全参考指标只能拿原图当参考比较的是校正前后像素差异不能说明校正是否更接近真实场景。更麻烦的是水下图像校正后会大幅度拉伸像素分布PSNR 数值通常会下降但视觉质量反而提升。因此水下图像处理中应当优先使用无参考质量评价指标。项目中可以同时计算全参考和无参考指标用无参考指标做最终效果判断全参考指标仅用来观察拉伸程度。一个实用的valueImages.m结构如下function stats valueImages(I, ref) % I: 待评价 RGB 图像uint8 或 double % ref: 可选参考图像若为空则只计算无参考指标 I im2double(I); if nargin 1 ~isempty(ref) ref im2double(ref); stats.PSNR psnr(I, ref); stats.SSIM ssim(I, ref); else stats.PSNR NaN; stats.SSIM NaN; end % 转换到 Lab 空间计算色彩特征 Lab rgb2lab(I); L Lab(:,:,1); a Lab(:,:,2); b Lab(:,:,3); chroma sqrt(a.^2 b.^2); saturation chroma ./ max(L, 1e-6); contrast std(L(:)); % UCIQE 加权组合权重参考常用论文配置 stats.UCIQE 0.4680 * std(chroma(:)) ... 0.2745 * mean(saturation(:)) ... 0.2576 * contrast; stats.entropy entropy(I); end这里的 UCIQE 是水下彩色图像质量评价指标基于色度标准差、饱和度均值和亮度标准差加权得到。数值越高代表色彩丰富度与对比度越好。entropy是图像信息熵反映像素分布是否均匀。颜色校正后的熵如果明显高于原图说明细节信息被恢复出来但如果熵过高也要警惕噪声被当作有效信息计入。4.2 不同指标的差异与选择下表整理了水下图像质量评价中容易混淆的指标。指标缩写是否需要参考图侧重点使用建议PSNR需要像素级误差不适合无参考水下评价SSIM需要结构相似性仅用于验证算法稳定性UCIQE不需要色度、饱和度、亮度综合比较不同校正参数时使用UIQM不需要清晰度、色彩、对比度多维度与 UCIQE 配合使用entropy不需要信息量辅助判断细节与噪声做实验时应当固定同一组测试图像分别输出校正前和校正后的 UCIQE再结合直方图观察红色通道分布。只比较最终数值而不观察中间透射率图很容易被虚假的数值抬高误导。参考项目里的test.m和mainTest.m就是为了批量跑测试图像而设计的输出目录里的ccI.jpg就是颜色校正后的结果可以作为一次性观察的样例。5. 跑通 mainTest.m 的完整流程与参数坑5.1 在 MATLAB 里启动和运行把underwaterCorrection.rar解压后目录结构里有InputImages和OutputImages两个子目录测试图是test1.jpg到test4.jpg。启动 MATLAB 后进入项目目录cd underwaterCorrection addpath(.) mainTestmainTest.m内部会调用Idark.m、getTransmissivity.m、colorCorrect.m和valueImages.m。如果想在命令行直接批处理新版 MATLAB 可以用-batchmatlab -batch run(mainTest.m)运行时如果提示找不到函数先确认当前目录下有四个核心.m文件。常见错误是把test.m和mainTest.m混用前者一般只跑单张测试图后者遍历整个InputImages目录。5.2 我在复现时遇过的实际参数坑第一个坑是win_size设置过大。我最初用 35 的窗口跑 640×480 测试图暗通道把近处石头和远处背景完全连成一片透射率图变成接近常数颜色校正结果像套了一层灰色滤镜。对小分辨率图像窗口不要超过 15。第二个坑是背景光 A 直接取单点亮度。测试图中test4.jpg有阳光直射区域单点背景光让整张图偏红。用前 0.1% 暗通道像素均值后颜色恢复正常。第三个坑在colorCorrect.m的 gamma 设置。如果三通道全部用 1蓝色背景仍然偏蓝只是亮度提高红色通道 gamma 降到 0.8 后石块颜色才自然。观察输出目录中的ccI.jpg如果边缘出现绿色和紫色轮廓说明t下限设得太低或者 gamma 调整幅度过大。跑完mainTest.m后可以修改getTransmissivity.m中的n从 1.33 到 1.15 再跑一遍对比观察折射率补偿对远处区域的抑制程度。最好的验证方式是打开 MATLAB 的图像对比工具同时显示原图、透射率图和校正图确认透射率图在物体边缘没有明显光晕从而判断颜色校正是否真实改善水下图像恢复效果。本文还有配套的精品资源点击获取