简介:面向图像处理学习者的Python实战资料,聚焦水下图像增强与修复,针对水下散射与吸收导致的图像模糊、偏色、对比度不足等问题,借助OpenCV实现去噪、色彩校正、对比度提升、去雾等关键处理步骤。压缩包内共4个文件,包含可直接运行的Python脚本、配套说明文档、一张示例效果图以及License文件,整体大小约210KB,结构紧凑便于快速上手。已有907人学习该资源,适合具备基础Python与OpenCV知识、希望动手实践水下视觉处理的开发者。通过源码可了解BGR与HSV空间转换、直方图均衡化、滤波去噪等具体操作,示例图直观展示处理前后效果,说明文档帮助梳理算法流程与参数调整思路,是一份轻量但覆盖核心流程的实用资源。
1. 基于 OpenCV 的水下图像增强与修复:先修物理,再谈算法
水下拍出来的照片十有八九是「绿蒙蒙、雾蒙蒙、黑蒙蒙」三选一,这根本不是相机的问题,而是水里的光衰减把红色吃掉了、把散射噪声留给了传感器。很多人拿到 OpenCV 第一件事就是调cv2.equalizeHist(),结果对比度是拉高了,色彩却更假了。这个项目拆开看,核心其实是「先理解水的光学特性,再选对 OpenCV 的增强链路」——先去噪、再校色、最后做对比度和去雾,每一步都有对应函数,但顺序错了效果就完全不对。适合正在做水下视觉、机器人巡检、海洋生物识别,或者单纯想系统掌握图像处理管线的开发者,随项目附带的image enhancement and restoration.py就是一条可以直接跑的完整流程。本文会按实际处理顺序,把每一步的原理、参数和踩坑点都拆开讲。
2. 理解水下光衰减:直方图不会骗人
2.1 为什么水下图像总是偏绿偏蓝
水的光学衰减不是均匀的。红光波长长,能量低,在水里衰减最快,通常在水下 5 米左右红色就基本消失了;绿光和蓝光波长短,透射能力强,所以水下照片的主色调永远是绿或蓝。这个物理事实直接决定了增强策略:你不可能靠一个全局白平衡把一张偏绿的图救回来,因为绿通道并不是过度曝光,而是另外两个通道太弱了。
处理水下图像的第一步,不是调参,而是「读数据」。我会在跑任何增强算法之前,先用 numpy 把三个通道的直方图、均值和标准差打出来,搞清楚这张图的色彩偏移到底有多严重。下面的代码可以快速定位问题:
import cv2 import numpy as np def analyze_underwater_image(img_path): img = cv2.imread(img_path) if img is None: raise FileNotFoundError(f"无法读取图像: {img_path}") # BGR 顺序,注意 OpenCV 默认通道排列 channels = ['Blue', 'Green', 'Red'] for i, ch in enumerate(channels): data = img[:, :, i] mean_val = np.mean(data) std_val = np.std(data) print(f"{ch} 通道 -> 均值: {mean_val:.1f}, 标准差: {std_val:.1f}") # 直方图可视化,直接用 matplotlib 输出到当前目录 import matplotlib.pyplot as plt colors = ('b', 'g', 'r') for i, color in enumerate(colors): hist = cv2.calcHist([img], [i], None, [256], [0, 256]) plt.plot(hist, color=color) plt.savefig('channel_histogram.png', dpi=100) print("直方图已保存为 channel_histogram.png")这段代码先把三通道统计量打印出来,再画一张直方图叠图。逻辑说明:水下图像的典型表现是 B 通道均值显著高于 R 通道,且 R 通道直方图集中在低灰度区间。如果 R 通道均值低于 50,说明红色衰减已经非常严重,后续必须做红通道补偿,而不是简单调色温。
参数说明:cv2.calcHist的第三个参数传i代表通道索引,OpenCV 里 0=B、1=G、2=R,这个顺序容易记反,输出时我用channels列表做了明文映射。[256]是直方图 bin 数量,[0, 256]是灰度范围,这两项一般情况下不需要改。
2.2 红通道补偿:最简单的色彩修复手段
读懂了直方图,下一步就是对症下药。红通道补偿的思路很直接:红通道衰减最严重,就用蓝绿通道的平均值来估算红通道缺失的能量,然后做加权融合。这是很多水下增强论文里 White Balance 方法的简化版,但工程上够用。
def red_channel_compensation(img, alpha=0.05): """ 基于蓝绿通道均值对红通道做线性补偿 alpha: 补偿强度,建议范围 0.02 ~ 0.15,过大容易偏红 """ b, g, r = cv2.split(img) # 红通道缺失能量的估计:蓝绿均值的加权 compensation = alpha * ((b.astype(np.float32) + g.astype(np.float32)) / 2.0) r_compensated = r.astype(np.float32) + compensation # 用蓝通道的均值做锚点,避免红通道溢出 b_mean = np.mean(b) r_new = np.clip(r_compensated, 0, 255).astype(np.uint8) # 补偿后再做一次线性拉伸,把红通道的动态范围拉开 min_val, max_val = np.min(r_new), np.max(r_new) if max_val - min_val > 0: r_normalized = (r_new - min_val) * (255.0 / (max_val - min_val)) r_final = r_normalized.astype(np.uint8) else: r_final = r_new return cv2.merge([b, g, r_final])逻辑说明:核心是compensation这一行——蓝绿通道的平均值代表红通道本来应该有的能量水平,乘以alpha系数决定补偿力度。补偿之后红通道可能整体偏暗,所以再做一次最小最大拉伸,把动态范围铺满。
参数说明:alpha是唯一需要调的参数。浅水图(5 米以内)红衰减不严重,alpha=0.03就够了;深水图(10 米以上)可以加到0.08。超过0.15整张图会明显偏红,把原本的色偏问题反向放大。注意cv2.split和cv2.merge都是 BGR 顺序,合并时别把顺序搞反。
2.3 用灰度世界假设做二次校正
红通道补偿解决的是「红色缺失」,但水下图像往往还有整体色温偏移。灰度世界假设认为:一张色彩正常的图像,三个通道的均值应该接近。这个假设在绝大多数自然场景下成立,虽然水下场景偏离比较大,但作为补偿后的二次校正非常合适。
def gray_world_correction(img): """灰度世界假设:三通道均值对齐""" b, g, r = cv2.split(img) b_avg = np.mean(b) g_avg = np.mean(g) r_avg = np.mean(r) # 取三通道均值的均值作为目标 target = (b_avg + g_avg + r_avg) / 3.0 # 按比例缩放各通道 b_new = np.clip(b * (target / b_avg), 0, 255).astype(np.uint8) g_new = np.clip(g * (target / g_avg), 0, 255).astype(np.uint8) r_new = np.clip(r * (target / r_avg), 0, 255).astype(np.uint8) return cv2.merge([b_new, g_new, r_new])逻辑说明:这套逻辑就是「谁弱补谁」——三通道均值偏离目标越远,缩放系数越大。红通道补偿刚做完时红通道均值通常还是偏低,灰度世界假设会再拉一把,但这里有个边界问题:如果补偿强度alpha调得太大,红通道均值反而会超过蓝绿,灰度世界又会把红色往下压,两个函数互相打架。
参数说明:这个函数没有参数,但它的效果取决于上一个函数的输出。我的经验是:先用alpha=0.05补偿,跑灰度世界,再看直方图——如果红通道均值仍然比蓝绿低 30% 以上,再回过去把alpha加到0.08。两步配合的调参顺序比单步反复试要快得多。
3. 对比度增强的层次:从全局均衡到 CLAHE
3.1 全局直方图均衡化的问题
cv2.equalizeHist()大概是 OpenCV 里被用得最狠也最容易被误用的函数。它对整张图的灰度直方图做重新分布,把集中的灰度区间拉伸到全范围。听起来没问题,但在水下场景里,图像往往是大面积蓝色背景加上小面积的前景物体——全局均衡会把背景的轻微噪声放大成明显的色块,而前景物体因为像素占比太小,根本分不到多少灰度级。
更重要的是:cv2.equalizeHist()只接受单通道灰度图,处理彩色图像必须逐通道做。水下图像三个通道的直方图分布差异很大,逐通道均衡会彻底破坏通道间的相对关系,导致严重的色彩偏差。很多新手在这里翻车——均衡完对比度确实高了,但是整张图变成了诡异的青紫色。
# 错误示范:逐通道全局均衡 b, g, r = cv2.split(img) b_eq = cv2.equalizeHist(b) g_eq = cv2.equalizeHist(g) r_eq = cv2.equalizeHist(r) wrong_result = cv2.merge([b_eq, g_eq, r_eq]) # 输出:色彩严重偏移,背景噪声被放大逻辑说明:这段代码的问题在于三个通道被独立拉伸,通道间的强弱关系被破坏。水下图像的蓝通道本来均值就高,均衡后它会占据更多的灰度区间,反而加深了蓝色色调。
3.2 CLAHE:自适应直方图均衡化的正确参数
正确的做法是用 CLAHE(Contrast Limited Adaptive Histogram Equalization),也就是cv2.createCLAHE()。它的关键改进有两个:一是把图像分成若干个小块分别均衡,让局部对比度自适应;二是限制了直方图的截断阈值,避免单个灰度级被过度放大。adaptiveEqualizeHist 是摘要里的口误,OpenCV 里并没有这个函数,正确名称是createCLAHE。
def clahe_enhance(img, clip_limit=2.0, tile_grid_size=(8, 8)): """ CLAHE 对比度增强 clip_limit: 直方图裁剪阈值,越大对比度越强,但噪声也越明显 tile_grid_size: 分块网格,8x8 是常用默认值 """ # 转换到 LAB 色彩空间,只增强 L 通道,保留色彩信息 lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b_ch = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=tile_grid_size) l_enhanced = clahe.apply(l) merged = cv2.merge([l_enhanced, a, b_ch]) result = cv2.cvtColor(merged, cv2.COLOR_LAB2BGR) return result逻辑说明:这里的关键是先把图像从 BGR 转到 LAB 色彩空间。LAB 的 L 通道只包含亮度信息,A 和 B 通道包含色彩信息——只对 L 通道做增强,色彩不会偏移。这是处理彩色图像时 CLAHE 的标准做法,比逐通道处理要安全得多。
参数说明:clipLimit是直方图裁剪阈值,OpenCV 默认值是 40.0,但那是给普通图像用的,水下图像噪声大,我一般从 2.0 起步。tileGridSize是分块大小,8x8 意味着把图像切分成 64 个小块。对低分辨率水下图像(比如 640x480),8x8 的块足够;如果图像超过 2000 万像素,可以考虑 16x16,避免块太小导致局部增强过头。
3.3 多尺度对比度增强:让细节层浮出来
CLAHE 处理完,整体对比度有了,但水下图像的细节往往集中在某个特定的尺度范围——近处是沙地纹理,中景是鱼群,远景是模糊的礁石轮廓。单次 CLAHE 只能拉出一个尺度的细节,所以我一般会再做一次多尺度处理:分层提取细节,加权融合回去。
def multi_scale_detail_enhance(img, sigma_list=(3.0, 9.0, 15.0)): """ 多尺度高斯差分细节增强 sigma_list: 高斯模糊的 sigma 序列,控制提取细节的尺度 """ base = img.astype(np.float32) detail_sum = np.zeros_like(base) for sigma in sigma_list: blur = cv2.GaussianBlur(base, (0, 0), sigmaX=sigma) detail = base - blur # 原图减去模糊图 = 该尺度下的细节 detail_sum += detail # 细节层叠加回原图 enhanced = base + detail_sum * 0.5 return np.clip(enhanced, 0, 255).astype(np.uint8)逻辑说明:高斯模糊去掉的是某个尺度以下的细节,原图减去模糊图就得到了这个尺度上的细节层。sigma 越小,提取的细节越精细;三个尺度叠加,相当于把大中小三个层次的纹理都拉出来。乘0.5是细节增益系数,这个值要谨慎,超过 1.0 会出现明显的 Halo 效应——物体边缘会有白边。
参数说明:sigma_list的三元组不是固定的,小图(边长 500 以内)用(2.0, 5.0, 10.0)就够了,大图可以像代码里那样用(3.0, 9.0, 15.0)。要注意:CLAHE 已经做了一次对比度增强,这里再加细节叠加,等于双重放大,如果原图噪声偏大,这一步会先把噪声也放大。所以我的管线里先去噪、再 CLAHE、最后再叠加细节。
4. 去雾、边缘检测与图像恢复:让水下目标现出轮廓
4.1 暗通道先验去雾的水下适配
水下图像和雾天图像的物理模型高度相似——水中的悬浮颗粒对光的散射,等效于大气中的雾霾。所以暗通道先验(Dark Channel Prior)去雾算法可以直接迁移到水下,只是参数要调整。暗通道先验的核心假设是:清晰图像中至少有一个颜色通道在某些像素处的值趋近于零。
def underwater_dehaze(img, omega=0.95, t0=0.1, window_size=15): """ 基于暗通道先验的去雾实现 omega: 去雾强度,取 0~1 t0: 透射率下限,防止分母过小导致噪声爆炸 window_size: 暗通道计算的滤波窗口 """ img_f = img.astype(np.float32) / 255.0 # 1. 计算暗通道:每个像素取三通道最小值,再做最小值滤波 dark_channel = np.min(img_f, axis=2) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (window_size, window_size)) dark_channel = cv2.erode(dark_channel, kernel) # 2. 估算大气光:取暗通道最亮的前 0.1% 像素在原图中的平均亮度 h, w = dark_channel.shape pixels = dark_channel.flatten() num_pixels = int(h * w * 0.001) indices = np.argsort(pixels)[-num_pixels:] atmosphere = np.mean(img_f.reshape(-1, 3)[indices], axis=0) # 3. 估算透射率 transmission = 1.0 - omega * dark_channel # 4. 透射率下限约束 transmission = np.clip(transmission, t0, 1.0) transmission = cv2.blur(transmission, (15, 15)) # 平滑透射率图 # 5. 恢复图像 restored = np.zeros_like(img_f) for i in range(3): restored[:, :, i] = (img_f[:, :, i] - atmosphere[i]) / transmission + atmosphere[i] return np.clip(restored * 255.0, 0, 255).astype(np.uint8)逻辑说明:流程分五步走——暗通道计算、大气光估算、透射率估算、平滑、恢复。暗通道用cv2.erode实现最小值滤波,这是 OpenCV 里效率最高的做法。大气光取暗通道最亮的 0.1% 像素,避免个别噪点干扰估计。
参数说明:omega=0.95是经典去雾论文里的推荐值,表示保留 5% 的雾量,避免画面过锐;在水下透明度低的场景我建议降到0.85,保留更多水体的自然散射,否则图像会黑得不像水下。t0=0.1是透射率下限,如果设成 0,有些像素除以接近 0 的透射率会直接爆出极亮的噪点。水下场景里,window_size=15比经典的 15x15 更合适,因为水下的悬浮颗粒更大、散射更严重。
4.2 Canny 边缘检测在水下场景的阈值策略
去雾之后,边缘检测才有意义。cv2.Canny()的两个阈值是传统玄学参数——网上到处是「高阈值是低阈值的 2~3 倍」的说法,但水下场景直接套这个经验很容易失效,因为水下边缘的梯度响应普遍偏弱。
def adaptive_canny(gray_img, sigma_ratio=0.33): """ 基于图像统计信息的自适应 Canny 阈值 sigma_ratio: 控制上限,常用区间 0.2~0.4 """ # 自动计算阈值 v = np.median(gray_img) lower = int(max(0, (1.0 - sigma_ratio) * v)) upper = int(min(255, (1.0 + sigma_ratio) * v)) # 高斯去噪,Canny 内部不自动去噪 blurred = cv2.GaussianBlur(gray_img, (3, 3), 0) edges = cv2.Canny(blurred, lower, upper) return edges, lower, upper逻辑说明:np.median取整张灰度图的中值,然后以中值为中心对称展开两个阈值。这个做法的合理性在于:水下图像的梯度分布集中在中低区间,用中值做锚点比用固定阈值(比如 50/150)更贴合场景。
参数说明:sigma_ratio=0.33意味着阈值窗口是中值上下各三分之一。水下弱对比度场景,我会把sigma_ratio调到0.5让阈值窗口更大,能多检出弱边缘,但代价是噪声也被当成边缘选进来;如果后续要做目标识别,宁可漏检也别多检,降到0.2更稳。Canny 之前必须做高斯模糊,否则梯度噪声会让边缘断成碎片。
4.3 图像恢复的边界:OpenCV dnn 模块能做多少
对于腐蚀特别严重的图像,基于传统算法的增强到了极限,剩下的路只有一条:数据驱动的图像恢复。OpenCV 的cv2.dnn.readNetFromModelOptimizer可以加载 ONNX 格式的模型,配合超分辨率或去噪的预训练权重做推理。但这里要把期望值放对——OpenCV dnn 模块不是做训练的,只能做推理,而且对输入尺寸有固定要求。
def dnn_super_resolution(img_path, model_path='edsr_x2.onnx'): """ 使用 OpenCV dnn 模块加载超分模型 模型: EDSR 或 FSRCNN,ONNX 格式 """ net = cv2.dnn.readNetFromONNX(model_path) img = cv2.imread(img_path) h, w = img.shape[:2] # OpenCV dnn 需要固定输入尺寸,这里做 padding 到 4 的倍数 new_h = h + (4 - h % 4) % 4 new_w = w + (4 - w % 4) % 4 padded = cv2.copyMakeBorder(img, 0, new_h - h, 0, new_w - w, cv2.BORDER_REFLECT) # 转换 blob 并推理 blob = cv2.dnn.blobFromImage(padded, scalefactor=1.0/255.0, size=(new_w, new_h), swapRB=False) net.setInput(blob) output = net.forward() # 输出形状是 1x3xCxW,需要转回 HWC result = output[0].transpose(1, 2, 0) result = np.clip(result * 255.0, 0, 255).astype(np.uint8) result = result[:h, :w] # 去掉 padding return result逻辑说明:blobFromImage把图像转成深度学习需要的 NCHW 格式,swapRB=False是因为 OpenCV 读图是 BGR,而我们不想把通道顺序搞乱。推理出来的output是归一化的浮点张量,要乘以 255 再转回 uint8。
边界提醒:这个方案只做 2 倍超分,EDSR_x2 模型约 38MB,运行一次在 CPU 上要 10 秒以上。更重要的是,超分不是增强——模糊的水下图像超分后依然是模糊的,只是像素变多。更实际的做法是先用第 2 章和第 3 章的管线做色彩和对比度增强,超分只用于最后放大识别目标。
5. 避坑指南:水下图像增强的五个翻车现场
5.1 直方图均衡化后色彩偏紫
现象:逐通道equalizeHist()之后,图像对比度确实提高了,但整张图蒙上一层紫色调,看着非常假。
原因:三个通道被独立拉伸,B、G、R 的灰度映射关系被破坏。水下图蓝绿通道均值高,均衡后它们占据了更多的灰度级,而红通道被压到低段,三通道失衡。
解决:放弃逐通道均衡,改用 LAB 色彩空间,只对 L 亮度通道做 CLAHE。A、B 色彩通道保持原样,色偏问题直接消失。
5.2 先增强再去噪,噪声被放大成颗粒
现象:处理后的图像水底沙地部分全是细密的噪点,像是加了一层「雪花」滤镜。
原因:典型的流程顺序错误——先做了对比度增强,再去做去噪。对比度增强会把原本的传感器噪声一并放大,之后的去噪算法面对已经被放大的噪声,要么去不干净留下残影,要么把细节纹理一起抹掉。
解决:处理顺序必须是「先降噪、再校色、最后增强」。降噪用cv2.fastNlMeansDenoisingColored(),它的参数h控制滤波强度,水下图像一般设h=5到h=8,太大容易把鱼鳞等细微纹理磨平。
5.3 去雾后图像偏黑,暗部完全看不见
现象:暗通道先去雾之后,整张图比原图更暗,阴影区域直接变成黑色块。
原因:暗通道先验假设「至少一个通道的局部最小值趋近零」,但水下图像的红通道衰减严重,暗通道计算时红通道天然接近 0,导致透射率整体偏低。透射率一低,恢复公式里的分母就小,暗部直接被压死。
解决:降低omega到0.85左右,给透射率留更多余量;同时把t0从0.1提高到0.2,限制透射率下限。如果图像本身就黑,先做一次 gamma 校正(cv2.LUT配合幂函数),把整体亮度抬上来再去雾。
5.4 红通道补偿过度,珊瑚和鱼变成洋红色
现象:补偿之后的图像,红色的物体饱和度溢出,原本是橙色的珊瑚变成了荧光粉。
原因:alpha系数设得太高是直接原因,但根本原因是补偿公式里的compensation没有考虑像素本身的亮度——暗像素被加上了同样的补偿量,信噪比低的区域失真最明显。
解决:给补偿加一个亮度掩码,亮像素补偿强,暗像素补偿弱:
def red_channel_compensation_improved(img, alpha=0.05): b, g, r = cv2.split(img) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 亮度归一化到 0~1,作为补偿强度的权重 weight = gray.astype(np.float32) / 255.0 weight = np.clip(weight, 0.2, 1.0) # 暗部至少保留 20% 补偿 compensation = alpha * ((b.astype(np.float32) + g.astype(np.float32)) / 2.0) r_new = r.astype(np.float32) + compensation * weight return cv2.merge([b, g, np.clip(r_new, 0, 255).astype(np.uint8)])参数说明:weight的裁剪下限设为0.2,意味着最暗的像素也保留 20% 的补偿量,避免暗部完全失去红色。这段代码可以直接替换原来的补偿函数,不用改管线下游。
5.5 Canny 边缘断裂成一截一截
现象:检测出的边缘不是连续的轮廓线,而是断断续续的短线,后续做轮廓检测根本拼不出完整物体。
原因:水下图像对比度低,梯度幅值普遍偏小,Canny 的滞后阈值窗口太窄,部分弱边缘像素被当成噪声剔除,导致边缘断裂。
解决:边缘检测之前,先对灰度图做一次形态学梯度增强——用cv2.morphologyEx的cv2.MORPH_GRADIENT操作膨胀图和腐蚀图相减,把弱边缘的梯度拉大,同时缩小sigma_ratio到0.25,让阈值窗口收窄。另外一个更实用的技巧是:如果目标是识别大块物体,直接调低 Canny 的高阈值到(lower+upper)/2,宁可多检一些背景,也别让前景断成碎片。
6. 从单张处理到批量管线:最后的落地技巧
单张验证通过的流程,在真正投入使用时一定会遇到新问题——几十张图连续跑,有的偏蓝、有的偏绿、有的简直像在墨水里拍的,固定参数根本扛不住。我的做法是:把整条管线封装成一个函数,加入「按图像统计量自适应调参」的逻辑,然后批量处理,每张图单独输出增强前后对比图,方便回头检查哪一步出了问题。
import os import cv2 import numpy as np import glob def pipeline_auto(img_path, output_dir): img = cv2.imread(img_path) # 1. 统计量决定走向 b_mean = np.mean(img[:, :, 0]) g_mean = np.mean(img[:, :, 1]) r_mean = np.mean(img[:, :, 2]) # 2. 依据统计量决定补偿强度 if r_mean < b_mean * 0.5: alpha = 0.10 # 红色严重缺失 elif r_mean < b_mean * 0.7: alpha = 0.06 # 中等衰减 else: alpha = 0.03 # 轻微衰减 # 3. 执行完整管线 step1 = cv2.fastNlMeansDenoisingColored(img, None, h=6, hColor=6) step2 = red_channel_compensation_improved(step1, alpha=alpha) step3 = gray_world_correction(step2) step4 = clahe_enhance(step3, clip_limit=2.5, tile_grid_size=(8, 8)) step5 = multi_scale_detail_enhance(step4, sigma_list=(3.0, 9.0, 15.0)) # 4. 保存结果和对比图 base = os.path.basename(img_path) name = os.path.splitext(base)[0] cv2.imwrite(os.path.join(output_dir, f"{name}_enhanced.jpg"), step5) # 5. 输出统计量,方便后续调优 return { 'b_mean': round(b_mean, 1), 'r_mean': round(r_mean, 1), 'alpha': alpha } # 批量处理目录下所有 jpg img_dir = './underwater_samples/' out_dir = './enhanced_output/' os.makedirs(out_dir, exist_ok=True) for img_path in sorted(glob.glob(os.path.join(img_dir, '*.jpg'))): info = pipeline_auto(img_path, out_dir) print(f"处理 {os.path.basename(img_path)}: alpha={info['alpha']}, " f"B均值={info['b_mean']}, R均值={info['r_mean']}")逻辑说明:第 2 步是根据红蓝均值比自动选择alpha,这就是把前面人工调参的经验固化成规则。第 5 步输出统计量,跑完一轮后回头检查alpha分布——如果大部分图的alpha都落在0.08以上,说明这批数据普遍偏深水,那就应该考虑在前置环节加一步全局白平衡。
复盘习惯:从那以后我每次跑批量处理,都强制让程序把所有中间步骤结果和参数表写进一个带时间戳的文件夹里。参数选得对不对不是看最终效果好不好看,而是看每个环节的输出有没有引入伪影——我见过太多调参调了三天,最后发现是第一步降噪的h值设大了,把细节全抹平了。所以,别偷懒,中间结果一定要落盘。希望这份实战拆解能帮你在水下图像增强这条路上少走几步弯路,参数要在你真实的数据集上重新验证,但处理顺序和排查路径是通用的。
本文还有配套的精品资源,点击获取