
简介本资源是一套基于深度学习的图像修复算法Python实现专为计算机相关专业本科生毕业设计、课程设计及项目实战练习打造。代码经导师指导并获99分高分评价结构清晰、注释完整小白可直接运行调试覆盖简单与复杂场景下的图像修复任务。压缩包共14个文件含2个核心Python脚本inpaint_simple.py与inpaint_complex.py、8张效果对比图png/jpg格式、1份README.md项目说明文档、1个.gitignore配置文件整体5.33MB轻量易部署。目前已有178人学习下载资源附带完整数据集示例、修复前后结果图及典型目录结构data/、result/等便于理解数据流向、模型调用逻辑与输出可视化流程是开展CV方向实践项目的可靠起点。1. 图像修复不是“P图”而是让模型学会“脑补”一个能跑通、能调参、能落地的深度学习修复方案你手头有一张被划痕覆盖的老照片或者一段监控视频里关键区域被遮挡——传统插值或Photoshop修补只能糊弄人眼而基于深度学习的图像修复算法是让神经网络在像素层面“理解”语义结构后主动重建缺失内容。这不是魔法而是用大量带掩码mask的图像对训练模型让它学会当看到半张人脸时自动补全另一半当看到被涂黑的文字区域还原出原始字形。本项目提供的 Python 源码包不是玩具级 demo而是包含完整训练 pipeline、预处理脚本、主流 backbone如 U-Net GAN、可复现的评估指标PSNR/SSIM/LPIPS和清晰项目说明的工业级起点。它适合三类人刚学完 PyTorch 想做 CV 实战的新手、需要快速验证修复效果的算法工程师、以及正在为老旧档案数字化或安防视频增强找技术落地方案的交付团队。别被“源码.zip”吓住——真正卡住你的从来不是代码行数而是 mask 如何生成、loss 怎么加权、batch size 设多大才不 OOM、以及为什么修复结果总发灰——这些本文全部拆开讲透。2. 从零跑通用最小依赖复现核心修复流程2.1 环境准备避开 Python 版本与 CUDA 的“玄学冲突”本项目实测兼容 Python 3.83.10强烈建议锁定 Python 3.9——这是当前 PyTorch 官方二进制包支持最稳定的版本且与 OpenCV、torchvision 兼容性最高。不要用 conda install pytorch 最新 nightly 版也不要盲目升级到 Python 3.11部分 legacy torchvision ops 尚未适配。CUDA 版本必须与 PyTorch 编译时绑定的版本严格一致常见翻车点是nvidia-smi显示驱动支持 CUDA 12.2但torch.version.cuda返回 11.8——这说明你装的是 CUDA 11.8 编译的 PyTorch而非驱动版本决定一切。# 推荐命令创建干净环境并安装确定版本 conda create -n inpaint python3.9 conda activate inpaint pip install torch2.0.1cu118 torchvision0.15.2cu118 --extra-index-url https://download.pytorch.org/whl/cu118 pip install opencv-python4.8.1 numpy1.23.5 scikit-image0.20.0 tqdm4.66.1提示torchvision0.15.2cu118中的cu118不是后缀而是 PyTorch 官方 wheel 包命名规范表示该包由 CUDA 11.8 编译。漏掉它会导致torchvision.ops.roi_align等算子不可用后续训练直接报AttributeError。2.2 数据准备VOC / COCO / Places2 三选一但 mask 必须自己生成项目源码中data/目录下通常只放 placeholder真实训练需自行准备数据集。我们以 VOC2012 为例因其标注精细、图像质量高、license 允许商用下载 VOC2012 trainval 数据约 2GB解压后得到JPEGImages/原始图和SegmentationClass/语义分割图关键动作生成结构化 mask。不能直接用随机矩形遮挡太简单也不能用 Photoshop 手绘不可复现。本项目采用generate_masks.py脚本基于边缘检测 随机腐蚀膨胀生成“语义感知 mask”# generate_masks.py import cv2 import numpy as np from pathlib import Path def create_edge_mask(img_path, output_dir, erosion_iter3, dilation_iter1): img cv2.imread(str(img_path)) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) # 提取强边缘 kernel np.ones((3,3), np.uint8) # 先腐蚀让边缘变粗再轻微膨胀连接断点模拟真实遮挡形态 mask cv2.erode(edges, kernel, iterationserosion_iter) mask cv2.dilate(mask, kernel, iterationsdilation_iter) # 反转边缘为 0待修复区背景为 255保留区 mask 255 - mask cv2.imwrite(str(output_dir / f{img_path.stem}_mask.png), mask) # 批量生成 voc_img_dir Path(VOC2012/JPEGImages) mask_dir Path(data/masks) mask_dir.mkdir(exist_okTrue) for p in voc_img_dir.glob(*.jpg): create_edge_mask(p, mask_dir)逻辑说明Canny 边缘代表物体轮廓将其反转后作为 mask意味着模型需修复的是“轮廓内部区域”这比纯随机矩形更贴近真实场景如人脸被墨水滴遮挡往往沿五官边缘扩散。参数erosion_iter3控制遮挡宽度3 像素宽的墨迹dilation_iter1防止边缘断裂导致修复不连贯。2.3 模型加载与单图推理三行代码验证 pipeline 是否通畅源码中inference.py是最短路径验证入口。不要一上来就跑训练先确保前向推理能走通# inference.py import torch from PIL import Image import numpy as np from model import InpaintingModel # 假设模型定义在此文件 # 1. 加载训练好的权重项目说明中会给出 checkpoint 路径 model InpaintingModel() model.load_state_dict(torch.load(checkpoints/best.pth, map_locationcpu)) model.eval() # 2. 加载原图 mask注意mask 必须是单通道 0/255 图0 表示待修复区 img np.array(Image.open(test.jpg).convert(RGB)) # [H,W,3] mask np.array(Image.open(test_mask.png).convert(L)) # [H,W], 0hole, 255valid # 3. 预处理归一化 转 tensor batch 维度 img_tensor torch.from_numpy(img.astype(np.float32) / 255.0).permute(2,0,1) # [3,H,W] mask_tensor torch.from_numpy((mask 0).astype(np.float32)) # [H,W], Truehole input_tensor torch.cat([img_tensor, mask_tensor.unsqueeze(0)], dim0) # [4,H,W] input_batch input_tensor.unsqueeze(0) # [1,4,H,W] with torch.no_grad(): pred model(input_batch) # 输出 [1,3,H,W]值域 [0,1] result (pred[0].permute(1,2,0).numpy() * 255).astype(np.uint8) Image.fromarray(result).save(output.jpg)参数说明map_locationcpu是为了跨设备兼容避免 GPU 训练后在 CPU 上加载报错mask 0是关键项目约定 mask 中 0 为待修复区域hole255 为有效区域valid与多数论文如 Context Encoder一致但与部分开源实现如 LaMa相反务必核对model.py中的 mask 处理逻辑torch.cat([...], dim0)将 mask 作为第 4 通道拼入输入这是 U-Net 类模型的标准做法而非单独传入 mask 张量。3. 训练配置详解为什么你的 PSNR 卡在 22dB 不动3.1 backbone 选型U-Net 是基线但必须加 attention 和 spectral norm项目源码默认使用 U-Net但直接套用经典结构效果平平。实测发现以下三处修改可将 PSNR 提升 1.52.0 dBEncoder 中加入 CBAM attention 模块在每个 down-sampling 后插入通道空间注意力让模型聚焦于破损区域周边纹理Decoder skip connection 前加 1×1 conv原始 U-Net 的 skip 是直接 concat但 encoder 特征与 decoder 特征尺度/语义层级差异大加 conv 对齐通道数并做轻量校准Discriminator 使用 spectral normGAN 训练不稳定主因是判别器过强对所有卷积层加 spectral norm而非 batch norm可有效抑制 mode collapse。# model.py 中 discriminator 片段 class Discriminator(nn.Module): def __init__(self, in_channels3): super().__init__() self.layers nn.Sequential( nn.Conv2d(in_channels, 64, 4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), SpectralNorm(nn.Conv2d(64, 128, 4, stride2, padding1)), # ← 关键 nn.LeakyReLU(0.2, inplaceTrue), SpectralNorm(nn.Conv2d(128, 256, 4, stride2, padding1)), nn.LeakyReLU(0.2, inplaceTrue), SpectralNorm(nn.Conv2d(256, 512, 4, stride2, padding1)), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(512, 1, 4, padding1) # 输出单通道 logits )注意SpectralNorm 是 torch.nn.utils.spectral_norm不是第三方库。它通过 SVD 分解约束权重矩阵的谱范数比 weight normalization 更稳定且无需额外参数。3.2 Loss 设计L1 Perceptual GAN但权重必须动态调整项目说明中常写 “loss 0.5L1 0.3Perceptual 0.2*GAN”这是典型误导。实际训练中三个 loss 的 scale 差异极大L1 在 0.010.1 量级PerceptualVGG feature loss在 0.52.0GAN 的 adversarial loss 则在 0.0010.01。硬编码固定权重必然导致某 loss 主导优化。我们的做法是L1 loss 保持原始 scalenn.L1Loss()Perceptual loss 除以 VGG 层输出的均值使其稳定在 ~1.0GAN loss 乘以一个衰减系数adv_weight 0.01 * (1 - epoch / total_epochs)避免早期判别器过强压制生成器。# trainer.py 中 loss 计算片段 vgg_loss perceptual_loss(pred, gt) # vgg_loss 已内部 normalize vgg_loss vgg_loss / vgg_loss.item() if vgg_loss.item() 0 else vgg_loss # 归一化到 1.0 adv_weight 0.01 * max(0, 1 - current_epoch / total_epochs) adv_loss adversarial_loss(disc_fake, True) * adv_weight total_loss l1_loss 0.8 * vgg_loss adv_loss # L1 权重设为 1.0VGG 固定 0.8GAN 动态3.3 Batch Size 与 Learning Rate显存不是瓶颈梯度累积才是真相很多人卡在 “显存不足”于是把 batch_size 设成 1lr 调到 1e-4 —— 这是最大误区。小 batch 导致 BN 统计不准尤其 U-Net 中的 upsample path且 GAN 的判别器需要足够样本才能区分真假。正确做法显存GB建议 batch_size是否启用梯度累积累积步数等效 batch_size84是416128是2162416否—16梯度累积代码极简optimizer.zero_grad() for i, (img, mask, gt) in enumerate(train_loader): pred model(img, mask) loss compute_total_loss(pred, gt, mask) loss.backward() # 不更新只累积梯度 if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()血泪经验accumulation_steps 必须整除len(train_loader)否则最后一个 batch 梯度未清零下一轮训练起始梯度错误。可在train.py开头加断言assert len(train_loader) % accumulation_steps 0。4. 避坑指南那些让修复结果发灰、边缘锯齿、文字模糊的致命细节4.1 现象修复区域整体偏暗对比度丢失像蒙了一层灰原因L1 loss 仅惩罚像素绝对误差对亮度一致性无约束且预处理时img/255.0归一化后模型输出值域 [0,1] 未经 gamma 校正直接保存导致 sRGB 显示偏暗。解决在inference.py保存前加 sRGB gamma 校正result (pred[0].permute(1,2,0).numpy() * 255).astype(np.uint8) # 添加 gamma2.2 校正sRGB 标准 result np.clip(result ** (1/2.2) * 255, 0, 255).astype(np.uint8) Image.fromarray(result).save(output_gamma.jpg)4.2 现象修复边缘出现明显锯齿或伪影尤其在文本/线条区域原因训练时 mask 边界未做 anti-aliasing抗锯齿导致模型学习到硬边先验且判别器输入未做高斯模糊预处理对高频噪声过于敏感。解决生成 mask 时用cv2.GaussianBlur(mask, (3,3), 0)对 mask 边缘做轻微模糊sigma0.5在Discriminator输入前加 blur layerclass BlurLayer(nn.Module): def __init__(self): super().__init__() self.blur torch.tensor([[1,2,1],[2,4,2],[1,2,1]], dtypetorch.float32) / 16 self.blur self.blur.reshape(1,1,3,3) def forward(self, x): return F.conv2d(x, self.blur.to(x.device), padding1) # 在 Discriminator.__init__ 中插入self.blur BlurLayer() # 在 forward 中x self.blur(x)4.3 现象修复后的文字笔画粘连、断裂无法识别原因VOC/COCO 数据集缺乏文字样本模型未见过字符结构且 L1 loss 对细线结构惩罚不足。解决数据层面在训练集末尾追加 10% 的合成文字数据用PIL.ImageDraw.text生成黑字白底图 随机 maskLoss 层面增加 Sobel 边缘 loss强化线条连续性def sobel_loss(pred, gt): sobel_x F.conv2d(pred, sobel_kernel_x, padding1) - F.conv2d(gt, sobel_kernel_x, padding1) sobel_y F.conv2d(pred, sobel_kernel_y, padding1) - F.conv2d(gt, sobel_kernel_y, padding1) return torch.mean(torch.abs(sobel_x)) torch.mean(torch.abs(sobel_y)) # sobel_kernel_x/y 为预定义 3x3 算子4.4 现象训练 loss 曲线震荡剧烈PSNR 波动超 3dB原因GAN 的判别器 loss 过低0.1或过高0.7表明 D/G 能力失衡且 learning rate 未 warmup。解决监控disc_real_loss和disc_fake_loss理想值应在 0.30.6 之间若disc_real_loss 0.2说明 D 过强在Discriminator最后一层前加 dropoutp0.3学习率 warmup前 5 个 epoch 线性从 0 升至 base_lrscheduler torch.optim.lr_scheduler.LinearLR( optimizer, start_factor0.001, end_factor1.0, total_iters5 )5. 进阶技巧如何让修复结果通过甲方验收—— 用 LPIPS 当“人眼裁判”5.1 为什么 PSNR/SSIM 不够它们根本不懂“像不像”PSNR 计算像素 MSESSIM 评估局部结构相似性但二者都严重偏向高频噪声——一张加了锐化滤镜的假图PSNR 可能比真图高 2dB。而 LPIPSLearned Perceptual Image Patch Similarity用 AlexNet/VGG 特征空间距离衡量与人类视觉感知高度一致。项目说明中若只提 PSNR大概率是学术 demo真要交付必须用 LPIPS。# eval.py from lpips import LPIPS lpips_fn LPIPS(netalex).cuda() # 或 netvgg def calculate_lpips(pred, gt): # pred/gt: [B,3,H,W] tensor, range [0,1] pred_norm (pred - 0.5) / 0.5 # LPIPS 要求 [-1,1] gt_norm (gt - 0.5) / 0.5 return lpips_fn(pred_norm, gt_norm).mean().item() # 示例修复图 vs 原图 lpips_score calculate_lpips(pred_tensor, gt_tensor) # 值越小越好0.15 为优秀注意LPIPS 需pip install lpips且依赖torchvision0.13。若报No module named torchvision.models.utils降级 torchvision 至 0.13.1。5.2 验证集构建别用训练集的 mask 做测试常见错误用generate_masks.py对验证集图片批量生成 mask然后评估。这会导致数据泄露——模型已在训练中见过同类 mask 结构。正确做法数据集mask 来源生成方式用途Train自动生成Canny 边缘 腐蚀膨胀训练Val手动绘制用 GIMP 画 3 种 mask① 矩形10%面积② 多边形模拟遮挡物③ 文字区域OCR 定位后挖空调参、早停Test真实场景从监控截图/老照片扫描件中手动圈出破损区最终验收手动绘制看似麻烦但能暴露模型泛化短板。例如若模型在“矩形 mask”上 PSNR28dB但在“文字 mask”上骤降至 22dB说明它没学会字符结构建模需回退加强文字数据。5.3 交付 checklist甲方不看代码只看这 5 个结果图别交 zip 包交一份 PDF 报告每页含项目内容说明原图清晰展示破损位置标红框出待修复区域mask二值图0修复区确认甲方认可遮挡范围修复结果RGB 图无压缩用 sRGB gamma 校正差分图pred - gtLPIPS 分数数值 解释“0.12接近人眼不可辨差异”我带过的三个交付项目甲方拒收的唯一理由都是“差分图红色太集中”而非 PSNR 数值。后来我们把差分图阈值从 10 调到 5即像素差 5 才标红配合 LPIPS 0.13一次过审。希望帮到你。本文还有配套的精品资源点击获取