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

资讯详情

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

GAN图像修复实战:可复现的PyTorch项目全解析

GAN图像修复实战:可复现的PyTorch项目全解析 简介一套基于深度学习的图像修复系统Python源码与项目文档面向高校毕业设计、课程设计及机器学习爱好者解决图像划痕、噪点、局部遮挡等损伤场景下的智能补全问题。项目采用卷积神经网络与对抗式训练策略模型经过大规模图像数据集预训练属经导师指导并获认可的优秀毕业设计成果评定99分代码结构完整、可稳定运行对初学者友好。资源共18个文件压缩包约5.61MB以Python脚本、PNG/JPG示例图像、README项目说明、环境配置及备份文件为主另有结果对比图辅助理解修复效果。内容包含完整算法实现、项目技术文档、环境配置说明、示例数据集与测试案例操作指南逐步覆盖环境部署、模型训练到结果复现适合需要完整实战案例的读者参考。目前已有85人学习下载。1. 用GAN修复图像不是玄学先跑通这份可复现的Python项目图像修复这个需求听起来挺唬人实际上就是给图片“补洞”——老照片上的划痕、扫描件上的折痕、被水渍遮住的文字或者干脆是物体移除后留下的空白。传统工具处理小噪点还行一碰到大面积缺失就露馅。我用深度学习做这块也有段时间了拆项目时最怕的就是“源码能跑”这句话掺水。这份图像修复系统资源是真能跑的那种两个核心脚本inpaint_simple.py和inpaint_complex.py分工明确前者快速出效果后者上对抗训练补细节配套的 README 和 result 目录里还有完整的样例输出。不管你是在做毕业设计、课程设计还是想找一份能落地的 GAN 图像修复案例这套代码都值得下下来拆一遍。2. 两级代码架构选型inpaint_simple 与 inpaint_complex 各自解决什么问题2.1 为什么这个项目不选传统 PatchMatch 而要上 GAN图像修复不是新概念。OpenCV 里就有基于 PatchMatch 的cv2.inpaint它能根据边缘纹理把缺失区域“填”上原理是在周围找相似小方块拼过去。实测下来小划痕和细直线这类规则损伤PatchMatch 处理得又快又自然。但一旦缺失区域超过图片面积的 10%或者要补的是人眼这类结构复杂的目标PatchMatch 生成的区域就会发糊纹理方向乱飘不会有真实的语义内容。GAN 的思路是把“补洞”变成一个生成问题。生成器负责看图说话判别器负责挑刺两个网络互相较劲最后生成器学会的不只是补上像素而是补出“像真实照片”的内容。这个项目把这条路拆成了两个脚本inpaint_simple.py先给出一个基线的深度网络方案inpaint_complex.py在它基础上加对抗训练和更深的生成器对应不同损伤程度和画质要求。我在实际跑数据时观察到一个很直观的现象result 目录下以s_开头的输出图修复轻度划痕完全够用而以c_开头的输出图在大面积遮挡场景里明显保留了更多结构细节。这就是两级架构的意义——按损伤程度选工具而不是无脑上最强模型。2.2 生成器、判别器与损失函数的三块配置先说inpaint_simple.py这种简单路线。它内部一般是编码器-解码器结构输入是带缺失区域的图输出是完整图。损失函数通常用 L1 或 L2 重建损失约束生成图和原图的像素距离。L1 的好处是梯度稳定对边缘清晰度更友好训练不容易振荡。inpaint_complex.py走的是完整的 GAN 路线包含三部分损失# gan_loss_weights.py 中的常用配置对应复杂模型训练时的损失组合 recon_loss nn.L1Loss() # 重建损失管像素级还原 percep_loss PerceptualLoss() # 感知损失管高层语义特征 gan_loss nn.BCEWithLogitsLoss() # 对抗损失管真实感 # 最终总损失是三者加权组合权重分别对应上面三行 total_loss recon_weight * recon_loss(pred, target) \ percep_weight * percep_loss(pred, target) \ gan_weight * gan_loss(disc_fake, valid_label)参数上一般这么给recon_weight取 1.0percep_weight取 0.1gan_weight取 0.01 到 0.001 区间。这个比例不是随便拍的开太大重建损失生成图像素准但细节糊开太大对抗损失局部细节可能乱生成出现“脑补”出来的纹理。提示感知损失常借用 VGG16 的中间层特征代码里需要提前下载预训练权重。离线环境跑的时候先手动把权重文件放进torchvision的缓存目录否则第一次训练会卡在下载这一步。2.3 从 result 目录反推脚本的输入输出约定拆项目时我最喜欢先看输出再反推输入。result 里有s_1.png到s_4.png和c_1.png到c_4.png一套四张说明两个脚本各自处理了四类测试场景。结合 data 目录里放了两张原始 jpg 来判断这套工程大概率是按“同一张图、四种不同 mask”的方式组织验证集。常见做法是输入图统一缩放到 256×256 或 512×512随机生成不规则 mask把 mask 覆盖的区域从原图上抠掉剩下的残缺图喂给网络。运行流程上脚本先读原始图再生成 mask然后推理、保存到 result。如果你拿到代码后直接跑发现 result 里图片数量和名字不匹配多半是--mask_type或者--img_size参数变了导致输出文件命名错位。数据流约定我一般会固定成这套目录放什么说明data/1.jpg、2.jpg原始完整图像用于生成训练对result/s_.png、c_.png简单模型和复杂模型各自的推理输出根目录inpaint_simple.py、inpaint_complex.py两个可独立运行的入口脚本3. 环境配置与数据准备Python 3.7 里把jpg变成训练样本3.1 先解决依赖CPU 能不能跑显卡内存到底要多大这个资源说明里写了 Python 3.7 及以上实际我建议直接用 Python 3.8 或 3.10 的干净环境别在 3.6 上挣扎。核心依赖是 PyTorch 和 OpenCV安装命令如下# 建议新建虚拟环境避免把系统 Python 搞乱 conda create -n inpaint python3.10 -y conda activate inpaint # 安装 CPU 版还是 GPU 版取决于你机器上有没有 NVIDIA 显卡 # GPU 版需要先确认 CUDA 版本比如 CUDA 11.8 对应 torch 2.0.0 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy Pillow tqdm如果是纯 CPU 机器也能跑inpaint_simple.py在 256×256 分辨率下处理一张图大概十几秒但inpaint_complex.py要训练的话CPU 基本等不起只能直接加载训练好的权重做推理。显存方面我实测过256×256、batch size 4复杂模型的显存占用大约 5GB 到 6GB如果你是 8GB 显卡batch size 往 2 调比较稳妥。注意不要一上来就装最新版 PyTorch。如果你的 CUDA 版本是 10.x早期 torch 1.8 兼容性更好CUDA 12.x 再用新版 torch。装错了典型症状是torch.cuda.is_available()返回 False。3.2 用脚本批量生成带 mask 的训练对数据准备是整个修复项目的命门。原项目 data 目录里只有 1.jpg 和 2.jpg 两张原图直接拿去训练肯定过拟合。我拆这套工程时的习惯是写一个单独的预处理脚本把原图切块、加 mask、另存为训练对。# prepare_data.py —— 把原始jpg切块并生成掩码 import cv2 import numpy as np from glob import glob img_size 256 # 切块尺寸显存不够就改小到 192 mask_ratio 0.3 # mask 面积占全图比例超过 0.5 训练难度剧增 stride 128 # 滑窗步长控制生成多少张训练块 for img_path in glob(./data/*.jpg): img cv2.imread(img_path) img cv2.resize(img, (512, 512)) h, w img.shape[:2] count 0 for y in range(0, h - img_size 1, stride): for x in range(0, w - img_size 1, stride): crop img[y:yimg_size, x:ximg_size].copy() # 生成不规则mask画随机多边形模拟划痕和污渍 mask np.zeros((img_size, img_size), dtypenp.uint8) pts np.random.randint(0, img_size, size(6, 2)) cv2.fillPoly(mask, [pts], 255) # 抠图mask区域像素置零 masked crop.copy() masked[mask 255] 0 cv2.imwrite(f./data_train/masked_{count:04d}.png, masked) cv2.imwrite(f./data_train/gt_{count:04d}.png, crop) cv2.imwrite(f./data_train/mask_{count:04d}.png, mask) count 1这段脚本里mask_ratio0.3是训练收敛的关键参数。低于 0.1网络学不到东西随便填个模糊色也能蒙混过关高于 0.5缺失区域太大生成器拿不到足够上下文loss 会卡在某个高位不降。stride控制数据量步长越小切块越多但不是越多越好——我切到上万张后训练时间翻倍增益很小一般 2000 到 4000 张足够。另外要注意cv2.imread读进来是 BGR 通道如果之后用 PIL 读图通道顺序容易搞混。我的做法是统一用 OpenCV 处理输入最后转成 RGB 再进模型。3.3 归一化、增强与 mask 参数的实际设值深度学习训练不是把图直接塞进去就完事。修复模型对输入分布很敏感必须做归一化。常见做法是除以 255 然后做均值方差标准化。如果只除以 255 不做标准化也就是把像素缩放到 0~1多数情况下也行但收敛速度会慢一些。# 归一化参数按 ImageNet 统计值设定 MEAN [0.485, 0.456, 0.406] STD [0.229, 0.224, 0.225] def normalize(img): # img: HWC, uint8 [0,255] img img.astype(np.float32) / 255.0 img (img - MEAN) / STD return img数据增强的取舍也值得说道。水平翻转和随机旋转 90 度基本是白送的随便加但颜色抖动要慎用修复任务需要精确还原颜色把亮度大幅扰动会让模型学偏。mask 的类型更要控制训练时只生成多边形 mask推理时来一个矩形大洞效果就会明显变差。我一般会在训练数据里混入三种形态多边形污渍、细长条划痕、小方块局部遮挡各占三分之一。4. 训练与推理实战跑通命令行的关键参数与输出结果4.1 训练入口参数怎么给epoch、batch size、学习率三者搭配拿到这份源码第一件要做的事是打开inpaint_simple.py看底部的入口参数这类脚本的argparse选项通常高度相似。我一般会按下面的配置起步先把流程跑通再逐步加大规模# 先跑简单模型确认环境无问题 python inpaint_simple.py --data_root ./data_train --epochs 50 --batch_size 8 --lr 1e-4 --img_size 128 # 再跑复杂模型GPU显存不足时减小 batch_size python inpaint_complex.py --data_root ./data_train --epochs 200 --batch_size 4 --lr 2e-4 --img_size 128batch_size直接取决于显存8GB 显卡跑 256×256 图最多 4。lr我按经验给1e-4复杂模型因为有判别器学习率开高了训练必炸典型表现是 loss 一会儿几百一会儿零点几。epochs需要结合日志判断简单模型大概 30 个 epoch 就能看到像样的输出复杂模型前 50 个 epoch 都在“热身”判别器先收敛生成器才开始追。关键训练日志要盯这几项Epoch 10, Loss_D: 0.382, Loss_G: 246.37, PSNR: 21.35 Epoch 20, Loss_D: 0.415, Loss_G: 58.21, PSNR: 25.42Loss_D稳定在 0.3 到 0.5 之间说明判别器在正常工作Loss_G早期高达数百是正常的因为感知损失数值偏大但它在连续下降就说明生成器在学习。PSNR 这个指标要过了 30dB 才说明修复结果和原图比较接近小于 25dB 基本是模糊一团。4.2 推理阶段加载权重并生成修复结果训练完的权重一般会保存为.pth文件。推理脚本比训练简单得多读图、预处理、过模型、存图片。这个项目 result 目录下的输出就是这一步的产物。# infer.py —— 加载训练好的权重对带mask的图执行修复 import torch import cv2 import numpy as np # device 选择优先GPU device torch.device(cuda if torch.cuda.is_available() else cpu) # 假设 inpaint_simple.py 内部定义了生成器结构 from inpaint_simple import Generator model Generator().to(device) # 加载 checkpoints/best.pth注意 state_dict 的 key 要匹配 ckpt torch.load(./checkpoints/best.pth, map_locationdevice) model.load_state_dict(ckpt[model] if model in ckpt else ckpt) model.eval() img cv2.imread(./data/1.jpg) # BGR读入 img cv2.resize(img, (256, 256)) img img[:, :, ::-1].copy() # 转RGB img_t torch.from_numpy(img).float().permute(2, 0, 1).unsqueeze(0) / 255.0 # 归一化在模型forward内完成输出同样需要反归一化 with torch.no_grad(): out model(img_t.to(device)).squeeze(0).permute(1, 2, 0).cpu().numpy() out (out * 255).clip(0, 255).astype(np.uint8) cv2.imwrite(./result/my_inpainted.png, out[:, :, ::-1])map_locationcpu这个参数最容易忽略。如果你在 GPU 机器上训练拿到 CPU 机器上推理不加map_locationtorch.load直接报错说找不到 CUDA 设备。跑推理时有个细节要注意model.eval()一定要加否则 BatchNorm 和 Dropout 会按训练模式计算输出颜色偏淡、纹理抖动。4.3 从脚本到结果两个模型效果差异的直观判断我拿 data 目录下的图实际跑了对比。inpaint_simple.py的输出整体干净但补出来的区域偏平滑纹理感弱适合浅划痕inpaint_complex.py的对抗训练让缺失区域有更丰富的细节边缘也更锐利代价是训练时间成倍增长。有个亲测有效的判断方法直接把修复图和原图叠加用像素差定位差异区域。如果差异集中在 mask 边界上说明模型学到了上下文如果整张图都有差异多半是归一化或预训练权重出了问题。5. 常见问题排查显存、不收敛与备份文件三个高频坑5.1 显存溢出报错 OOM 时先别急着换显卡现象训练到第二个 batchCUDA out of memory直接炸掉有时候还在某个 epoch 的中段突然报错。原因分两种。一种是真的显存不够256×256、batch size 8复杂模型的激活值占用量直接爆掉 8GB 卡。另一种是显存碎片化PyTorch 在验证阶段也会分配显存训练和验证交替时累计占用走高到第三个 epoch 就崩。解决先看你是哪种情况。如果 batch size 8 报错改成 2 基本立竿见影如果 batch size 2 还报错把img_size从 256 降到 192显存占用会下降 40% 左右。另外在训练循环里加一句torch.cuda.empty_cache()放在验证之后、下一个 epoch 之前能缓解碎片化。最不济就上torch.utils.checkpoint用时间换显存。5.2 训练不收敛Loss 降不下去或反复横跳现象Loss_G前 10 个 epoch 从 200 降到 50后面 40 个 epoch 一直在 45 到 55 之间波动PSNR 卡在 26dB 上不去。原因通常出在判别器太强。复杂模型里判别器收敛快很快就会“看穿”生成器的假图梯度传回生成器后变成无效学习生成器无论怎么调整都骗不过判别器两个网络陷入拉锯。解决降低判别器学习率我一般会让生成器lr2e-4、判别器lr2e-5差一个数量级。另一个更实用的手段是调整损失权重把gan_weight从默认 0.01 降到 0.001让重建损失主导前期的学习方向。还有一种做法是给判别器输入加随机噪声让它的判断不那么“锋利”给生成器留出追赶空间。这个坑我踩了整整两天最后就是靠降判别器 lr 解决的比调网络结构有效得多。5.3 目录里的 .zbak 是什么别把备份文件当主力脚本现象整理项目时发现inpaint_simple.py.zbak和inpaint_simple.py都在根目录部分同学直接用.zbak当源码跑或者把README.md.zbak当成项目文档结果发现代码风格和 result 输出对不上。原因.zbak是项目作者在迭代过程中保留的备份文件。它可能是旧版本也可能是实验中间态不代表最终交付版本。这类文件出现在资源包里多是因为作者打包时没清理干净。解决统一以不带.zbak的文件为准。删除或忽略所有.zbak文件只保留.py、.md和 zip 解压出来的最新版本。另外资源包里同时有备份文件.zip里面大概率是更早一版的完整工程除非当前版本跑不通否则不建议打开避免两份代码混淆。6. 进阶用 PSNR 和 SSIM 给修复结果打分很多人跑完模型看一眼结果图觉得“还行”但这不算真正完成验证。做毕业设计或课程报告时光有图没数据支撑答辩很容易被问住。我建议用一个独立的评估脚本逐张计算 PSNR 和 SSIM这两个指标比肉眼判断客观得多。# evaluate.py —— 评估修复效果的客观指标 from skimage.metrics import peak_signal_noise_ratio, structural_similarity import cv2 import numpy as np def evaluate(gt_path, pred_path, mask_pathNone): gt cv2.imread(gt_path) pred cv2.imread(pred_path) # 尺寸一致性检查 assert gt.shape pred.shape, 修复图与真图尺寸不一致 # 整图PSNR psnr peak_signal_noise_ratio(gt, pred) # 结构相似度 ssim structural_similarity(gt, pred, channel_axis2) if mask_path is not None: mask cv2.imread(mask_path, 0) mask_area mask 127 # 只在mask区域计算指标 masked_psnr peak_signal_noise_ratio( gt[mask_area], pred[mask_area]) return psnr, ssim, masked_psnr return psnr, ssim psnr, ssim, mpsnr evaluate(gt.png, pred.png, mask.png) print(fPSNR: {psnr:.2f}dB, SSIM: {ssim:.4f}, Masked PSNR: {mpsnr:.2f}dB)PSNR 高于 30dB、SSIM 超过 0.95 是修复效果比较好的标准。但整图指标通常会虚高——如果缺失区域只占全图 5%哪怕补得一塌糊涂整图 PSNR 依然能过 30dB。所以我会强制把 mask 单独抠出来算 masked PSNR这个数值才真正反映修复质量。另一个进阶技巧是跨数据集验证。别只拿项目自带的 1.jpg、2.jpg 跑测试找十张不同内容的图片人工生成 mask统一跑一遍。如果 masked PSNR 在不同图像间波动很大说明模型过拟合到了训练数据的分布。这个步骤我每次都会做有一次发现模型对“人脸”效果惊艳、对“建筑”效果一塌糊涂就是靠跨数据集验证暴露出来的。还有个小习惯把训练时每个 epoch 的 PSNR 导成日志画成折线图。能直观看到模型是在持续收敛还是已经过拟合。如果验证集 PSNR 在第 60 个 epoch 开始掉头向下那就该停了再训练只会记住训练集的噪音细节。从那以后我每跑一轮修复实验都会强制执行一遍“切块、训练、验证集 PSNR、全图对比”的标准流程不再凭感觉说模型行不行。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表