尧图精选

基于Pytorch的对偶生成对抗网络图像去雾实践

🕒 发布时间:2026/8/31 11:30:50 📁 来源:尧图网络
简介本资源是基于PyTorch实现的对偶生成对抗网络Dual GAN图像去雾项目面向计算机视觉方向本科生及研究生特别适用于毕业设计、课程设计与深度学习实战训练。项目完整复现了端到端雾霾图像恢复流程涵盖数据加载、双路径生成器与判别器构建、对抗损失与循环一致性损失联合优化等核心模块代码含详细中文注释配套README.md说明文档清晰阐述原理、环境配置与运行步骤。压缩包共25个文件包含10个Python源码如Generator.py、Discriminator.py、train.py、predict.py、6张训练/测试效果可视化图PNG、5张实测去雾结果图JPG、2个预训练模型权重.pkl及辅助工具脚本整体大小为21.24MB。目前已有223人学习下载项目经导师指导并高分通过毕业答辩所有模块均严格调试可直接运行提供从数据预处理、模型训练到单图推理的全流程实践支撑。 雾天拍出来的照片总是灰蒙蒙的对比度低、细节丢失更麻烦的是很多后续视觉任务——目标检测、语义分割、无人驾驶感知——在这种输入下都会明显掉点。传统去雾方法里暗通道先验DCP最出名但它对天空区域容易过曝在真实复杂场景下鲁棒性不够。这几年GAN被大量用在图像翻译任务上于是就有了一个很自然的思路把“有雾图像”和“清晰图像”当成两个视觉域训练一个生成对抗网络做跨域转换这正好是Pytorch项目“基于Pytorch实现对偶生成对抗网络来实现图像去雾”在做的事。这个项目最核心的亮点是它不依赖成对的雾图/清晰图训练数据。真实世界里我们很难拍到同一场景“有雾”和“无雾”两张完全对齐的照片而对偶生成对抗网络DualGAN/CycleGAN类结构正是为这种非成对场景设计的。项目里包含了完整的Python源码、项目说明文档和详细的代码注释对想入门GAN图像翻译、或者需要把去雾模块集成到自己的视觉流程里的同学来说是一份很适合拿来跑通、改造和二次开发的参考实现。我在复现过程中把模型结构、损失函数、训练策略和踩过的坑都梳理了一遍这篇文章就按这个思路来拆解整个项目。1. 项目核心思路与整体设计1.1 对偶生成对抗网络到底在做什么如果你已经接触过普通GAN那对偶GAN的理解门槛很低。常规GAN只有一个生成器和一个判别器生成器负责伪造样本判别器负责区分真假两者对抗训练最后生成器学会生成以假乱真的图片。但这种方式只能做“生成”做不了两个图像域之间的转换。对偶GAN的“对偶”两个字体现在它用两对生成器-判别器同时训练生成器G_A_B把A域图像有雾图转成B域风格清晰图生成器G_B_A把B域图像清晰图转成A域风格有雾图判别器D_A判断输入是不是真实的A域图像判别器D_B判断输入是不是真实的B域图像我用一个翻译器的类比来解释G_A_B相当于一个中译英翻译员G_B_A相当于英译中翻译员D_A和D_B分别是两边的“母语评审”。光靠“中译英”和“英译中”分别训练两边很容易乱来——翻译员可能随便编内容只要骗过评审就行。于是对偶GAN加了一个关键约束把一篇中文翻译成英文再把英文翻回中文结果必须和原中文尽量一致。这就是循环一致性约束它保证了转换过程不丢失原图的内容和结构。对去雾任务来说就是去雾之后的图片再“加雾”回去应该变回原来的雾图。1.2 去雾问题如何用图像转换建模图像去雾为什么要套用图像翻译框架这得从雾图的物理退化模型说起。在计算机视觉里雾图常被描述成大气散射模型I(x) J(x)·t(x) A·(1 - t(x))其中I是有雾图J是清晰无雾图t是传输率透射率A是大气光。雾越浓t越低I越接近纯大气光A图像看起来就是白茫茫一片。去雾的本质就是从I反推出J、t和A。这个模型虽然经典但真实场景的t和A很难准确估计传统方法大多在估计t上做文章一旦遇到复杂光照就翻车。而用GAN来做去雾思路完全换了一个角度不显式估计t和A直接把“有雾”和“无雾”看成两个视觉域用数据驱动的方式学习从有雾域到无雾域的映射。网络自己隐式地学习去雾规律这种方式对复杂的真实雾天更加鲁棒。在这个项目里A域是雾图B域是清晰图。训练时只需要两个文件夹trainA放一堆雾图可以是合成的也可以是真实的trainB放一堆清晰图不要求一一对应。模型通过学习A到B的映射完成去雾同时利用G_B_A把清晰图转回雾图来保证内容一致性。1.3 项目源码结构与代码组织拿到这个zip包解压之后第一件事是看目录结构。标准CycleGAN类项目都会按功能模块划分代码这个项目也不例外主要目录如下. ├── models/ │ ├── __init__.py │ ├── networks.py # 生成器、判别器、权重初始化等网络定义 │ └── cycle_gan_model.py # 对偶GAN的完整模型封装包含前向、损失、优化 ├── datasets/ │ ├── __init__.py │ ├── image_folder.py # 读取A/B域图片的数据集类 │ └── transforms.py # 图片缩放、裁剪、翻转等预处理 ├── utils/ │ ├── __init__.py │ ├── image_pool.py # 假图缓存池提升判别器稳定性 │ └── visualizer.py # 训练过程日志与图片可视化 ├── options/ │ ├── __init__.py │ ├── base_options.py # 公共参数定义 │ └── train_options.py # 训练专用参数 ├── train.py # 训练入口 ├── test.py # 测试入口 └── 项目说明.md # 项目部署、运行、原理说明我的建议是先把项目说明文档通读一遍再对照models/cycle_gan_model.py看主流程。项目代码注释写得很全从网络结构到损失函数都有说明很适合作为课程设计、毕业设计或者入门GAN图像翻译的参考基座。2. 网络结构与Pytorch实现细节2.1 生成器网络残差卷积与下采样-上采样结构先看生成器这个项目使用的生成器是典型的Encoder-Residual-Decoder结构从Pytorch代码里可以看到大致的网络骨架一个卷积层把输入3通道升到64通道两个步长为2的卷积层做下采样逐步把特征图缩小、通道数扩大相当于压缩图像信息中间接9个残差块ResidualBlock每个残差块包含两个卷积层并带有跳跃连接用于学习图像内容和结构的精细调整两个转置卷积反卷积做上采样把特征图恢复到原始分辨率最后一层用Tanh激活把输出值映射到[-1, 1]区间与输入图像的归一化范围保持一致。为什么中间用残差块而不是单纯堆卷积因为去雾任务中浅层和高层的特征都很重要残差结构通过跳跃连接让梯度能顺畅传到前层同时让网络只需要学习“残余的雾”而不是从零重建整张图训练更容易收敛。Pytorch里残差块的实现大致如下class ResidualBlock(nn.Module): def __init__(self, in_channels): super(ResidualBlock, self).__init__() self.block nn.Sequential( nn.ReflectionPad2d(1), nn.Conv2d(in_channels, in_channels, kernel_size3, padding0), nn.InstanceNorm2d(in_channels), nn.ReLU(inplaceTrue), nn.ReflectionPad2d(1), nn.Conv2d(in_channels, in_channels, kernel_size3, padding0), nn.InstanceNorm2d(in_channels) ) def forward(self, x): return x self.block(x)这里有两个细节值得注意。第一卷积层用的是ReflectionPad2d而不是普通ZeroPad反射填充可以避免图像边缘出现伪影去雾结果边缘更干净。第二归一化用的是InstanceNorm而不是BatchNorm这个问题后文会详细展开但在生成器里它对小Batch训练非常关键。2.2 判别器网络PatchGAN的妙处判别器D_A和D_B用的是PatchGAN。普通的判别器输入整张图片输出一个0到1之间的标量表示“整张图真/假”的概率。而PatchGAN不是输出一个值而是输出一个N×N的特征图class PatchDiscriminator(nn.Module): def __init__(self, in_channels3, base_channels64): super(PatchDiscriminator, self).__init__() self.model nn.Sequential( nn.Conv2d(in_channels, base_channels, kernel_size4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(base_channels, base_channels * 2, kernel_size4, stride2, padding1), nn.InstanceNorm2d(base_channels * 2), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(base_channels * 2, base_channels * 4, kernel_size4, stride2, padding1), nn.InstanceNorm2d(base_channels * 4), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(base_channels * 4, 1, kernel_size4, stride1, padding1) ) def forward(self, x): return self.model(x)输出的每个像素对应输入图像上一个局部区域感受野相当于判别器对图片的每一小块分别判断真假。为什么用PatchGAN呢因为去雾最关心的是局部纹理是否真实、雾气是否残留、轮廓是否清晰这些都属于高频局部特征。PatchGAN能把这些局部细节“盯死”而全局判别器往往被整体颜色分布带偏忽略局部失真。另外PatchGAN参数更少训练速度更快也更稳。在去雾任务里它比整图二分类判别器实用得多。我在实际测试中也发现把判别器改成输出单值后生成图像虽然整体颜色差不多但放大看细边缘会有很多奇怪的伪影这就是判别器没有约束到局部纹理造成的。2.3 网络初始化与训练稳定性的关联GAN网络对初始化非常敏感。这个项目在networks.py里实现了统一的权重初始化函数核心是用均值为0、标准差0.02的正态分布初始化卷积层权重偏置初始化为0。初始化代码大致是这样的def weights_init_normal(m): classname m.__class__.__name__ if hasattr(m, weight) and (Conv in classname or Linear in classname): nn.init.normal_(m.weight.data, 0.0, 0.02) if m.bias is not None: nn.init.constant_(m.bias.data, 0.0)标准差0.02这个数值是GAN社区经过大量实验验证的经验值。它让网络初始时参数在小范围内扰动避免一开始就出现梯度爆炸或者判别器过早碾压生成器的情况。很多人训练GAN一上来就崩溃很可能就是初始化不规范。这里还有一个容易踩的坑如果用nn.Sequential搭网络最后一定要确认每个层的bias是否合理。尤其是生成器的最后一层卷积之后接了Tanh这层卷积的初始化也走同一个函数但如果某些项目里忘记初始化生成器训练初期输出全是黑色或者灰色会严重拖慢收敛。3. 损失函数设计与训练策略3.1 对抗损失LSGAN为什么比原始GAN更稳去雾本质上是一个图像生成任务对抗损失决定了生成图像是否“像清晰图”。这个项目采用LSGAN最小二乘GAN形式的对抗损失。原始GAN用的是二分类交叉熵损失当判别器过于自信时生成器拿到的梯度会迅速消失训练很不稳定。而LSGAN把判别器的输出视为回归问题目标是最小二乘误差判别器要把真实图预测为1把生成图预测为0生成器要让判别器把自己生成的图预测为1。最小二乘损失的梯度在误差大的时候更大在误差小的时候更小梯度信息比交叉熵要充裕生成器在训练初期能更快学习。从实践结果看使用LSGAN后训练曲线更平滑而且很少出现判别器loss瞬间归零、生成器直接摆烂的情况。在Pytorch里实现的时候需要注意Pytorch的BCEWithLogitsLoss和MSELoss的输入输出范围。LSGAN直接对判别器的输出做MSE所以判别器最后一层不要加Sigmoid保持线性输出。如果你在使用这个项目时发现判别器输出是0~1的概率说明代码里的判别器被改过不是标准的LSGAN设定。3.2 循环一致性损失保证去雾不丢内容光靠对抗损失生成器完全可以“放飞自我”——把雾图变成一张看起来清晰但内容完全不同的图只要骗过判别器就行。这就是GAN常见的模式坍塌。对偶GAN的解决方案是循环一致性损失对每个雾图x先经G_A_B去雾得到fake_B再经G_B_A加雾得到rec_A要求rec_A与x尽量一致L_cycle(x) || G_B_A(G_A_B(x)) - x ||₁同理对清晰图y也有对应的循环。循环损失通常用L1范数而不是L2因为L1对边缘和细节的惩罚更友好生成结果更锐利不会像L2那样把图像拉得过于平滑。在去雾任务里循环一致性损失的作用是约束内容保留去雾后的图片还要能够“变回”原来的雾图这强迫G_A_B在去掉雾气的同时保留原图的场景结构、物体位置和整体构图。如果循环损失权重太小去雾结果可能内容漂移如果权重太大生成器会趋于保守雾去不干净。在项目里循环损失的权重λ一般设为10这是一个平衡效果较好的默认值。3.3 身份映射损失解决颜色偏色问题这是很多新手容易忽略的一个损失。对偶GAN还有一个可选的identity loss在去雾项目里非常有用L_identity || G_A_B(y) - y ||₁ || G_B_A(x) - x ||₁简单解释把一张清晰的图y直接输入G_A_B理论上应该还是清晰图因为y已经“无雾”了不必再转换同理把雾图x直接输入G_B_A也应该还是雾图。身份损失的作用是约束生成器不要随意改变输入的颜色分布。为什么要加这个因为纯对抗循环一致性训练出来的去雾模型经常把输出颜色改得怪怪的比如整体偏灰、偏蓝或者对比度过度拉伸。加身份损失后生成器会尽量保留原图的色彩风格只去雾、不乱改色。但身份损失权重不宜太高太高会让生成器觉得“什么都不做就是最好的选择”导致去雾力度不足。我的经验是权重设在0.5左右训练十几轮后观察生成图如果没有偏色再适当降低。整个训练过程中最终损失是三项损失的加权和损失项公式简述默认权重对抗损失LSGAN生成器让判别器认为输出为真1.0循环一致性损失去雾后再加雾与原图一致10.0身份映射损失清晰图去雾后仍为清晰图0.53.4 训练超参数与优化器选择项目训练配置对GAN来说非常标准但我还是建议你调参前先理解为什么这么设优化器Adam学习率2e-4betas(0.5, 0.999)。beta1取0.5是GAN训练的常见做法相比默认0.9让优化器更看重当前梯度有助于稳定对抗训练学习率策略前100个epoch保持2e-4后100个epoch线性衰减到0让网络在后期慢慢收敛避免在损失曲面震荡Batch size建议1。小batch配合InstanceNorm是这类图像翻译任务的标准用法判别器更新时使用假图缓存池生成器生成的图不直接用于更新判别器而是放入一个最多存50张图的队列每批次随机取一张历史假图和一张当前假图混合使用。这个技巧能防止判别器“记住”最新的生成图从而减少训练波动。4. 数据准备与完整复现流程4.1 环境准备与Pytorch安装在动手跑代码之前先把环境配置好。这个项目基于Pytorch建议Python 3.8及以上版本。Pytorch的安装建议直接从官网获取与CUDA版本匹配的命令比如CUDA 11.8对应的命令大致是pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118如果只是CPU环境测试直接pip install torch torchvision即可但训练GAN还是强烈建议用NVIDIA GPU哪怕是入门级显卡也比CPU快一个数量级。安装完成后在Python里验证import torch print(torch.__version__) print(torch.cuda.is_available())如果cuda.is_available()返回True说明GPU环境可用。4.2 数据集的制作与目录组织图像去雾的数据集来源有两类一类是公开的合成雾图数据集RESIDE另一类是自己用大气散射模型合成。我推荐先用RESIDE的OTS子集跑通流程数据量大、场景丰富训练效果容易出来。如果你想自己合成雾图公式就是前面提的大气散射模型。写一个批量处理脚本对每张清晰图随机生成一个传输率t比如0.4~1.0之间的随机值和大气光A比如0.8~1.0代入公式就能得到雾图。我用这种方法做过一个小规模数据集几十张清晰图就能合成几百张雾图非常适合做实验快速迭代。数据目录按项目要求组织成如下结构datasets/defog/ ├── trainA/ # 有雾图像 ├── trainB/ # 清晰图像 ├── testA/ # 测试用有雾图像 └── testB/ # 测试用清晰图像用于计算指标注意trainA和trainB里的图像不需要一一对应数量也不需要相等这正是对偶GAN的优势。数据集读取代码通常用torchvision的ImageFolder实现同时配合transform做数据增强。训练时图片处理成286×286输入随机裁剪出256×256区域再随机水平翻转最后归一化到[-1, 1]区间。这些预处理在datasets/transforms.py里都能找到对应实现。4.3 训练命令与过程监控环境配置好后训练入口脚本是train.py核心参数包括python train.py \ --dataroot ./datasets/defog \ --name defog_experiment \ --model cycle_gan \ --batch_size 1 \ --n_epochs 100 \ --n_epochs_decay 100--name参数决定实验输出目录所有训练日志、checkpoint和可视化结果会存放在checkpoints/defog_experiment/下。模型权重默认每5个epoch保存一次同时也会保存最近一次的latest权重方便中断后续训。训练过程中建议开启visdom或者TensorBoard来观察loss曲线和生成结果。只看loss值是看不出效果的一定要每隔几十个iteration把当前生成器G_A_B的输出图片保存下来亲眼确认去雾效果。我看到过很多次loss非常漂亮但输出完全不能看的情况所以可视化监督非常必要。4.4 测试与评估指标训练完成后用test.py做推理python test.py \ --dataroot ./datasets/defog \ --name defog_experiment \ --model cycle_gan \ --epoch 100测试结果保存在results/defog_experiment/目录。如果项目里有参考清晰图testB还可以进一步计算量化指标。去雾任务最常用的是PSNR和SSIMfrom skimage.metrics import peak_signal_noise_ratio, structural_similarity psnr peak_signal_noise_ratio(output, target, data_range1.0) ssim structural_similarity(output, target, channel_axis-1)PSNR越大越好SSIM越接近1越好。但我必须提醒一句在去雾任务里PSNR和SSIM提升幅度往往有限甚至有时候视觉上更自然的图像PSNR反而低。因为去雾本质是“主观感知任务”所以最终效果评估一定要结合人眼观察重点看边缘是否锐利、整体颜色是否真实、天空区域有没有过曝、远处细节是否恢复。5. 训练过程中踩过的坑与排查实录5.1 训练几轮后Loss变成NaN这是最常见的崩溃方式。排查顺序是这样的先检查学习率是否过大2e-4对这个项目一般不会出事但如果你手滑改成2e-3大概率NaN然后检查输入数据范围生成器输出用Tanh在[-1,1]区间如果输入数据忘了归一化到[-1,1]而是在[0,255]网络很容易发散最后检查有没有除零操作特别是InstanceNorm对单通道输入会算均值/方差数值稳定性一般没问题但如果某个batch里全是同一张纯色图片方差接近0也会出问题。我的建议是先用很小的数据集、只跑10个epoch测试代码能正常跑通再上完整数据。遇到NaN不要慌先定位是哪个损失变成NaN缩小排查范围。5.2 去雾结果偏灰、偏暗或者颜色失真如果去雾后的图像看起来清晰了但整体发灰最可能的原因是循环一致性损失的权重太高生成器为了满足“加雾回去与原图一致”会倾向于输出保守的中间色调。解决办法是适度调低λ或者提高身份损失的权重来约束颜色方向。颜色失真严重时优先检查身份损失是否加入。我一开始训练时把身份损失权重设为0训练到第50轮左右生成的图像天空变成奇怪的紫色。后来加入身份损失并设权重为0.5这个问题基本消失。记住身份损失在去雾任务里不是可选优化项而是保颜色的关键正则项。5.3 雾气去不干净残留白色薄雾如果生成图看起来“有点清但没清透”通常是以下原因训练epoch不足生成器还没完全学会强去雾PatchGAN的感受野不够大对大面积雾气的整体分布不敏感判别器太弱无法有效区分“微雾图”和“清晰图”。针对最后一个原因可以尝试把判别器的卷积核从4改成稍微大一些或者增加判别器的层数让它的辨别能力更强从而反过来督促生成器下狠手去雾。但这也可能带来训练不稳定需要同时留意判别器loss和生成器loss是否保持在合理范围。5.4 加载预训练权重时报错“module”前缀不匹配如果你用DataParallel在多卡环境训练保存下来的权重文件名里所有键名会带上module.前缀。单卡测试加载时会报错提示size mismatch或者key不存在。解决办法是在加载时做一次字符串替换state_dict torch.load(checkpoint_path, map_locationcpu) new_state_dict {k.replace(module., ): v for k, v in state_dict.items()} model.load_state_dict(new_state_dict)5.5 如何判断模型是否过拟合GAN过拟合的表现和普通分类网络不一样它通常不是accuracy下降而是生成图像变得单一、重复或者对训练集里的某几张雾图去雾效果极好但对新图完全不行。判断方法很简单测试时把训练集里的图和全新的图分别放进同一模型里跑如果训练集图效果远好于新图说明过拟合了。对策是增加数据多样性、采用更强的数据增强或适当引入Dropout。对GAN来说过拟合没有分类任务那么普遍但小数据集上依然会出现值得警惕。这个项目我前前后后跑了很多个版本最大的体会是GAN做图像去雾网络结构反而不是重点真正决定效果上限的是损失函数的配比和数据质量。训练过程中你会看到一个很有意思的现象——当循环一致性和对抗损失达到某种平衡时生成器会突然从“只会把图像变灰”变成“真正能看清远处山峦的轮廓”这个临界点需要一点耐心去调参。建议你拿到项目后先不要动模型结构用默认参数跑通一遍再用小数据集做几组对照实验分别调整循环损失权重、身份损失权重和训练epoch数记录每组实验的输出图片。等你亲手把颜色漂移、雾残留这些问题一个个压下去之后你对对偶GAN的理解会比读十篇论文都深。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →