
简介这是一套面向计算机相关专业毕业设计、课程设计及机器学习入门者的深度学习图像修复项目资料基于卷积神经网络与对抗式训练策略实现划痕修复、噪点消除和局部遮挡还原等图像缺失区域智能补全功能代码经导师指导并获99分评定适合需要完整实战案例的学习者。资源包共18个文件约5.61MB以png、jpg示例图片与修复结果图为主辅以py算法源码、md项目说明及zbak备份文件涵盖算法实现、技术文档、环境配置说明与示例数据集目录结构清晰便于按模块查阅。目前已有85人学习。项目文档提供从环境部署到模型训练的逐步操作指南配合inpaint_simple与inpaint_complex两套源码读者可完整复现训练与测试流程理解图像修复的模型搭建、数据预处理与效果评估思路对撰写论文或完成课程作业均有直接参考价值。1. 图像修复系统从零落地为什么你的第一个版本大概率会糊成一片你手里有一张老照片中间被撕掉一块或者一张截图被水印盖住了一大片。你想用深度学习把它补回来于是搜到了「基于深度学习的图像修复系统Python实现与项目文档」这个方向。这件事的本质是给定一张带缺失区域的图和一个二值掩码让模型学会在缺失区域生成语义合理、纹理连贯的像素。它解决的不是「把马赛克变清晰」这种超分问题而是「这块内容根本不存在你得凭空造出来还得像真的」。适合谁有 Python 基础、跑通过至少一个 CNN 分类或分割项目、想做一个能写进简历或直接交付的完整系统的人。但我要先把丑话说在前面如果你直接拿一个普通卷积自编码器去训缺失区域大概率会糊成一团灰色或者重复纹理因为普通卷积把缺失区域也当成有效像素参与了计算模型学到的是「猜平均值」而不是「重建结构」。这个坑我在第一次做图像修复时踩得结结实实后面会讲怎么绕过去。2. 图像修复的模型选型从普通卷积到门控卷积到底差在哪2.1 为什么普通卷积在缺失区域上会翻车普通卷积核在滑动时无论当前位置是有效像素还是缺失像素都会一视同仁地乘权重再求和。假设缺失区域用 0 填充那么卷积输出里就混入了大量「0 乘以权重」的项相当于模型在告诉网络「这里就是黑色或者零值」但实际这里只是未知。训练时损失函数又只关心最终输出和原图的差距网络很快学会一个偷懒策略把缺失区域预测成周围颜色的平均值因为这样至少不会在 L2 损失上爆炸。结果就是你看到的糊。常见做法是引入掩码感知机制。最直接的是部分卷积Partial Convolution每次卷积后根据掩码更新有效区域让后续层只对已知像素做卷积。另一种是门控卷积Gated Convolution用可学习的门控值动态决定每个位置的信息保留比例。两者都能明显改善结构连续性但门控卷积实现更简洁训练也更稳我一般会优先选它。2.2 用 PyTorch 实现门控卷积层的最小代码import torch import torch.nn as nn import torch.nn.functional as F class GatedConv2d(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, stride1, padding0): super().__init__() # 特征分支正常卷积提取特征 self.conv_feat nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding) # 门控分支输出 0~1 的权重决定每个位置保留多少特征 self.conv_gate nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding) self.sigmoid nn.Sigmoid() def forward(self, x): feat self.conv_feat(x) gate self.sigmoid(self.conv_gate(x)) return feat * gate # 逐元素相乘门控值抑制无效区域逻辑说明输入张量先分别经过两个卷积层一个负责生成特征一个负责生成门控信号。门控信号经过 Sigmoid 后落在 0 到 1 之间再与特征逐元素相乘。这样网络可以自己学习在缺失区域附近降低门控值避免无效信息向前传播。参数说明in_channels和out_channels按你的网络宽度设定第一层通常是 4RGB 三通道加掩码一通道到 64kernel_size常用 3 或 5修复任务里 5 能捕获更大范围上下文但计算量也上去padding设为kernel_size // 2保持空间尺寸不变。注意门控分支的初始化不要用全零否则训练初期门控全开等于没有门控我一般用默认初始化即可。2.3 生成器整体结构怎么搭一个能用的修复生成器通常是编码器-解码器加跳跃连接。编码器用步长为 2 的门控卷积逐层下采样把空间尺寸降到 1/4 或 1/8同时通道数翻倍解码器用最近邻上采样加门控卷积恢复尺寸。跳跃连接把编码器浅层的高分辨率特征直接拼到解码器对应层帮助恢复纹理细节。中间可以插入 4 到 8 个空洞卷积残差块扩大感受野让模型在补全大区域时能看到更远的上下文。输出层用普通卷积加 Tanh把像素值压到 -1 到 1 之间和归一化后的输入匹配。提示不要一上来就堆很深的网络。修复任务的数据量通常不大生成器超过 20 层很容易过拟合验证集上的 PSNR 会先升后降。3. 数据准备与掩码生成不规则掩码才是真实场景3.1 数据集从哪来、怎么组织公开的修复数据集常见的有 Places2 和 CelebA前者场景多样后者人脸集中。如果你做的是通用修复Places2 更合适如果专做人脸修复CelebA 足够。但不管用哪个你都需要自己生成掩码因为真实缺失区域很少是规则矩形。目录结构建议这样组织dataset/ ├── train/ │ ├── images/ # 原图jpg 或 png │ └── masks/ # 对应的二值掩码png 格式白色为缺失 ├── val/ │ ├── images/ │ └── masks/掩码和原图必须同名比如0001.jpg对应0001.png。掩码里白色像素值 255 表示需要修复的区域黑色 0 表示保留区域。训练时把原图归一化到 -1 到 1掩码归一化到 0 到 1然后按通道拼接送入网络。3.2 用 OpenCV 生成不规则掩码的脚本import cv2 import numpy as np import random def random_irregular_mask(h, w, max_strokes8): mask np.zeros((h, w), dtypenp.uint8) for _ in range(max_strokes): # 随机起点和终点模拟笔刷划痕 x1, y1 random.randint(0, w-1), random.randint(0, h-1) x2, y2 random.randint(0, w-1), random.randint(0, h-1) thickness random.randint(5, 25) # 用随机曲线连接两点增加不规则性 pts np.array([[x1, y1], [(x1x2)//2 random.randint(-30,30), (y1y2)//2 random.randint(-30,30)], [x2, y2]]) cv2.polylines(mask, [pts], False, 255, thickness) return mask # 示例为一张 256x256 的图生成掩码 mask random_irregular_mask(256, 256) cv2.imwrite(mask_sample.png, mask)逻辑说明每次循环随机取两个点在中间加一个带随机偏移的控制点用cv2.polylines画一条粗线。多条线叠加后形成不规则的缺失区域。参数说明max_strokes控制线条数量8 到 12 条能覆盖 10% 到 30% 的面积比较接近真实破损thickness控制线宽5 到 25 像素对应不同尺度的缺失。注意掩码要保存为单通道 PNG不要用 JPG否则边缘会产生压缩伪影训练时模型会学到这些伪影。3.3 数据加载器里必须做的两件事第一掩码要随机翻转和旋转但原图和掩码必须同步变换否则掩码对不上原图模型直接学废。第二不要把整张图都归一化到 0 到 1 就完事原图归一化到 -1 到 1 和 Tanh 输出匹配掩码保持 0 到 1。我见过有人把掩码也归一化到 -1 到 1结果门控卷积的输入范围乱了训练 loss 震荡得厉害。4. 训练策略与损失函数L1 只是起步感知损失和风格损失才是关键4.1 损失函数怎么组合只用 L1 损失模型会倾向于生成模糊但平均误差小的结果。要生成清晰的纹理必须加入感知损失和风格损失。感知损失用预训练 VGG 网络提取特征比较生成图和原图在特征空间的差距风格损失计算特征图的 Gram 矩阵差异约束纹理风格一致。常见权重是 L1 为 1感知损失为 0.05风格损失为 120对抗损失为 0.1。但这不是铁律你的数据集和掩码比例不同权重需要微调。import torch import torch.nn as nn import torchvision.models as models class VGGPerceptualLoss(nn.Module): def __init__(self): super().__init__() vgg models.vgg16(pretrainedTrue).features[:16].eval() for p in vgg.parameters(): p.requires_grad False self.vgg vgg self.criterion nn.L1Loss() def forward(self, pred, target): # 输入范围 -1~1VGG 期望 0~1需要转换 pred_feat self.vgg((pred 1) / 2) target_feat self.vgg((target 1) / 2) return self.criterion(pred_feat, target_feat)逻辑说明截取 VGG16 前 16 层作为特征提取器不更新参数。输入从 -1 到 1 映射到 0 到 1 后送入 VGG计算特征图的 L1 距离。参数说明features[:16]对应到第三个池化层之前能捕获中层纹理特征如果你需要更抽象的结构约束可以取到[:23]。注意 VGG 的预处理是减均值除标准差但这里为了简化只做了范围映射实际项目中最好补上标准化。4.2 训练循环里要看哪些指标每轮训练后在验证集上计算 PSNR 和 SSIM但不要只盯着这两个。PSNR 高的模型可能很糊SSIM 对结构敏感一些但也不完全代表视觉质量。我一般会同时保存验证集上随机 8 张图的修复结果肉眼过一遍。如果发现缺失区域边缘有接缝说明跳跃连接或者掩码更新有问题如果整体颜色偏灰说明 L1 权重太高或者感知损失太弱。注意训练初期不要开对抗损失先让 L1 和感知损失把结构稳住等 PSNR 不再明显上升后再加入判别器。否则生成器会被判别器带偏输出一堆高频噪声。4.3 学习率和批大小的经验值初始学习率设 2e-4用 Adam 优化器beta1 取 0.5beta2 取 0.999。批大小根据显存来8GB 显存跑 256x256 的图批大小 8 到 12 比较稳。如果显存不够不要硬撑把图裁到 128x128 先跑通流程再逐步放大。学习率在第 30 轮和第 60 轮各降一半总共训 100 到 150 轮。我试过用余弦退火效果和阶梯下降差不多但阶梯下降更容易复现。5. 避坑与排查图像修复系统最常见的 5 个翻车现场5.1 现象修复区域全是灰色或模糊色块原因普通卷积把缺失区域当有效像素模型学到的是局部平均值。解决换成门控卷积或部分卷积并在掩码更新逻辑里确保缺失区域不参与后续卷积。检查你的掩码是否在每层之后正确传播如果掩码没有随卷积更新门控就失效了。5.2 现象训练 loss 正常下降但验证集 PSNR 很低原因过拟合。修复任务的数据量往往不够生成器参数量又大。解决加数据增强随机裁剪、翻转、颜色抖动在生成器里加 Dropout 或者谱归一化减少残差块数量。我一般会把验证集比例提到 10%并且每 5 轮保存一次模型选验证集最好的那个而不是最后一轮。5.3 现象修复结果和周围区域有明显接缝原因跳跃连接直接把编码器特征拼到解码器但编码器特征里混入了缺失区域的零值导致接缝处颜色突变。解决在跳跃连接前也加门控或者用掩码对拼接特征做加权。另一个常见原因是归一化层用了 BatchNorm训练和推理的统计量不一致换成 InstanceNorm 会好很多。5.4 现象显存溢出报 CUDA out of memory原因批大小太大或者中间特征图没释放。解决先把批大小降到 4 甚至 2用梯度累积模拟大批量在验证阶段用torch.no_grad()包住前向传播如果还不行把生成器的通道数从 64 降到 32。不要盲目上混合精度修复任务对数值精度敏感fp16 容易导致 loss 变成 NaN。5.5 现象掩码边缘有锯齿修复后边缘不自然原因掩码生成时用了 JPG 压缩或者掩码没有做形态学平滑。解决掩码保存为 PNG生成后用cv2.GaussianBlur做轻微模糊再二值化让边缘过渡更自然。另外训练时可以对掩码做随机膨胀和腐蚀让模型适应不同边缘形态。6. 从能跑到好用用测试时增强和掩码细化把 PSNR 再提 1.5 dB模型训完之后直接推理往往不是最优的。我习惯在推理阶段加两个技巧。第一个是测试时增强把输入图做水平翻转、垂直翻转、旋转 90 度分别推理后再把结果逆变换回来取平均。这个操作不增加训练成本但能稳定提升 0.5 到 1 dB 的 PSNR。第二个是掩码细化推理时把掩码稍微膨胀几个像素让模型多修复一圈边缘区域然后用原始掩码把多余部分裁掉这样接缝会更自然。def tta_inference(model, image, mask): # image: 1x3xHxW, mask: 1x1xHxW preds [] for flip_h in [False, True]: for flip_v in [False, True]: img_t image.clone() mask_t mask.clone() if flip_h: img_t torch.flip(img_t, dims[3]) mask_t torch.flip(mask_t, dims[3]) if flip_v: img_t torch.flip(img_t, dims[2]) mask_t torch.flip(mask_t, dims[2]) with torch.no_grad(): out model(torch.cat([img_t, mask_t], dim1)) if flip_v: out torch.flip(out, dims[2]) if flip_h: out torch.flip(out, dims[3]) preds.append(out) return torch.mean(torch.stack(preds), dim0)逻辑说明对输入做四种翻转组合分别推理后逆变换回原方向最后取平均。参数说明翻转维度dims[3]是宽dims[2]是高不要搞反。注意掩码也要同步翻转否则模型看到的掩码和图像不对应。这个技巧对不规则掩码效果更明显因为不同翻转角度下缺失区域的上下文不同平均后能抑制单次推理的随机性。还有一个容易被忽略的点验证时用的掩码分布要和训练时一致。如果你训练用的是随机笔刷掩码验证时却用规则矩形PSNR 会虚高实际部署时效果很差。我一般会留一个固定的测试集掩码类型和真实场景对齐每次改模型都跑一遍这个测试集只看这个数字不被训练 loss 迷惑。最后说个习惯每次实验必须记录配置文件、随机种子和验证集指标不然过两周你根本想不起来哪个模型是哪个。我吃过这个亏三个模型文件混在一起最后只能全部重训。希望帮到你。本文还有配套的精品资源点击获取