尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AOD-Net图像去雾实战:轻量级端到端卷积网络全流程解析

AOD-Net图像去雾实战:轻量级端到端卷积网络全流程解析 简介面向图像去雾研究与实战的一份完整资源包同时提供了基于暗原色先验的传统算法与AOD卷积网络的深度学习方法两条实现路径前者使用MATLAB编写并配有GUI界面后者基于Python与PyQt实现已经训练好的模型权重也包含在内。非常适合有一定编程基础、希望对比学习传统图像处理与深度学习去雾技术的读者。包内共76个文件涵盖28个jpg、13个png等测试图片8个m文件对应MATLAB代码6个py及8个pyc文件为Python实现另有1个pth模型、1份PDF报告以及可执行程序入口整体压缩包仅19.88MB。需要特别留意的是AOD神经网络的可执行程序体积较大约780M已另存于网盘包内附有打开说明。目前已有505人学习下载可用于算法复现、对比两种去雾思路的差异也可作为课程设计与毕业设计的参考。 图像去雾一直是 CV 里比较接地气的方向。我一开始接触 AOD-Net 也是因为一个实际的落地需求室外摄像头拍到的画面一到雾天就灰蒙蒙的不管人眼还是后续的检测算法都很头疼。后来在项目里试了多种方案最终选了 AOD 卷积网络做去雾。这篇博文就围绕这个实战项目展开把数据准备、模型结构、训练推理和踩坑记录都梳理一遍希望对想在工程里落地图像去雾的朋友有帮助。这个项目解决的是“有雾图像恢复清晰图像”的问题。和传统暗通道先验、直方图均衡这类方法比AOD-Net 最大特点是端到端训练输入一张有雾图直接输出一张去雾图不需要做中间的透射率估计和大气光估计省事很多。如果你正要开始做去雾或者想给自己的检测、识别流程加一个图像预处理模块这篇博文可以直接当参考。1. 项目整体设计与思路拆解1.1 图像去雾为什么难AOD-Net 的思路图像去雾本质上是一个病态逆问题。绝大多数方法都依赖大气散射模型I(x) J(x) * t(x) A * (1 - t(x))其中 I(x) 是有雾图像J(x) 是我们要恢复的无雾图像t(x) 是透射率A 是全局大气光。已知 I(x)要求 J(x)需要估计 t(x) 和 A。但满足这个方程的 J(x) 组合有无数种所以只能靠先验或学习来约束。传统暗通道先验方法会把问题拆成两步先估计透射率 t再估计大气光 A。每一步都会引入误差误差累积到最后图像容易出现偏色、光晕和块状伪影。AOD-Net 的做法是干脆不拆了把 t 和 A 组合成一个新的中间变量 K(x)网络直接预测 K(x)再通过一个简单公式恢复 J(x)。因为是一个整体映射误差可以反向传播到所有层训练也更稳定。AOD-Net 全称是 All-in-One Dehazing Network里面的“All-in-One”就是从这来的。项目实践下来这套思路最大的好处是模型够轻推理够快不需要像 GAN 那样折腾训练稳定性非常适合工程落地。1.2 项目选型分析为什么不用传统暗通道先验当初我在项目里其实先试了暗通道先验OpenCV 里写起来很快但一碰到天空区域就露馅。暗通道先验的假设是“无雾图像的局部区域里至少有一个通道的强度很低”但天空、白墙这类区域不满足这个假设估计出来的透射率会偏低导致去雾后天空区域发暗变灰甚至还出现光晕。后来也考虑过基于 GAN 的去雾模型效果是好看收敛也比较慢对训练数据量要求高。工程环境里往往没有那么多成对的有雾/无雾数据而且模型一大部署到边缘设备就麻烦。AOD-Net 使用纯卷积结构模型参数只有几万个原始版本大概 8 万参数左右相比动不动几十上百兆的大模型对显存和算力要求低很多。如果是做实时视频流预处理AOD-Net 的优势会非常明显。在 1080p 输入下用 GPU 推理单帧耗时可以压到几毫秒。CPU 推理也能接受小图大概几十毫秒一帧。这对摄像头场景特别友好。2. 环境准备与数据说明2.1 环境依赖与工具选择我用的是 PyTorch版本 1.13 和 2.x 都跑过差异不大。Python 建议 3.8 以上依赖这几个库就够了torch1.10 torchvision opencv-python numpy Pillow tqdm为什么选 PyTorch最直接的原因是调试方便。AOD-Net 结构简单动态图打印中间张量 shape 很直观。另外 PyTorch 生态里有现成的 AMP 混合精度支持后面优化训练速度会用到。如果你之前用过 TensorFlow其实也行但代码要对模型定义做一些迁移没必要。硬件方面训练建议至少有一块 8G 显存的 GPU。显存不够也不是不能跑可以把输入图像裁小到 240x240或者用 CPU 训练一个简单版本就是慢一些。推理环节对硬件要求很低普通笔记本 CPU 也能处理单张图片。2.2 数据集准备RESIDE 与自合成雾图做去雾训练最常用的数据集是 RESIDE里面包含大量合成有雾图像和对应的清晰图像。如果要复现论文效果建议用 Outdoor Training Set 或者 ITS 数据子集。不过 RESIDE 完整包下载体积比较大如果只想把流程跑通我们可以先自己合成一批雾图快速验证模型能不能收敛。合成雾图其实很简单找一批清晰图随机生成大气光 A取值在 0.7 到 1.0 之间的 RGB 值随机生成透射率 t可以按深度图设计也可以直接用随机图案平滑然后套用大气散射模型生成 I(x)。import cv2 import numpy as np def synthetic_haze(img, a0.9, t_min0.3, t_max0.8): # img: [H, W, 3] 0-255 无雾图 # a: 大气光强度t: 透射率图 H, W img.shape[:2] t np.random.uniform(t_min, t_max, (H, W)).astype(np.float32) # 对 t 做平滑避免出现过于锐利的边缘 t cv2.GaussianBlur(t, (15, 15), 0) t np.expand_dims(t, axis-1) # [H, W, 1] A np.ones((1, 1, 3), dtypenp.float32) * a img_f img.astype(np.float32) / 255.0 haze img_f * t A * (1.0 - t) return (np.clip(haze, 0, 1) * 255).astype(np.uint8)训练时把无雾图和合成的有雾图组成配对样本。合成数据虽然和真实雾天有差异但作为流程验证和模型预热足够了。后面如果要做正式训练再切到 RESIDE 即可。3. 模型结构与核心实现3.1 AOD-Net 的模型结构AOD-Net 结构不复杂核心是一个 K-estimation 模块加一个重构模块。K-estimation 模块里有一串卷积层输入有雾图像输出一个和输入同尺寸的 K(x) 图通道数也是 3因为 RGB 三个通道的大气光模型要分别处理。我写 PyTorch 版本时会用一个循环堆叠卷积层方便调整通道数。原始结构里有几个不同感受野的卷积大概思路是先用 1x1、3x3、5x5 的组合把特征提取出来再通过几层 3x3 卷积回归到 K(x)。为了避免梯度消失中间会有拼接操作把浅层特征和深层特征拼到一起。import torch import torch.nn as nn class AODNet(nn.Module): def __init__(self): super(AODNet, self).__init__() self.conv1 nn.Conv2d(3, 3, 1, padding0) self.conv2 nn.Conv2d(3, 3, 3, padding1) self.conv3 nn.Conv2d(6, 3, 5, padding2) self.conv4 nn.Conv2d(6, 3, 7, padding3) self.conv5 nn.Conv2d(12, 3, 3, padding1) self.relu nn.ReLU(inplaceTrue) def forward(self, x): x1 self.relu(self.conv1(x)) x2 self.relu(self.conv2(x1)) cat1 torch.cat([x1, x2], dim1) x3 self.relu(self.conv3(cat1)) cat2 torch.cat([x1, x2, x3], dim1) x4 self.relu(self.conv4(cat2)) cat3 torch.cat([x1, x2, x3, x4], dim1) k self.conv5(cat3) return k这里要说明一下这个实现是简化版核心结构没变。AOD-Net 最后的重构公式是J(x) K(x) * I(x) - K(x) b其中 b 一般取 1。K(x) 是由网络输出的 3 通道映射I(x) 是输入的有雾图。实现时就是逐像素乘加完全可微所以整个网络可以直接端到端训练。3.2 大气散射模型与 K 参数估计为什么把透射率和大气光合并成 K 能有效原因在前向计算时AOD-Net 没有显式约束 K 的物理含义而是让网络自己组合。但是在训练中网络会慢慢学到“如何把有雾图映射到无雾图”的组合关系。相比两步估计这种隐式的参数化更灵活也更容易优化。从数值角度看K 的通道数必须是 3因为 RGB 三个通道的恢复方程是独立的。如果大气光是灰度的三个通道可以共享结果但图像往往有颜色偏色分开估计能更好地处理真实雾天场景。训练时我们并不会有真实的 K 作为监督而是直接把 K 代入重构公式计算去雾图像和清晰图像之间的 MSE 损失。所以网络自己会找到最适合当前数据集的 K 分布。这比工程上手动调参要省力。3.3 损失函数与评估指标AOD-Net 原论文用的是 RGB 空间的欧氏距离MSE损失。代码里写起来就是loss torch.mean((dehazed - clear) ** 2)不过实际训练时我发现纯 MSE 容易让图像整体偏平滑边缘细节有点糊。后来我在项目中改为 L1 Loss 和 SSIM Loss 组合效果会明显锐利一些。L1 损失对离群值不敏感SSIM 损失关注结构相似度。权重可以设成 0.6 的 L1 加上 0.4 的 SSIM这个可以根据自己的数据集调整。评估指标一定要用 PSNR 和 SSIM。PSNR 峰值信噪比衡量像素误差越大越好SSIM 结构相似性从亮度、对比度、结构三个维度衡量越接近 1 越好。写评估脚本时记得先裁剪到 [0, 1] 或 [0, 255] 再计算否则数值会产生误解。4. 训练与推理实战4.1 训练脚本与关键配置这一部分我直接给出一套能跑通的训练配置我们实际项目就是基于这个配置改的。# train.py 核心片段 import torch from torch.utils.data import DataLoader from torchvision.transforms import Compose, ToTensor, Resize # 假设 HazeDataset 返回 (haze_img, clear_img) train_loader DataLoader(dataset, batch_size8, shuffleTrue, num_workers4, pin_memoryTrue) model AODNet().cuda() optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion torch.nn.L1Loss() for epoch in range(30): for step, (haze, clear) in enumerate(train_loader): haze haze.cuda() clear clear.cuda() k model(haze) b 1.0 dehazed k * haze - k b loss criterion(dehazed, clear) optimizer.zero_grad() loss.backward() optimizer.step() if step % 50 0: print(fEpoch {epoch} Step {step} Loss {loss.item():.6f})这里需要注意输入图像的归一化不要直接使用 ImageNet 的 mean/std因为去雾网络处理的是像素值域有明确物理含义。简单做法是把图像缩放到 [0, 1] 区间模型输出也在这个区间最后乘 255 转回 uint8。学习率我建议从 1e-3 开始每 10 个 epoch 衰减到 1e-4 左右。Batch size 根据显存情况调整8 比较稳妥。如果显存足够可以开到 16收敛速度会快一点。4.2 训练技巧与调参经验我踩过的第一个坑是收敛速度很慢。排查后发现是数据读取时没有做随机裁剪模型每次看到整张大图局部特征学习不够充分。后来改成训练时随机裁剪到 256x256效果提升非常明显。推理时再整图输入虽然训练和推理分辨率不同但卷积网络对尺寸不敏感。第二个经验是数据增强。去雾模型很容易过拟合到某种透射率分布。我加了一些简单的随机旋转、水平翻转、颜色抖动让模型适应更多场景。颜色抖动不能太强否则会破坏雾图合成时的一致性。第三个经验是梯度裁剪。训练早期如果学习率偏大loss 可能出现突刺加一句torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)能显著提升稳定性。训练结束后建议保存两部分结果一是模型权重二是几组典型样张。这样后面调参时可以直接对比不用每次重新跑一遍推理。4.3 推理脚本与结果后处理推理脚本比训练简单加载权重后直接前向即可。注意 OpenCV 读图是 BGR 顺序PyTorch 训练时一般用 RGB这里要转换一下否则输出色彩会偏蓝偏黄看着很奇怪。import cv2 import torch from PIL import Image from torchvision.transforms import ToTensor model AODNet().cuda() model.load_state_dict(torch.load(aodnet.pth, map_locationcuda)) model.eval() def dehaze_image(model, img_path): img_bgr cv2.imread(img_path) img_rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) tensor ToTensor()(img_rgb).unsqueeze(0).cuda() # [1,3,H,W] 归一化到 [0,1] with torch.no_grad(): k model(tensor) b 1.0 dehazed k * tensor - k b result dehazed.squeeze(0).permute(1, 2, 0).cpu().numpy() result cv2.cvtColor(result, cv2.COLOR_RGB2BGR) result (result * 255.0).astype(uint8) cv2.imwrite(dehazed.jpg, result)输出前一定要做 clip 操作不然可能会出现不均匀的亮点。我习惯写成np.clip(result * 255, 0, 255)防一手。推理模式记得切换model.eval()里面如果有 BatchNorm 层行为和训练时不一样。虽然 AOD-Net 原始结构没有 BN但如果你加了 BN 做改动这一步不能省。5. 常见问题与排查技巧实录5.1 训练 loss 不下降或震荡如果训练一开始 loss 就在几个数值之间来回跳先别急着调模型。第一步检查数据读取把一张有雾图和对应的清晰图打印出来确认通道顺序和归一化正确。很多问题是 BGR 和 RGB 混用导致的模型要学的东西完全错乱loss 当然降不下来。第二步检查学习率。学习率 1e-3 对 Adam 来说偏大如果数据量小可以降到 5e-4 或 3e-4。学习率太大表现为 loss 整体下降但不稳定偶尔出现一个尖峰。第三步用一个小数据集做“过拟合测试”只拿 8 张图训练 100 个 step如果 loss 能从 0.2 降到 0.05 以内说明模型和代码没问题问题在数据或学习率配置上。这一步排查效率极高。5.2 输出图像偏暗或颜色异常这是新手最容易遇到的问题。输出偏暗最直接的原因是模型输出的像素值没有限制在 [0,1] 范围内一些负值或大于 1 的值在转 uint8 时被截断导致整体亮度偏移。解决方法是后处理时加上 clip。颜色异常往往是通道顺序问题。PyTorch 训练时用 RGBOpenCV 读取是 BGR。如果忘记转换去雾后的画面红色和蓝色通道就会互换看起来像是严重的偏色。这个问题在项目初版几乎必踩排查时可以先打印 result 的通道均值对比原图能快速定位。还有一种情况是合成雾图时大气光 A 设置不对。如果 A 的 RGB 三个通道不一致生成的雾图本身带有颜色偏色模型去雾后也会残留偏色。建议先使用灰度大气光做验证比如 A0.85等流程跑通再尝试彩色大气光。5.3 显存不足与推理速度优化AOD-Net 本身非常轻量但训练时如果把输入尺寸设置为 1024x1024显存还是会吃紧。常规做法是训练时随机裁剪到 256x256推理时再全图输入。如果你的显存小于 6G可以进一步裁剪到 224x224或者降低 batch size。推理速度优化我建议从三方面入手。第一模型转半精度Pytorch 开启 AMP 或者直接把权重.half()在支持 FP16 的 GPU 上有明显加速。第二导出 ONNX用 ONNX Runtime 或 TensorRT 推理部署也更方便。第三如果只是做视频流可以加一个简单的帧缓存把上下帧相同区域的结果复用减少重复计算。我实际在 Jetson Nano 上部署过这个模型输入 640x480 的分辨率半精度推理可以做到 30 FPS 左右完全能满足实时视频处理需求。这个性能水平是很多大模型给不了的。6. 一些额外的工程建议6.1 部署到实际应用的注意点实际应用中不会只处理单张图片视频流才是常见场景。AOD-Net 对逐帧去雾是有效的但连续帧之间亮度容易有轻微波动。如果直接看视频会感觉画面一跳一跳的。一个轻量方案是做时域平滑对相邻几帧的 K 输出做指数滑动平均。这样既保留去雾效果又不会让亮度突变太明显。另外真实雾天场景和合成数据集有分布差异。如果项目效果不理想可以准备一些真实雾天图像用“无参考图像质量评价”来筛选效果好的图加入训练做微调。无参考指标有很多但最简单的方式是直方图对比看去雾后图像对比度是否提升、颜色是否自然。6.2 后续扩展方向AOD-Net 虽然简单但作为图像预处理器和下游任务结合很顺畅。我在项目中把它接到目标检测模型前面雾天下检测精度提升了不少。把去雾模块和检测模型端到端一起训练也是可行的只是工程复杂度会高一些。还有一种扩展是改成视频去雾。可以引入时序信息用 3D 卷积或时序卷积模块但会牺牲一些轻量性。如果算力允许可以尝试把 AOD-Net 作为基础模块在时间维度上做轻量融合效果会更平稳。最后分享一个小技巧平时把训练好的模型导出成 ONNX 格式调试其他项目时可以直接用 ONNX Runtime 跑不依赖 PyTorch 环境。我在项目里就经常这么干部署环节省了很多麻烦。这个项目整体不难但里面涉及的数据处理、模型设计、训练调参、部署优化都是实际工程里反复要用的内容。把这些点吃透后续再接触其他图像恢复任务思路会顺很多。本文还有配套的精品资源点击获取
返回列表