多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

GAN图像去模糊实战:源码解析与训练避坑指南

GAN图像去模糊实战:源码解析与训练避坑指南 简介这份资源面向图像处理与深度学习方向的学习者和开发者聚焦利用生成对抗网络完成图像去模糊任务适合具备一定Python与神经网络基础、希望从理论走向实操的中级读者。压缩包共52个文件约34.28MB以36个py源码文件为核心涵盖数据加载、模型定义、训练与测试脚本及工具模块另含2个pth权重、6张jpg与4个gif效果图、1个html和1个md说明文档便于对照代码与可视化结果理解流程。项目围绕生成器与判别器的对抗训练展开包含运动模糊生成、损失函数、条件GAN模型等关键模块并配有流程教程可帮助读者掌握从模糊图像到清晰图像的完整实现路径。目前已有241人学习适合作为课程设计、科研入门或算法复现的参考案例。1. 图像去模糊遇上 GAN这套源码到底能跑出什么效果手里有一批运动模糊的图用传统维纳滤波或者盲反卷积去修参数稍微调偏一点结果不是振铃就是糊得更均匀。这是我最初接触图像去模糊时的真实处境。后来转向深度学习方案试过直接回归清晰图的 CNN结果普遍偏平滑高频纹理全被吃掉。直到用上生成对抗网络做去模糊才在锐度和纹理还原上看到明显改善。这套「图像去模糊-使用生成对抗网络进行图像去模糊算法」的源码包就是一条已经搭好的 GAN 去模糊流水线包含数据加载、模型定义、训练测试脚本还附带了运动模糊数据集生成工具和流程教程。它适合两类人一是想跑通 GAN 去模糊完整链路、看到输入模糊图到输出清晰图对比的开发者二是想拆开看生成器、判别器、损失函数怎么组织方便自己改结构做实验的研究者。下面我按实际拆包和复现的顺序把这份资源讲透。2. 拆开压缩包目录结构与 GAN 去模糊的数据流2.1 从目录看这套代码的模块划分拿到压缩包先别急着装环境把目录结构过一遍能省掉后面很多找文件的功夫。这份源码的顶层结构大致是这样目录/文件作用data/数据集加载与预处理含对齐/非对齐数据集、单图数据集options/命令行参数定义训练和测试各一套models/核心网络与模型逻辑含损失、网络结构、条件 GAN 模型datasets/运动模糊数据生成工具含 PSF 生成、轨迹生成、模糊图合成util/评估指标、图像池、可视化、PNG 处理等辅助工具checkpoints/训练权重保存位置含示例实验名train.py/test.py训练与测试入口README.md流程教程与使用说明这个划分是典型的「配置-数据-模型-工具」四层结构。options负责把命令行参数收拢成配置对象data负责按配置吐 batchmodels负责前向和损失util负责杂活。理解这条数据流后面调参和排错才有方向。2.2 生成器与判别器的职责边界GAN 去模糊的核心是生成器 G 和判别器 D 的对抗。生成器接收模糊图输出清晰图判别器接收「真实清晰图」和「生成器输出的清晰图」判断哪个是真的。训练时两个网络交替优化先固定 G 更新 D让 D 尽量区分真假再固定 D 更新 G让 G 尽量骗过 D。这份源码里models/networks.py定义网络结构models/losses.py定义损失models/conditional_gan_model.py把 G、D、损失串成训练逻辑。条件 GAN 的「条件」就是输入的模糊图——生成器不是凭空生成清晰图而是在模糊图条件下做映射。这个设定很关键它让去模糊变成一个条件生成问题而不是无条件生成。常见做法是生成器用编码器-解码器结构中间加残差块或跳跃连接保留低频结构信息判别器用 PatchGAN对局部图块判真假而不是对整图出一个标量。PatchGAN 的好处是能关注局部纹理对去模糊这种需要恢复高频细节的任务更合适。源码里具体用了哪种拆开networks.py一看便知。2.3 数据加载器怎么把模糊-清晰对喂进网络data/目录下有多个数据集类aligned_dataset.py处理成对的模糊-清晰图unaligned_dataset.py处理非对齐数据single_dataset.py处理单张测试图。训练时最常用的是成对数据每一条样本包含一张模糊图和对应的清晰图。数据加载的典型流程是从磁盘读图 → 随机裁剪到指定尺寸 → 随机翻转做增强 → 归一化到 [-1, 1] → 转成张量 → 按 batch 返回。归一化到 [-1, 1] 而不是 [0, 1]是因为生成器输出层常用 tanh值域匹配能加速收敛。data/base_dataset.py里通常定义了modify_commandline_options和get_loader两个方法前者根据数据集类型补默认参数后者返回 DataLoader。data/custom_dataset_data_loader.py负责按名字实例化具体数据集类。这条链路是配置驱动的改数据集类型只需要改命令行参数不用动代码。2.4 运动模糊数据集的生成逻辑datasets/motion_blur/下有三个脚本generate_trajectory.py生成运动轨迹generate_PSF.py根据轨迹生成点扩散函数blur_image.py用 PSF 对清晰图做卷积得到模糊图。这是合成运动模糊的标准做法。运动模糊的本质是相机在曝光时间内移动导致像素在轨迹上积分。用 PSF 卷积清晰图就是模拟这个积分过程。轨迹生成通常用随机游走或贝塞尔曲线控制轨迹长度和弯曲程度来模拟不同强度的模糊。PSF 是轨迹的核表示卷积时要注意边界处理否则边缘会出现暗角或伪影。这套生成工具的价值在于你可以用自己的清晰图批量合成模糊-清晰对不用去找现成的模糊数据集。对于做实验来说数据可控性比什么都重要。提示合成模糊时轨迹长度和 PSF 尺寸要匹配。PSF 太小模糊效果不明显PSF 太大卷积计算量暴涨且边缘伪影加重。常见做法是 PSF 尺寸取 21 到 41 之间的奇数。3. 环境搭建与训练脚本从零跑通第一条去模糊链路3.1 依赖安装与版本选择这份源码基于 PyTorch依赖不算复杂。我一般会先建一个干净的虚拟环境避免和系统里的包打架。常见做法是用 conda 或 venv 建环境然后按 README 里的依赖列表装。# 创建虚拟环境 conda create -n deblur_gan python3.8 -y conda activate deblur_gan # 安装 PyTorch具体版本按显卡 CUDA 版本选 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装其余依赖 pip install numpy opencv-python pillow scipy tqdm tensorboard这里有几个参数要留意。Python 版本建议 3.7 到 3.9太新的版本可能和旧版 PyTorch 不兼容。PyTorch 版本要和 CUDA 驱动匹配cu118表示 CUDA 11.8如果你的驱动只支持到 11.6就换成对应的。opencv-python用于图像读写和 PSF 卷积tensorboard用于看训练曲线这两个别漏。装完后跑一句python -c import torch; print(torch.cuda.is_available())输出 True 说明 GPU 可用。如果输出 False先查驱动和 CUDA 版本别急着往下走。3.2 用自带脚本合成一批运动模糊数据在训练之前得先有数据。如果你手里只有清晰图可以用datasets/motion_blur/下的脚本合成模糊图。# 生成运动轨迹 python datasets/motion_blur/generate_trajectory.py \ --num_trajectories 100 \ --max_length 30 \ --output_dir ./datasets/trajectories # 根据轨迹生成 PSF python datasets/motion_blur/generate_PSF.py \ --trajectory_dir ./datasets/trajectories \ --psf_size 31 \ --output_dir ./datasets/psf # 用 PSF 对清晰图做卷积生成模糊图 python datasets/motion_blur/blur_image.py \ --sharp_dir ./datasets/sharp \ --psf_dir ./datasets/psf \ --output_dir ./datasets/blurnum_trajectories控制生成多少条轨迹max_length是轨迹最大长度值越大模糊越强。psf_size是 PSF 核尺寸取奇数31 是一个比较稳的中间值。blur_image.py会把sharp_dir里的每张清晰图和随机一条 PSF 卷积输出到blur_dir。合成完后目录里应该有成对的模糊图和清晰图。文件名要能对应上否则后面的成对数据加载会找不到配对。常见做法是模糊图加_blur后缀清晰图加_sharp后缀加载器按前缀匹配。3.3 训练参数怎么设以 train.py 为例训练入口是train.py参数通过options/train_options.py和options/base_options.py定义。核心参数我列一下参数含义建议值--dataroot数据集根目录指向你的模糊-清晰对目录--dataset_mode数据集模式aligned成对数据--model模型类型conditional_gan--batch_size批大小1 到 4看显存--lr初始学习率0.0002--n_epochs总 epoch 数100 起--lambda_L1L1 损失权重100--gan_modeGAN 损失类型lsgan或vanilla--name实验名自定义用于保存权重lambda_L1是 GAN 去模糊里最关键的参数之一。它控制 L1 重建损失和对抗损失的相对权重。L1 损失保证生成图在像素层面接近清晰图对抗损失保证纹理锐利。lambda_L1设太小生成图会偏糊设太大对抗损失被压制纹理恢复不明显。100 是一个常见起点实际要根据效果微调。gan_mode选lsgan最小二乘 GAN通常比原始 GAN 稳定梯度不会饱和得太快。如果训练时判别器损失很快掉到 0说明判别器太强可以降低判别器学习率或加标签平滑。启动训练python train.py \ --dataroot ./datasets/my_deblur_data \ --dataset_mode aligned \ --model conditional_gan \ --batch_size 2 \ --lr 0.0002 \ --n_epochs 100 \ --lambda_L1 100 \ --gan_mode lsgan \ --name deblur_exp1 \ --gpu_ids 0--gpu_ids 0指定用第一块 GPU多卡可以写0,1。--name deblur_exp1会在checkpoints/下建同名目录权重和日志都存那里。训练过程中用tensorboard --logdir checkpoints/deblur_exp1看损失曲线和生成图。3.4 测试与结果查看训练完或者中途想验证效果用test.pypython test.py \ --dataroot ./datasets/my_deblur_data \ --dataset_mode aligned \ --model conditional_gan \ --name deblur_exp1 \ --epoch latest \ --results_dir ./results/deblur_exp1--epoch latest加载最新权重也可以指定具体 epoch 号。--results_dir是结果输出目录里面会有输入模糊图、生成清晰图、真实清晰图的对比。util/visualizer.py负责把结果拼成对比图util/metrics.py里通常有 PSNR 和 SSIM 的计算可以量化评估。看结果时别只看 PSNR。PSNR 高的图不一定视觉上锐利GAN 的优势在于感知质量。我一般会同时看 PSNR、SSIM 和肉眼观感三者结合判断。4. 避坑与排查训练 GAN 去模糊时最容易翻车的几个点4.1 生成图全灰或全黑现象训练几个 epoch 后生成器输出的图几乎是一张灰色或黑色图没有任何结构。原因最常见的是归一化不匹配。生成器输出层用 tanh值域是 [-1, 1]但如果数据加载时把图归一化到了 [0, 1]生成器学到的输出和目标对不上就会塌缩到均值附近。另一个原因是学习率太大生成器直接崩了。解决检查data/base_dataset.py里的归一化逻辑确保和生成器输出层匹配。如果生成器用 tanh数据要归一化到 [-1, 1]如果生成器用 sigmoid数据归一化到 [0, 1]。学习率从 0.0002 往下调试 0.0001 或 0.00005。4.2 判别器损失迅速归零现象训练开始没多久判别器损失就掉到接近 0生成器损失飙升生成图越来越差。原因判别器太强生成器完全骗不过梯度消失。这在原始 GAN 损失下很常见尤其是判别器网络比生成器深很多的时候。解决换lsgan或wgan损失梯度更平滑。降低判别器学习率比如生成器用 0.0002判别器用 0.0001。加标签平滑把真实标签从 1.0 改成 0.9。还可以给判别器加 dropout 或谱归一化限制它的判别能力。4.3 显存不够batch_size 降到 1 还报 OOM现象训练时显存溢出即使batch_size设成 1 也跑不起来。原因输入图尺寸太大或者生成器/判别器网络太深中间特征图占显存。去模糊任务常用 256x256 的 patch如果直接上 512x512 或更大显存需求翻倍。解决在options/base_options.py里找--crop_size或--load_size把训练裁剪尺寸降到 128 或 256。测试时可以恢复大尺寸因为测试不需要存梯度。另外检查有没有在训练循环里累积了不必要的中间变量比如把每步的生成图都存到列表里。4.4 合成模糊图和真实模糊分布不匹配现象在合成数据上训练得很好PSNR 很高但拿真实拍摄的模糊图测试效果一塌糊涂。原因合成模糊用的是线性卷积加高斯噪声真实模糊还包含相机响应非线性、传感器噪声、压缩伪影等。合成数据和真实数据之间存在域差距。解决在合成时加更多退化因素比如随机噪声、JPEG 压缩、亮度变化。或者用非成对数据做域适应unaligned_dataset.py就是为这种场景准备的。如果手头有少量真实模糊-清晰对可以拿合成数据预训练再用真实数据微调。4.5 训练 loss 震荡不收敛现象生成器和判别器损失来回震荡几十个 epoch 都不收敛。原因GAN 训练本身就是博弈过程损失震荡是正常的。但如果震荡幅度过大可能是学习率太高、batch_size 太小、或者 L1 权重和对抗权重比例失衡。解决先固定随机种子排除数据顺序的干扰。降低学习率增大 batch_size如果显存允许。调整lambda_L1让 L1 损失和对抗损失量级接近。还可以用梯度裁剪限制梯度范数。如果震荡持续考虑换更稳定的 GAN 变体。5. 进阶玩法用评估指标和可视化把去模糊效果量化5.1 PSNR 和 SSIM 怎么算才靠谱util/metrics.py里通常有 PSNR 和 SSIM 的实现。PSNR 基于均方误差公式是10 * log10(MAX^2 / MSE)MAX 是像素最大值。SSIM 从亮度、对比度、结构三个维度比较更接近人眼感知。算这两个指标时有几个细节容易出错。一是通道处理RGB 图要转成 YCbCr 只在 Y 通道算还是三通道平均不同实现不一样对比时要统一。二是边界裁剪卷积或生成过程会在边缘产生伪影算指标时裁掉几个像素的边界更公平。三是数据范围PSNR 对像素值范围敏感[0, 255] 和 [0, 1] 算出来的值差很多要确认实现里用的是哪个范围。import numpy as np import cv2 def calculate_psnr(img1, img2): # img1, img2: uint8, HWC, BGR mse np.mean((img1.astype(np.float64) - img2.astype(np.float64)) ** 2) if mse 0: return float(inf) return 10 * np.log10(255.0 ** 2 / mse) def calculate_ssim(img1, img2): # 转灰度后计算 SSIM gray1 cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) C1 (0.01 * 255) ** 2 C2 (0.03 * 255) ** 2 mu1 cv2.GaussianBlur(gray1.astype(np.float64), (11, 11), 1.5) mu2 cv2.GaussianBlur(gray2.astype(np.float64), (11, 11), 1.5) sigma1_sq cv2.GaussianBlur(gray1.astype(np.float64) ** 2, (11, 11), 1.5) - mu1 ** 2 sigma2_sq cv2.GaussianBlur(gray2.astype(np.float64) ** 2, (11, 11), 1.5) - mu2 ** 2 sigma12 cv2.GaussianBlur(gray1.astype(np.float64) * gray2.astype(np.float64), (11, 11), 1.5) - mu1 * mu2 ssim_map ((2 * mu1 * mu2 C1) * (2 * sigma12 C2)) / \ ((mu1 ** 2 mu2 ** 2 C1) * (sigma1_sq sigma2_sq C2)) return ssim_map.mean()这段代码里C1和C2是稳定常数防止分母为零。高斯核大小 11、标准差 1.5 是 SSIM 原论文的推荐值。sigma1_sq和sigma2_sq是局部方差sigma12是局部协方差。算出来的ssim_map是逐像素的 SSIM取均值得到整图 SSIM。5.2 用 TensorBoard 看训练动态util/visualizer.py通常会把生成图、损失曲线写到 TensorBoard。训练时开一个终端跑tensorboard --logdir checkpoints/你的实验名浏览器打开对应端口能看到生成器损失和判别器损失随 epoch 的变化每个 epoch 的生成图对比模糊输入、生成输出、真实清晰学习率变化曲线看损失曲线时别指望两条线都平滑下降。GAN 的损失是博弈结果生成器损失下降时判别器损失可能上升这是正常的。关键看生成图有没有越来越清晰以及两个损失有没有出现极端值。5.3 用 image_pool 稳定判别器训练util/image_pool.py实现了一个图像缓冲池用来存历史生成的图。判别器训练时不只拿当前生成器输出的图还从池里随机采样历史生成图。这样判别器不会只针对当前生成器的输出过拟合训练更稳定。这个技巧来自 CycleGAN在条件 GAN 里同样适用。池大小一般设 50太小起不到稳定作用太大占显存。如果你发现判别器损失震荡厉害可以检查一下 image_pool 有没有启用池大小是否合理。5.4 我踩过的一个坑测试时忘了切 eval 模式有一次训练完测试生成图里出现了奇怪的随机噪声训练时明明没有。查了半天才发现测试脚本里忘了调model.eval()生成器里的 dropout 和 batch norm 还在训练模式每次前向都引入随机性。加上model.eval()和torch.no_grad()后输出就稳定了。从那以后我每次写测试脚本都强制走一遍eval()no_grad()的检查。这个坑不复杂但很容易忘尤其是从训练脚本复制代码改测试的时候。希望帮到你。本文还有配套的精品资源点击获取
返回列表