
简介基于OpenCV与Python的水下图像增强与修复资源面向图像处理初学者及计算机视觉开发者有针对性地解决水下环境中常见的成像模糊、颜色失真、对比度不足等视觉问题。压缩包共4个文件涵盖实现核心算法的Python脚本、项目说明文档、处理前后的效果对比图片以及开源许可证整体仅210KB轻量紧凑便于下载和阅读。目前已有907人学习。内容围绕水下图像处理的关键环节展开覆盖去噪、色彩校正、对比度提升、边缘检测等操作脚本可直接运行并观察处理效果非常适合在算法验证、课程实验或项目起步阶段使用。借助源码和文档读者可以掌握OpenCV中滤波、色彩空间转换、直方图均衡化等常用函数的实际调用方法理解从问题分析、代码实现到效果验证的完整流程同时通过对比样例图片能直观看到增强与修复前后的差异为深入水下视觉或图像修复应用打下扎实基础。1. 水下图像增强为什么比普通低照度难先把偏色和雾化拆成可计算的模型水下拍回来的图很少能直接拿来用。水体会先吸收长波长的红光几米之内红色基本消失再深一点橙色和黄色也跟着衰减剩下的主要是蓝绿光所以水下照片第一眼总是偏蓝绿色同时水中悬浮颗粒对光线的散射会让整张图像蒙一层薄纱远处细节像被磨掉一样。近处如果开了补光灯又会冒出一片亮斑把原本的纹理直接“吃掉”。基于 OpenCV 做水下图像增强与修复本质上只解决三件事把丢失的红色分量从剩余通道里估算回来把散射造成的雾化压下去再把过曝或损坏的区域补回去。这套方向适合做水下机器人视觉、水产养殖监测、潜水影像后期的人不需要先学图像处理理论花一个下午就能跑通一条可复现的 pipeline。2. 用 OpenCV 校正水下偏色与低对比度灰度世界白平衡和 CLAHE 的参数怎么定先做增强再做修复是我做水下视频处理时一直保持的顺序。增强解决“看不见”的问题修复解决“缺一块”的问题。顺序反过来修复会因为颜色偏得太多把错误的信息当成参考最后补出来一片假纹理。第二章先把增强链路搭出来第三章再谈修复。2.1 先看 BGR 通道均值判定偏色方向再动手OpenCV 读进来的图像顺序是 BGR很多人第一次处理水下图像会把通道顺序搞反后面所有补偿都打在错误的通道上。正确做法是先量化偏色方向不要凭肉眼猜。写一个诊断脚本把每个通道的均值打出来。import cv2 import numpy as np img cv2.imread(underwater.jpg) b, g, r cv2.split(img) for name, ch in zip((蓝, 绿, 红), (b, g, r)): print(f{name}通道均值: {ch.mean():.2f})这段代码的输出能直接告诉你该补偿哪条通道。淡水湖和近海的颜色还不一样近海通常蓝得厉害蓝通道均值会明显高于红通道两倍以上浑浊的养殖塘往往是绿通道占主导蓝和红同时很低再深一点的开放水域里红通道均值甚至会为零头。看到这样的分布就不要去想什么通用滤镜直接用通道比例做补偿。这里说一句很容易踩的习惯不要在没有统计前就打开 PhotoShop 式滤镜那样只会把偏蓝拉成偏青把偏绿拉成偏黄。2.2 灰度世界白平衡的 OpenCV 实现红通道补偿倍数与上限水下增强里最常用的基础假设是灰度世界认为在自然环境下一张正常曝光的图像在三通道上的均值应该大致相等。水下图像因为红光衰减红通道均值特别低于是把红通道乘上一个大于 1 的增益蓝通道乘上一个小于 1 的增益就能从物理上补偿一部分水层吸收。def gray_world_balance(img, max_gain3.0): result img.astype(np.float32) avg_r np.mean(result[:, :, 2]) avg_g np.mean(result[:, :, 1]) avg_b np.mean(result[:, :, 0]) gain_r min(avg_g / (avg_r 1e-6), max_gain) gain_b np.clip(avg_g / (avg_b 1e-6), 0.5, max_gain) result[:, :, 2] * gain_r result[:, :, 0] * gain_b result np.clip(result, 0, 255) return result.astype(np.uint8), (gain_r, gain_b)这里的基准通道我选了绿色因为大多数水下场景里绿通道衰减最少用它做分母得到的增益最稳定。代码里的max_gain是个很关键的保险丝浅水区或者有潜水灯直射时红通道衰减没那么严重自动计算出的增益可能高达四倍以上把整张图拉成夕阳红限制在 3.0 以内能保住色彩。1e-6是为了防止某个通道像素全为 0 时出现除零水下图像经常有纯黑区域这个保护必须有。如果你装的是opencv-contrib-python也可以用现成的 OpenCV 扩展接口cv2.xphoto.createGrayworldWB()它把灰度世界假设封装好了参数只有setSaturationThreshold()。但我的习惯是先用手写版本跑通环节因为你能看到实际增益值也方便把增益结果写回文件做中间验证。扩展接口适合已经确定阈值后批量处理素材的情况。2.3 在 LAB 空间做 CLAHEclipLimit 和 tileGridSize 怎么配合白平衡把颜色拉回正常范围后对比度通常还是差。水下图像的动态范围很窄直方图往往挤在一个窄区间里这时候直接做全局直方图均衡化会让亮部过曝暗部噪点全被翻出来。我一般只对 LAB 颜色空间里的 L 通道做 CLAHE也就是对比度受限的自适应直方图均衡化色度通道 a 和 b 保持原样这样能避免色相偏移。def clahe_on_lab(img, clip_limit2.0, tile_size8): lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimitclip_limit, tileGridSize(tile_size, tile_size)) l_enh clahe.apply(l) lab_enh cv2.merge([l_enh, a, b]) return cv2.cvtColor(lab_enh, cv2.COLOR_LAB2BGR)clipLimit控制对比度的放大上限。取值越小越保守水下素材光线差、噪声重我一般选 1.5 到 2.5超过 3.0 会把传感器噪声一起放大水面悬浮颗粒的纹理也会被加强成类似噪点的东西。tileGridSize是分块数量8×8 是通用起步值意思是把图像切成 64 块小区域分别做直方图均衡。调大到 16×16 时局部对比更强但容易出现明显的块状边界近距离看纹理像鱼鳞一样一层层断层这就是业内常说的块效应。低速处理视频帧时如果原图是 4000×3000 这种大尺寸我还会先把图像缩放一半再跑 CLAHE速度能快出很多视觉效果损失很小。把两步串成一条完整链路def enhance_underwater(img): balanced, _ gray_world_balance(img) result clahe_on_lab(balanced, clip_limit2.5, tile_size8) return result顺序很有讲究必须先白平衡再 CLAHE。如果反过来CLAHE 会先把偏蓝通道中的对比度强行拉大蓝通道里的色偏在后续白平衡里很难被完全纠正最后画面会出现一层诡异的青色底板。跑完这一步偏色图基本上能被拉回肉眼可看的范围但补光灯造成的亮斑、被水雾遮住的远景还是老样子这两个问题要交给下一章的修复链路。3. 用 OpenCV 修复水下图像的亮斑和雾化inpaint 与暗通道先验的落地组合水下图像修复在实践中比增强更容易被误解。很多人把“修复”理解成套一个美颜滤镜但真正需要修复的是三类东西补光灯造成的过曝亮斑、传感器或者镜片上的划痕与水痕、以及水散射造成的雾状退化。这三类退化的信息丢失机制不一样处理手段也不一样。3.1 修复不是增强水下退化里哪些该由 inpaint 处理增强是像素级的数值映射它不产生新内容只是把已有的对比度重新分配。修复则是对缺失信息的推断。以亮斑为例过曝区域里像素值全部变成 245 以上已经没有纹理信息了任何增强算法都找不回来必须用周围像素把这块区域“补写”出来。OpenCV 内置的cv2.inpaint就是干这个的它会根据掩码指定区域用相邻像素的梯度方向把信息扩散进去。在我做水下影像的流程里inpaint 最常见的三个用途是第一去掉水下补光灯聚焦造成的中心亮斑这个亮斑会严重影响后续特征点提取第二修复镜片上的水滴和划痕这类退化通常是线状的小区域第三修复鱼类或者其他目标运动时留下的残影不过残影修复对掩码精度要求很高处理不好会出现重影。inpaint 的前提是你能提供一个相对干净的掩码掩码生成往往是整个流程里最花时间的地方。3.2 亮斑掩码生成与 cv2.inpaint两种算法的选择依据亮斑检测最直接的办法是亮度阈值但水下图像里明亮的鱼鳞、反光的沙粒也是高亮区域直接全选会把好纹理也送进修复区。所以我在阈值之后会加一步连通域筛选只保留面积在合理范围内的亮斑。def build_highlight_mask(gray, thresh240, area_range(50, 5000)): _, mask cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY) num, labels, stats, _ cv2.connectedComponentsWithStats(mask, 8) clean np.zeros_like(mask) for i in range(1, num): area stats[i, cv2.CC_STAT_AREA] if area_range[0] area area_range[1]: clean[labels i] 255 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) return cv2.dilate(clean, kernel, iterations2) mask build_highlight_mask(gray_img, thresh235, area_range(100, 8000)) inpainted cv2.inpaint(img, mask, inpaintRadius3, flagscv2.INPAINT_TELEA)cv2.connectedComponentsWithStats返回每个连通域的左上角坐标、宽高和面积我把面积小于 50 的孤立白点剔除它们多半是椒盐噪声面积大于 8000 的高亮区通常就是整片反光水面不是局部亮斑留着不修复更稳。dilate这一步很重要阈值分割出的亮斑边缘往往沿梯度方向收缩了半圈把掩码往外扩 2 个像素inpaint 才有足够的过渡带。cv2.inpaint里的两种算法各有倾向INPAINT_TELEA基于快速行进法适合修补细线划痕和小型亮斑速度较快修复后的边缘比较锐利INPAINT_NS基于偏微分方程对大面积亮斑更稳但计算量大不少而且修复区域的纹理会被抹得比较平。我的习惯是小瑕疵用 TELEA大亮斑用 NS。如果一张图上同时有补光灯亮斑和细划线痕就在同一张图上分两次处理。inpaintRadius的取值也不能随意半径设为 1 到 2 时修复痕迹很重设到 5 以上亮斑周围会糊一大片3 是大部分水下素材的折中值。3.3 暗通道先验去水雾基于 OpenCV 的透射率估计与参数表水雾和空气中的雾不完全一样但暗通道先验在近岸浑浊水里依然能用只是参数要重新校。方法的核心思路是在无雾的自然图像里每个局部区域内总会有某个像素在某个颜色通道上的值非常低这个“暗通道”几乎为 0。水下图像被散射光干扰后暗通道不再是纯黑而是相当于叠加了一层大气光把这层光从图像里减掉再补偿透射率就能压掉雾感。def dark_channel(img, patch15): b, g, r cv2.split(img) min_rgb cv2.min(cv2.min(b, g), r) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (patch, patch)) return cv2.erode(min_rgb, kernel) def atmospheric_light(img, dark, top_percent0.001): h, w dark.shape flat_dark dark.ravel() flat_img img.reshape(-1, 3) k max(1, int(h * w * top_percent)) indices np.argsort(flat_dark)[::-1][:k] return np.mean(flat_img[indices], axis0) / 255.0 def dehaze_dcp(img, patch15, omega0.95, t_min0.2): img_f img.astype(np.float32) / 255.0 dark dark_channel(img, patch) / 255.0 A atmospheric_light(img_f, dark) A_dark np.mean(A) t 1.0 - omega * dark / (A_dark 1e-6) t np.clip(t, t_min, 1.0) t cv2.blur(t, (patch, patch)) out np.zeros_like(img_f) for i in range(3): out[:, :, i] (img_f[:, :, i] - A[i]) / np.maximum(t, t_min) A[i] return np.clip(out, 0, 1) * 255.0, t代码里patch决定暗通道的局部窗口大小对应的是 OpenCV 里的形态学腐蚀核。普通空气中 patch 取 15 表现不错水下环境我建议 15 到 25窗口太小暗通道会有很多洞透射率图不稳定。omega控制去雾强度普通雾天取 0.95 没问题水下取 0.9 到 0.95 之间比较安全太高会把远处物体压成偏黑的轮廓。t_min是透射率下限水下悬浮物密度大限制得比空气中保守一些0.1 到 0.2 比较合理取太大会限制修复力度限制太小会放大噪声。有个很关键的差异是大气光估计。水下场景里红色通道严重不足直接用普通暗通道先验估计出的“大气光”会偏向蓝绿色导致去雾后画面发紫。我的常见做法是先去执行第二章的灰度世界白平衡再做暗通道去雾顺序不能换。另一个更贴合水下物理的方法是把大气光估计限制在蓝绿通道上红通道单独补偿这部分体现为代码里的A_dark np.mean(A)只取大气光三通道均值而不过度依赖红通道。处理完这张图后通常还会再轻微跑一次 CLAHE让去雾造成的灰调重新变得通透。4. 在 OpenCV 里实现基于 Retinex 的水下低照度增强SSR 与 MSRCR 的取舍说到低照度很多人马上想到直方图均衡化但水下低照度还不是普通暗光即使补光灯打得很足远处物体依然会因为散射变成低亮度低反差。Retinex 理论是这类场景中常用的一条思路而且用 OpenCV 已有的高斯模糊和通道操作就能实现不需要额外引入深度学习框架。4.1 Retinex 为什么能处理低照度光照分量与反射分量的分离Retinex 把人眼看到的图像分成两个部分光照分量乘上反射分量。光照分量描述环境的明暗分布反射分量描述物体表面的固有颜色和纹理。水下补光灯造成中心亮四周暗的现象本质上就是光照分量不均匀水层散射则会让反射分量的高频细节被抹平。Retinex 的思路是用一个低通滤波估计光照分量把原图除以光照分量得到反射分量再做对数变换把乘除关系变成加减这样可以把非均匀光照的影响压掉。在 OpenCV 里实现单尺度 Retinex 最常见的替代做法是对图像取对数后减去对数域的高斯模糊结果。这个差值近似等于反射分量的对数。由于它不依赖全局直方图的分布对光照边缘比较强的画面效果比 CLAHE 更自然。4.2 单尺度 RetinexSSR的 OpenCV 实现sigma 与光晕的关系def ssr_channel(channel, sigma80): img channel.astype(np.float32) / 255.0 img np.maximum(img, 1e-6) log_img np.log(img) blur cv2.GaussianBlur(channel.astype(np.float32), (0, 0), sigma) blur np.maximum(blur / 255.0, 1e-6) log_blur np.log(blur) diff log_img - log_blur diff cv2.normalize(diff, None, 0, 255, cv2.NORM_MINMAX) return diff.astype(np.uint8) def ssr(img, sigma80): b, g, r cv2.split(img) b_res ssr_channel(b, sigma) g_res ssr_channel(g, sigma) r_res ssr_channel(r, sigma) return cv2.merge([b_res, g_res, r_res])cv2.GaussianBlur的核大小传(0, 0)表示完全由第五个参数sigma决定这样写比手工指定核大小更省事而且尺度更连续。sigma 在这里直接控制“光照分量”的平滑范围sigma 小光照分量保留的细节多最终结果里边缘增强明显但暗区的噪声也会被放大sigma 大光照分量更平坦色彩还原更稳但对比度提升有限。水下低照度素材我一般从 80 起步偏暗的环境调到 120过大会出现一圈光晕。光晕是 Retinex 比较典型的翻车现象所有人物边缘都像背光一样带着一圈白边。单尺度 Retinex 有个明显的短板它只能照顾一个尺度下的细节。补光灯照亮的前景和远处的暗背景差异太大选一个 sigma 很难同时讨好两个区域于是就有了多尺度版本。4.3 多尺度 Retinex 与颜色恢复MSRCRalpha、beta、gain 的参数设置多尺度 Retinex 是把三个不同 sigma 的 SSR 结果加权平均。水下场景里我常用的是 15、80、200 这一组15 负责抓细节80 负责中距离纹理200 负责全局颜色一致性。直接用三个尺度叠加还不够因为每个通道独立做归一化后通道之间的比例会被打乱画面会偏灰发青。所以要加颜色恢复因子也就是 MSRCR 的核心改动。def msr_channel(channel, sigmas): img channel.astype(np.float32) / 255.0 img np.maximum(img, 1e-6) log_img np.log(img) msr None for sigma in sigmas: blur cv2.GaussianBlur(channel.astype(np.float32), (0, 0), sigma) blur np.maximum(blur / 255.0, 1e-6) log_blur np.log(blur) if msr is None: msr log_img - log_blur else: msr log_img - log_blur return msr / len(sigmas) def msrcr(img, sigmas(15, 80, 200), alpha125.0, beta46.0, gain1.0, offset10.0): img_f img.astype(np.float32) / 255.0 img_f np.maximum(img_f, 1e-6) b, g, r cv2.split(img_f) sum_bgr b g r msr_b msr_channel(b, sigmas) msr_g msr_channel(g, sigmas) msr_r msr_channel(r, sigmas) cr_b beta * np.log(alpha * b / (sum_bgr 1e-6)) cr_g beta * np.log(alpha * g / (sum_bgr 1e-6)) cr_r beta * np.log(alpha * r / (sum_bgr 1e-6)) out_b gain * (cr_b * msr_b offset) out_g gain * (cr_g * msr_g offset) out_r gain * (cr_r * msr_r offset) out cv2.merge([out_b, out_g, out_r]) return cv2.normalize(out, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)颜色恢复因子里的alpha控制颜色饱和度的敏感度默认 125 在普通图像上合适水下蓝绿主导的场景我通常调到 100 以下否则红通道恢复因子会异常偏高整体发红。beta是颜色非线性强度默认 46对偏色严重的图可以调到 30 左右恢复会更保守。gain和offset是输出前的动态范围控制gain 大于 1 会让中间对比更强烈但亮部容易过曝offset 越大画面越明亮不会产生负值。这里提醒一句MSRCR 不是万能的水下雾感很重的图中先把暗通道去雾做了再接 MSRCR顺序反了会得到一张颜色诡异、高亮区域全是光晕的图。5. 基于opencv的水下图像增强与修复常见问题与排查五类翻车现场这一章整理我在实际跑流程时反复遇到的五个典型问题全部按照现象、原因、解决的顺序来写。这些坑不是参数不好调而是大部分人对水下图像的物理特性缺少预期看到结果不对就盲目调参越调越歪。5.1 白平衡后整张图发红照片被修成“夕阳红”现象灰度世界白平衡在水下近景照片上跑完红色溢出沙子变成橙色拍出来的珊瑚礁像被晚霞整个盖住。原因灰度世界假设并不总是成立。浅水区或者补光灯距离目标很近时红光衰减不明显红通道均值没有低到需要大幅补偿的程度而自动增益计算依然把红通道拉到绿通道的水平蓝色通道又被压得很低于是红色轻松过曝。解决在增益计算后加一个上限判断红通道增益不超过 1.6蓝通道增益不低于 0.6如果浅水素材偏多可以直接禁用蓝通道补偿只做红通道增强。也可以把cv2.xphoto.createGrayworldWB()里的saturationThreshold从默认值调低避免高饱和区域参与增益计算。经验是先打印三通道均值如果蓝绿接近、红微低一点就应该用轻量修正而不是满血白平衡。5.2 CLAHE 出块状伪影鱼鳞纹理变成彩色噪点现象增强后图像出现棋盘格一样的接缝鱼身鳞片附近有彩色小斑点放大后噪声极其明显。原因tileGridSize太小或者clipLimit太大时每个分块独立做直方图均衡的结果在边界不连续直接在 BGR 三通道上分别做 CLAHE 更是会让同一像素的三通道增益不同产生彩色噪声。水下图像本身噪声就大这种块效应会被进一步增强。解决第一不要在 BGR 上逐通道做 CLAHE改到 LAB 空间只处理 L 通道第二clipLimit控制在 2.5 以下视频帧建议 1.5第三tileGridSize不要小于 8×8高速运动场景可以提高到 16×16。如果块状接缝已经出现把 L 通道先做 3×3 高斯模糊再加 CLAHE能软化分块边界代价是损失少量边缘锐度。5.3 inpaint 修复亮斑留下水痕边缘反而更糊现象用cv2.inpaint修复补光灯亮斑后亮斑是消失了但周围出现一圈半透明的水痕鱼尾巴边缘被抹成模糊的弧形。原因掩码把非亮斑区域也选了进来或者亮斑区域太大inpaintRadius3供不应求。更常见的原因是INPAINT_TELEA对大面积缺失区域会把靠近边界的背景信息过度加权扩散方向被高亮边缘带偏。解决用连通域过滤后的干净掩码不要直接阈值全图大块亮斑换用INPAINT_NS如果水痕还是明显把inpaintRadius降到 2。还有一个很实用的技巧inpaint 修复后在修复区域边界内外各缩进 2 个像素做一次 5×5 高斯模糊可以让过渡更平滑但不要对全图模糊否则细节会一起被磨掉。5.4 暗通道去雾后出现紫边和灰霾现象去雾后的水下照片颜色没有变干净反而多了紫色边条远景像涂了一层不均匀的灰浆。原因暗通道先验是为正常大气条件设计的水下红通道极度衰减导致暗通道里几乎所有位置都带蓝绿色残留估计出的“大气光”被拉向蓝绿。透射率图也因此整体偏低远景恢复过头紫边和灰霾就是这么来的。解决在暗通道去雾前先做一次灰度世界白平衡让红通道有最低限度的基数把omega从 0.95 降到 0.8 附近t_min设置到 0.2 以上不要让透射率无限接近 0。更彻底的做法是只对蓝绿通道估计透射率红通道用白平衡单独补偿这样就不会出现紫色边缘。如果你看到灰色雾层不消除检查cv2.blur(t, (patch, patch))这一步是否执行了透射率图不平滑时去雾结果常常会带着类似磨砂玻璃的脏感。5.5 Retinex 导致色彩断层和光晕双重打击现象MSRCR 输出在天空或者水体背景区域出现一圈一圈的色彩断层增强后的物体边缘有一圈白色光晕。原因水体背景区域像素值变化缓慢log 域的差值本来就小再做归一化拉伸时这些微小差值被放大成阶梯状的断层光晕则是高斯模糊半径不够大光照分量把亮度边缘的过渡信息当成物体边缘保留下来除不净。解决第一输出前不要用cv2.normalize的全局最大最小值拉伸改用固定增益和固定偏移比如gain1.0、offset10.0让背景区域的动态范围变化更平缓第二把 sigma 最小值从 15 提高到 30让高频尺度不那么激进第三光晕明显时对 MSRCR 输出再做一个限幅将整张图饱和度乘以 0.9光晕的白色感能被压掉不少。色彩断层一旦形成再用任何滤波器都很难救回来所以调参时要看中间结果而不是只看最终输出。6. 用一个可复现的验证流程收尾PSNR、SSIM 与无参考指标怎么选水下增强效果不能只靠眼睛看屏幕上觉得好换到另一个显示器可能就崩了。我自己的验证习惯是三步走先用 PSNR 和 SSIM 对有参考图的素材做客观评分再对没有参考图的真实水下素材用无参考指标做稳定性判断最后把增强前后的图做局部放大对比检查两类肉眼易忽略的失真。有参考图的场景比如实验室水池里放一块标准色卡同时拍模糊版和清晰版可以用下面这段代码快速算分。from skimage.metrics import structural_similarity as ssim def evaluate(ref, test): psnr_value cv2.PSNR(ref, test) gray_ref cv2.cvtColor(ref, cv2.COLOR_BGR2GRAY) gray_test cv2.cvtColor(test, cv2.COLOR_BGR2GRAY) ssim_value ssim(gray_ref, gray_test) return psnr_value, ssim_value这里我没有直接用cv2.PSNR之外的复杂实现因为它足够稳定。两条曲线如果在同一批素材上跑完后 PSNR 提升不到 2dB或者 SSIM 出现回落说明增强算法把纹理细节改坏了如果 PSNR 升了但 SSIM 降了往往是对比度提升带来的副作用这种增强在视觉上会显得“硬”不适合视频连续帧。真实水下数据没有参考图我一般用梯度信息做一个快速判断增强后的图用cv2.Laplacian(gray, cv2.CV_64F).var()计算清晰度这个方法虽然是老办法但对纹理恢复效果敏感能在不同参数组合中帮忙确定选择方向。另一个看颜色恢复是否过度的手段是把增强结果转换到 HSV 空间统计 S 通道的均值水下素材原本饱和度偏低增强后 S 通道均值暴涨超过原图两倍以上说明颜色已经失真。手头有能力再算一点色偏时可以自己实现简单的 UCIQE就是用 Lab 空间的色度方差和亮度对比度做加权组合实现代码不过几十行不需要引入额外模型。最后我习惯把原图、增强图、修复图的同一局部区域裁出来拼成三张并排图专门看两个位置鱼鳞或者礁石边缘有没有白色光晕背景水域有没有块状断层。这两处是算法最容易翻车的地方整图评分往往看不出来。早期我在这上面吃过亏给客户交付了一套参数整体效果很好结果对方拍了一组底部沙子素材块状伪影全出来了只能用返工教训换来了后面这一套验证顺序。建议你把参数和验证脚本绑在一起保存每次调完参数跑一遍结果稳定再下结论。希望帮到你。本文还有配套的精品资源点击获取