基于融合的水下图像与视频增强算法:原理、Matlab实现与调参指南
做水下图像增强也有几年了接过不少来自某海洋工程项目的原始水下视频。第一次拿到这类素材时我是有点懵的画面整体偏绿能见度低到连鱼群和礁石的边界都分不清光线越深越昏暗像戴了一副绿色墨镜在看东西。那时我才真正意识到水下图像退化不是“调高亮度”就能解决的问题而是一套物理原因叠加出来的系统性劣化。这就是为什么今天我想把“基于融合的水下图像与视频增强算法”这条路子完整写下来。它不是新东西但在很多实战场景里它比端到端的深度学习方法更容易落地不需要大规模标注数据、中间过程可解释、参数调节有明确方向而且用Matlab就能实现从单张静态图一路做到视频流。不管你是刚接触图像增强的学生还是在工程里被水下画面困扰的开发者这套思路都值得在手边备一份。1. 水下图像为什么“绿”且“糊”退化机理与融合方法的解题根基很多人拿到水下图像的第一反应是“把绿色去掉”但真正做增强时你会发现连“去掉绿色”这件事都很难下手因为它不是简单的色偏。要让算法知道怎么做取舍先要把水下的光路规律搞清楚。1.1 光在水里的衰减规律决定了偏色的发生顺序水下成像和空气中成像最大的差别是光要经过水层。水对光有选择性吸收不同波长的光穿透能力完全不同。红光波长长能量衰减最快通常在几米之内就被吸收得差不多了橙光、黄光次之蓝光和绿光波长较短穿透性最好所以摄像机拍到的水下画面会被蓝绿色主导。水越深越浑浊这种现象越严重这也是为什么深水视频往往是深蓝发黑而浅水近藻类区域则偏绿。换句话说偏色是光衰减的必然结果不是相机白平衡没调好。这里有个容易被忽略的细节红通道并不是完全没有信息而是在较浅的距离或近距离拍摄时尚有残留。所以在做颜色校正时对红通道的增益不能一刀切地拉到很大否则会把暗部噪声一起放大。1.2 前向散射和后向散射让画面“糊”上加“糊”除了吸收水中的悬浮颗粒还会对光造成散射。散射分两种前向散射是指光线经过目标反射后在到达相机的路上被颗粒二次散射体现在画面上就是边缘模糊、细节发虚后向散射则是环境光没有经过目标直接被颗粒反射到相机镜头里像一层薄雾罩在画面上降低整体对比度。两者叠加使得水下图像不仅偏色而且像蒙了一层纱。去雾算法能在一定程度上处理后向散射但对前向散射造成的细节丢失无能为力。这也是为什么单纯套用大气去雾模型在水下往往不理想的原因。1.3 单路方法为什么总是顾此失彼先看几种最常见的基础增强手段直方图均衡化可以把动态范围拉开暗部细节变清楚但对偏色几乎没有修正作用而且对噪声区域特别不友好灰度世界白平衡能纠正色偏原理是假设场景整体颜色平均趋于灰色可一旦场景里有大面积单一色物体这个假设就会失效带限对比度自适应直方图均衡CLAHE能提升局部细节但如果直接在RGB三个通道上分别做很容易出现颜色失真。单独使用这些方法往往会面临“色彩准了细节糊细节清了颜色怪”的两难。其实这不能怪方法本身而是单一方法只建模了退化过程的某一个侧面。1.4 融合方法为什么能同时兼顾多个目标融合策略的思路和“多专家投票”很像。既然单个算法只能解决部分问题那就干脆把几个不同侧重点的增强结果都算出来再根据每个像素在哪个结果中最可信来加权合并。色彩校正版本负责恢复颜色对比度增强版本负责把细节从模糊中拽出来亮度修正版本负责让暗部可见最后由权重图决定每个位置该信谁多一点。这样得到的结果既不是某个单一方法的输出也不会被某个失败结果拖累最后会落在一个更均衡的点上。水下图像增强研究中基于融合的方法是公认稳定性较高的一类方案也是我这篇文章的主角。2. 基于融合的水下增强算法设计三路输入与多尺度融合的完整框架2.1 三路输入支路各司其职我这里用的是经典的三路增强设计。第一路做颜色校正核心是补偿红通道、校正整体色偏输出一个色彩相对自然的版本第二路做对比度增强典型做法是在亮度空间上做CLAHE保留细节但在一定程度上抑制颜色污染第三路做亮度与细节修正可以用伽马校正或者带颜色保护的锐化把前面两路照顾不到的暗部和中间调补起来。三条支路各有侧重融合时才能形成互补。如果只做两路比如只做颜色校正和对比度增强那么在偏色和光照同时严重的区域效果会明显不如三路稳定。有的论文会加第四条“去雾”支路用暗通道估计和水下散射模型生成一个去雾版本。我建议刚开始做的时候先用三路跑通再去加分支因为每加一个分支就多一组权重和参数调试成本是线性上升的。2.2 权重图告诉算法“哪个区域该信哪一路”融合不是简单平均。三路版本的优劣区域是不同的颜色校正版在底色偏绿的区域可能很好但在中性色物体上容易过白对比度增强版在纹理丰富的区域表现好但在平坦暗部会引入块状噪声。权重图的作用就是逐像素地表达“这个位置哪一路结果最可信”。常用的权重子图有三个饱和度权重色彩丰富的区域说明颜色校正更可信对比度权重局部方差大的区域说明结构信息保留好显著性权重突出前景目标防止背景主导融合权重。三个子权重相乘再做一次平滑后归一化就是最终权重图。这里的关键是相乘之前权重图一定要经过足够范围的高斯模糊否则融合时会沿着物体边缘出现明显的接缝和光晕。这一步在实现阶段最容易忽略。2.3 多尺度金字塔融合为什么不能只在原尺度上加权直接在原始分辨率上做加权平均遇到边缘时权重突变会造成伪影看起来像物体周围多了一圈光晕。多尺度融合的解决办法是把每路图像分解成不同频段用拉普拉斯金字塔保留各尺度的细节信息把权重图做成高斯金字塔保留空间连续性。在每一层金字塔上分别做加权融合最后重建。低频层决定整体色彩和亮度高频层决定细节和边缘不同频段各取所需。这么做的直观结果是过渡自然细节清晰不容易出现那种廉价的“贴图感”。金字塔层数少则3层多则6层具体看输入图像的大小下文实现部分再细说。2.4 整体流程与参数总览整个算法的核心流程可以用一句话串起来原图经过白平衡、CLAHE、伽马校正三路增强分别计算饱和度、对比度、显著性三个权重子图并合成最终权重最后在拉普拉斯金字塔上逐层融合并重建输出。跑通以后需要调的核心参数也就四五个我平时习惯用一个结构体把它们集中管理方便对图像和视频分别配置。这里先给出一个总览表具体的调参经验放到第4节。参数作用推荐范围调节方向金字塔层数决定多尺度融合的精细程度4~6层数越多细节越自然耗时越慢CLAHE clip limit控制局部对比度增强强度0.005~0.03画面发闷时增大噪声变大时减小白平衡强度平衡色彩校正强度0.6~0.9偏色严重时增大色彩过艳时减小伽马值调整整体亮度分布0.8~1.2暗部看不清时减小过曝时增大权重平滑半径抑制融合伪影7~15像素出现光晕时增大边缘糊时减小3. Matlab从零实现单帧增强函数的代码拆解这一章直接上代码。我用的是Matlab标准的图像处理工具箱函数包括rgb2lab、adapthisteq、imresize版本R2020a及以上都能直接跑。核心流程是写好单帧增强函数再用VideoReader/VideoWriter套一个视频处理壳。3.1 主函数结构与调用方式建议的目录结构就两个文件加一个测试脚本uw_enhance_main.m放主增强函数和内部辅助函数uw_enhance_video.m放视频读取与逐帧处理test_demo.m用来跑单帧测试。主函数接收一个RGB图像矩阵和一个参数结构体输出增强后的RGB图像。如果参数为空则使用默认值这样函数既可以被人拿去做批量测试也能在视频脚本里复用。function enhanced uw_fusion_enhance(img, params) % 基于融合的水下图像增强主函数 % 输入 imgHxWx3 uint8 RGB图像 % 输出 enhancedHxWx3 uint8 RGB图像 if nargin 2 params struct(); end if ~isfield(params, pyramid_levels), params.pyramid_levels 5; end if ~isfield(params, clip_limit), params.clip_limit 0.02; end if ~isfield(params, wb_strength), params.wb_strength 0.8; end if ~isfield(params, gamma_value), params.gamma_value 1.0; end img im2double(img); % 三条增强支路 I1 white_balance_branch(img, params.wb_strength); I2 clahe_branch(img, params.clip_limit); I3 gamma_branch(img, params.gamma_value); % 合成权重图并归一化 W1 compute_weight_map(I1); W2 compute_weight_map(I2); W3 compute_weight_map(I3); W_sum W1 W2 W3 1e-6; W1 W1 ./ W_sum; W2 W2 ./ W_sum; W3 W3 ./ W_sum; % 多尺度金字塔融合 enhanced pyramid_fusion({I1, I2, I3}, {W1, W2, W3}, params.pyramid_levels); enhanced clamp_image(enhanced); enhanced im2uint8(enhanced); end调用方式很简单读一张测试图进去直接调用out uw_fusion_enhance(img, params)。第一次跑时如果对主线还不熟建议先用默认参数看效果再逐项调整。3.2 白平衡支路与CLAHE支路的实现白平衡支路我用的是“灰度世界红通道补偿”的混合做法。先假设整张图平均颜色应该趋于中性算出红蓝通道相对绿色通道的增益再把增益限制在安全范围内防止暗部噪声被过度放大。注意白色物体区域不能增益过大所以引入了strength参数做混合。function I1 white_balance_branch(img, strength) avgR mean2(img(:,:,1)); avgG mean2(img(:,:,2)); avgB mean2(img(:,:,3)); gainR avgG / (avgR 1e-6); gainB avgG / (avgB 1e-6); gainR min(gainR, 2.5); gainB min(gainB, 2.5); Iwb img; Iwb(:,:,1) img(:,:,1) * gainR; Iwb(:,:,3) img(:,:,3) * gainB; Iwb clamp_image(Iwb); % 强度混合避免校正过度 I1 strength * Iwb (1 - strength) * img; I1 clamp_image(I1); endCLAHE支路建议在Lab颜色空间中只处理亮度通道L不直接动RGB这样可以显著减少颜色失真。Matlab自带的adapthisteq就是CLAHE的标准实现参数ClipLimit对应对比度限制强度NumTiles控制局部块大小。水下图像一般建议把NumTiles设成8x8数值太大容易产生格状伪影。function I2 clahe_branch(img, clip_limit) lab rgb2lab(img); L lab(:,:,1); % 在亮度通道上做CLAHE L2 adapthisteq(L, ClipLimit, clip_limit, NumTiles, [8 8]); lab(:,:,1) L2; I2 lab2rgb(lab); I2 clamp_image(I2); end这里有个细节如果原图暗部噪声已经比较大先做一次轻量级双边滤波再进CLAHE效果会稳很多否则对比度增强会先把噪声的“性格”增强出来。3.3 权重图与多尺度融合的实现权重图部分我这里提供的是三个子权重相乘的版本。饱和度权重由Lab空间里a、b通道的模长近似得到对比度权重用局部标准差显著性权重简化为“像素颜色与全图平均色的距离”突出水中与背景差异大的目标。最后一定要对乘积结果做较大半径的高斯模糊我在代码里用了15像素这是为了防止边缘伪影。function W compute_weight_map(I) lab rgb2lab(I); a lab(:,:,2); b lab(:,:,3); sat sqrt(a.^2 b.^2); % 局部标准差近似对比度 h fspecial(gaussian, 15, 4); g imfilter(I, h, replicate); local_var imfilter((I - g).^2, h, replicate); con sqrt(mean(local_var, 3)); % 与全图平均色的距离作为显著性近似 mean_color reshape(mean(reshape(I, [], 3), 1), 1, 1, 3); sig sqrt(sum((I - mean_color).^2, 3)); W sat .* con .* sig; W imgaussfilt(W, 7); W W / (max(W(:)) 1e-6); end多尺度融合部分自行实现高斯金字塔和拉普拉斯金字塔。构建金字塔时每层用imresize缩小一半上一层重建到下一层大小时再用imresize放大。对每个频段做权重归一化融合最后逐层重建。这个代码量不大但注释要写清楚。function out pyramid_fusion(images, weights, levels) num length(images); Lap cell(1, num); Gau cell(1, num); for i 1:num Lap{i} laplacian_pyramid(images{i}, levels); Gau{i} gaussian_pyramid(weights{i}, levels); end % 逐层加权融合 fused_pyr cell(1, levels); for l 1:levels acc zeros(size(Lap{1}{l})); acc_w zeros(size(acc)); for i 1:num w Gau{i}{l}; acc acc w .* Lap{i}{l}; acc_w acc_w w; end fused_pyr{l} acc ./ (acc_w 1e-6); end % 重建 out fused_pyr{levels}; for l levels-1:-1:1 out imresize(out, size(fused_pyr{l})); out out fused_pyr{l}; end end function G gaussian_pyramid(img, levels) G cell(1, levels); G{1} img; for l 2:levels G{l} imresize(G{l-1}, 0.5); end end function L laplacian_pyramid(img, levels) G gaussian_pyramid(img, levels); L cell(1, levels); L{levels} G{levels}; for l 1:levels-1 up imresize(G{l1}, [size(G{l},1), size(G{l},2)]); L{l} G{l} - up; end end function y clamp_image(x) y min(max(x, 0), 1); end写到这里单帧增强的完整闭环已经通了。按我的使用经验金字塔层数在5层时对1080p图像的效果和速度最均衡第4节我会给不同场景的参考配置。3.4 从单帧到视频VideoReader与帧间平滑视频增强和图像增强最大的区别不是逐帧套函数而是帧间稳定性。前几帧增强得很好下一秒增强强度突然抖一下观看体验会比原始视频还差。所以视频版本除了调用上面的单帧函数还需要加一个帧间平滑把当前帧的增强结果与上一帧结果做渐进混合。alpha系数设成0.5到0.7比较稳系数太大画面会拖尾太小根本压不住闪烁。function uw_enhance_video(video_in, video_out, params) if nargin 3, params struct(); end params.video_smooth_alpha 0.6; r VideoReader(video_in); w VideoWriter(video_out, MPEG-4); w.FrameRate r.FrameRate; open(w); prev []; frame_idx 0; while hasFrame(r) f readFrame(r); e uw_fusion_enhance(f, params); if ~isempty(prev) alpha params.video_smooth_alpha; e im2uint8(alpha * im2double(e) (1-alpha) * im2double(prev)); end writeVideo(w, e); prev e; frame_idx frame_idx 1; if mod(frame_idx, 50) 0 fprintf(已处理 %d 帧\n, frame_idx); end end close(w); end另外一个提速技巧是先用较快的参数跑一遍视频找到画面最脏的几帧用单帧函数精调参数再把参数套回整个视频而不是直接在视频上边放边调参那样既慢又容易错过问题帧。注意Matlab的VideoWriter需要先关闭再读文件如果文件名已经存在要确认是否允许覆盖否则会报错。4. 实测效果评估与参数调优从指标到场景化配置4.1 主观评估先看三件事评估一张水下增强结果我从来不只看指标而是先做三件事。第一看边缘找画面里明显的物体轮廓看轮廓是不是干净、有没有白色光晕第二看色彩看岩石、船体这些应该接近中性色的物体有没有被过分染色水底沙地的颜色是不是自然第三看噪声把画面暗部局部放大两到三倍看有没有出现明显的色斑或颗粒。这三关都过了再去计算客观指标才有意义。如果只盯着指标调参数很容易出现UCIQE涨了但观感更差的情况因为指标会被异常的高饱和度带偏。4.2 客观指标UCIQE、UIQM、PCQI到底在衡量什么UCIQE是水下彩色图像质量评价指标由色度标准差、亮度对比度和饱和度均值按一组固定权重组合而成分数越高通常意味着画面色彩信息更丰富、对比度更好。它的问题在于对过饱和图像比较宽容所以我会把它当趋势指标而不是绝对标准。UIQM由色彩测量、清晰度测量和对比度测量三部分组成考虑更全面。PCQI衡量的是局部对比度质量适合判断图像增强是否过度提升了局部结构。实际上Matlab计算这些指标都不复杂写一个小函数就能用。UCIQE的核心计算大概是这样function score calc_uciqe(img) lab rgb2lab(img); a lab(:,:,2); b lab(:,:,3); c sqrt(a.^2 b.^2); L lab(:,:,1); sigma_c std(c(:)); con_lum std(L(:)) / (mean(L(:)) 1e-6); mean_sat mean(c(:)); score 0.4680 * sigma_c 0.2745 * con_lum 0.2576 * mean_sat; end那组0.4680、0.2745、0.2576系数是公开文献里常用的一组直接拿来参考没有问题。如果你手头有主观标注数据用回归重新标定一组会更好。4.3 参数调节的核心法则参数调节有一个我比较信奉的原则先固定结构再调强度。金字塔层数和权重平滑半径属于结构参数一般设置一次就不用动真正需要反复调的是CLAHE的clip_limit、白平衡强度和伽马值这三个强度参数。调CLAHE时画面发闷就加大clip_limit从0.01开始每次加0.005直到细节清楚为止一旦看到噪声冒头就退回一步。调白平衡强度时偏绿严重想拉回颜色就加大强度但注意观察中性色物体有没有发白。伽马值则负责兜底亮度暗部整体太黑就降高光区死白就升。不要一次同时动三个参数否则你根本不知道画面变化是哪个参数引起的。每次只改一个记录前后指标和主观感受这是最慢但最快的方式。4.4 几种典型水下场景的参数方案不同的水域条件差异很大我给一个可参考的配置表。蓝水清水场景偏色不严重、对比度尚可参数往保守方向设绿色浑浊场景偏色和散射都严重需要更积极的颜色校正和对比度增强近底泥浆水场景细节极少重点放在抑制噪声和保留仅有的结构信息上。场景白平衡强度CLAHE clip_limit伽马值金字塔层数备注蓝水浅水0.50.011.04轻校正防过饱和绿色浑浊水0.90.0250.95颜色和细节都需强拉近底泥浆水0.60.0081.15以保结构、抑噪为主这只是起点拿到自己的数据之后要按4.3的法则重新微调。有一点很重要把同一套参数用在不同场景时建议用批量测试脚本把一整个文件夹的样本全跑一遍不能只看一两张效果好就认定参数可靠。5. 工程化落地中最容易踩的坑与排查链路5.1 视频闪烁问题从现象到根因的完整排查视频增强后闪烁是水下视频落地时最高频的坑。现象是同一段水下画面里增强强度来回波动静止的礁石看起来在“呼吸”。排查链路是这样的先确认是不是输入视频本身有亮度抖动把视频连续几帧的亮度均值打出来如果原视频就抖那是采集端问题如果原视频稳定而增强后抖动问题出在增强参数在帧间变化。根因通常是白平衡增益和CLAHE统计量基于每一帧独立计算某些帧里碰巧出现大块亮色物体或被悬浮物遮挡统计结果突变增强强度跟着突变。解决方案有两个方向一是做帧间平滑第三章已经给了代码二是用视频前30帧的中值统计结果固定白平衡增益相当于把“自适应”降级为“半自适应”稳定性会大幅提升。5.2 过度校正灰色岩石被拉成白色白平衡强度过高时画面里接近中性色的物体比如灰色坝体、白色浮标会被增益放大成死白丢失结构纹理。排查时把原图和增强结果放在同一画面对比看发白区域在原图是不是本来接近中性色。如果是说明不是过曝而是增益溢出。解决思路是加一个中性色保护计算像素饱和度饱和度极低的像素先抑制其增益倍数。最简单的做法就是把白平衡强度从0.9降到0.6附近再配合伽马值把高光压一压。如果还想保留更强的颜色校正效果可以在权重图的饱和度子图上给低饱和区域更小的权重让融合阶段自动少信颜色校正结果。5.3 噪声放大问题水下图像暗部噪声本来就大CLAHE增强会把它放大成明显的颗粒和色斑。这类问题很容易被误判成“算法不行”其实是你把对比度增强推过了头。排查时先确认噪声出现的位置如果集中在中亮度和暗部区域说明是增益过大如果是全图均匀的黄绿色噪点说明原始视频在采集时增益就过高算法层面只能缓解。对前者降低clip_limit或者进CLAHE之前先做一次保边去噪对后者增强处理之前先做一个亮度通道的温和去噪不要对着RGB三个通道分别去噪那样会损失色彩信息。5.4 性能优化从单帧到视频的取舍用Matlab处理1080p水下视频不做优化时速度很感人每秒零点几帧都正常。工程上我常做的取舍有三个一是先缩放后增强把输入帧缩到宽960增强完再放大回原分辨率细节损失在绝大多数项目里都看不出来二是只在亮度通道做主要增强色彩通道用白平衡轻薄处理即可速度翻倍三是降低金字塔层数到4层减小局部统计窗口半径。按我的实测这三招合起来通常能把单帧处理时间压缩到原来的三分之一到四分之一。如果你的任务里光源和深度变化都不大还可以把“每帧计算权重图”改成“每隔五帧计算一次中间帧复用”闪烁控制得好的话效果几乎一样。最后分享一个我自己的调试习惯每次拿到新的水下素材先不急着写代码或调参数而是先挑出三到五张有代表性的帧把偏色程度、模糊程度、噪声水平分别记下来。然后按照“先白平衡后对比度、先全局后局部、先主观后指标”这个顺序走一遍把每一段的改动记录到一张表里。这套方法看起来笨但它让我少走了很多弯路。基于融合的水下图像与视频增强算法最迷人的地方就是你把每个环节都弄清之后它能完全按你的意图去平衡色彩、细节和噪声而不是一个黑盒。希望这篇文章能给你省掉一些我当年踩坑的时间。