简介:面向计算机视觉与深度学习方向的学生、教师及从业者,这套图像修复算法程序基于深度学习与图像处理技术,针对老照片污渍、破损缺失、局部瑕疵等常见图像损伤,提供完整可运行的修复方案。资源共64个文件,压缩包仅2.15MB,结构清晰:42个Python脚本承担数据加载、模型构建、训练与评估流程;2个C++与2个CUDA源文件配合2个头文件实现底层优化;12张PNG与2张JPG展示修复样例与效果对比;README说明文档帮助快速上手。整体按数据、训练、评估、工具等模块组织目录,并附带测试集,便于定位与验证。程序覆盖从数据准备到测试集验证的完整链路,已测试运行成功,答辩平均分96分,目前已有593人学习下载。既适合作为课程设计、毕业设计参考,也便于初学者对照学习进阶;下载后如遇运行问题,作者支持远程教学。
1. 图像修复为什么不能只靠卷积堆叠
老照片上的划痕、信号传输造成的数据块丢失、写字遮挡形成的污渍,都可以归为图像修复问题。传统方法用偏微分方程扩散周边像素,处理细线还凑合,缺损面积一大就输出一片模糊。这个项目提供了一整套基于深度学习的图像修复算法实现,代码覆盖数据读取、掩码生成、网络训练、指标评估和结果可视化,不是单个模型文件,而是一条完整链路。如果你在做深度学习课程设计、毕业论文,或者想系统了解生成模型怎么落地,这套代码值得拆开看。下面从生成对抗原理、源码结构、数据集、训练评估和调优五个层面,把它还原成能自己复现的方案。
2. 图像修复的生成对抗原理与损失函数设计
2.1 从自编码器到GAN:缺失区域的条件生成
图像修复的本质是条件生成:给定可见像素和掩码,推断缺失区域最合理的像素分布。早期基于卷积自编码器的方案用L2损失回归完整图像,但L2是对多个合理内容的平均,边缘和纹理都会变钝。把问题转向生成对抗之后,生成器负责修复结构,判别器负责判断输出是否像真实图片,两者博弈使修复结果保留高频细节。所以这份代码选择GAN路线,不是炫技,而是L2回归在“大区域缺失”上的确做不到纹理级重建。
如果你想找一个从深度学习入门就能上手的项目,这套代码很合适:不需要理解复杂的数学推导,把CNN、激活函数、对抗训练和损失函数设计全部串在一个任务里,跑通一次就能建立整体直觉。
2.2 dnnlib与生成器结构
源码里的dnnlib和torch_utils是生成模型工程常见的工具库,legacy.py用来兼容旧版本模型的权重。gimg.py、show_img.py是结果展示入口,train.py是训练入口。从这个项目的文件组织看,生成器走的是带跳跃连接的U-Net路线:编码器逐层下采样获得语义,解码器通过跳跃连接保留边缘细节。激活函数用ReLU或LeakyReLU,归一化层建议用InstanceNorm,因为修复任务batch size通常不大,BatchNorm的全局统计量容易漂移。
判别器通常用PatchGAN,输出N×N的patch真伪图,而不是单一标量,这样能对局部区域纹理给出细粒度判断。这一结构与PyTorch社区大量修复项目一致,读代码时对照这个结构会容易很多。共享内存方面,如果机器只有8G显存,patch数量可以调小,但不要小于16×16,否则判别器会失去局部纹理约束能力。
2.3 对抗损失、L1与感知损失的搭配
训练过程不是一个损失在起作用,而是三种损失叠加。对抗损失让结果分布接近真实,L1损失约束像素级重建,感知损失约束深层特征一致。下面是一段简化的损失实现:
# 损失函数示意 import torch import torch.nn.functional as F def hinge_gan(g_real, g_fake): loss_d = F.relu(1 - g_real).mean() + F.relu(1 + g_fake).mean() loss_g = -g_fake.mean() return loss_d, loss_g def pixel_loss(pred, target, mask): return F.l1_loss(pred * mask, target * mask) def perceptual_loss(pred, target, vgg): pred_feat = vgg(pred) target_feat = vgg(target) return sum(F.l1_loss(p, t) for p, t in zip(pred_feat, target_feat))hinge_gan采用Hinge形式的对抗损失,在图像生成里比标准BCE稳定。pixel_loss里的mask把像素约束在缺失区域,防止背景主导梯度。perceptual_loss借助预训练VGG特征,牺牲一点像素误差换取感知一致。常见初始权重是L1=1、perceptual=1、adversarial=0.1,纹理不足时调大adversarial,结构歪斜时调大perceptual。下面这张表可以快速定位问题:
| 损失 | 约束对象 | 结果偏弱时的表现 |
|---|---|---|
| L1 | 缺失区域像素 | 结构稳但边缘平滑 |
| 感知损失 | VGG深层特征 | 语义乱但像素误差小 |
| 对抗损失 | Patch局部分布 | 纹理缺失或过锐利 |
3. 源码结构与数据集准备:从datasets.py到test_sets
3.1 源代码目录与文件职责
打开压缩包后,建议先读README.md,但不是所有课程设计项目的文档都写得完整,核心信息往往还要从文件命名里推断。根目录下最关键的有datasets.py、training/train.py、evaluation、metrics、test_sets。整体组织方式与常见PyTorch生成模型工程一致,先理解每个文件的职责,再动手改。
| 文件/目录 | 主要职责 |
|---|---|
| README.md | 文档说明:依赖、训练指令、目录约定 |
| datasets.py | Dataset实现,读取图像与掩码,做增强 |
| training/train.py | 训练入口,组织损失、优化器与日志 |
| evaluation/ | 评估脚本,加载权重并计算指标 |
| metrics/ | PSNR、SSIM、LPIPS等指标实现 |
| gimg.py / show_img.py | 修复结果拼接与可视化 |
| legacy.py | 加载旧版本权重时的兼容层 |
| test_sets/ | 固定测试集,保证评估可复现 |
| dnnlib / torch_utils | 工具函数、配置与网络模块 |
datasets.py决定了模型在训练时看到什么内容。如果输入只有图像而没有掩码,常见做法是在加载时动态生成掩码,模拟划痕和污渍。掩码形态直接影响泛化:只在中心画方块的模型,遇到细长划痕会失灵。我一般同时使用中心矩形、随机块和自由笔刷三种掩码。
提示:mask必须是单通道png,三通道图会让mask_t维度错乱,排查成本很高。
3.2 数据集目录约定与掩码生成
为了不破坏项目原有依赖,建议按下面的结构组织新数据:
dataset/ ├── train/ │ ├── image/ │ │ ├── 00001.png │ │ └── 00002.png │ └── mask/ │ ├── 00001.png │ └── 00002.png └── test_sets/ ├── image/ └── mask/每个图像文件必须有一个同名掩码文件。掩码是单通道灰度图,白色表示需要修复的区域,黑色表示保留区域。如果从公开数据集构造修复样本,首先生成的是无伤原图,再用程序合成掩码。下面的函数生成不规则自由笔刷掩码,比固定矩形更接近真实污渍:
# 生成自由笔刷掩码 import numpy as np import cv2 def make_free_form_mask(shape=(256, 256), strokes=12, max_len=32, min_w=2, max_w=6): mask = np.zeros(shape, np.uint8) for _ in range(strokes): x, y = np.random.randint(0, shape[1]), np.random.randint(0, shape[0]) for _ in range(np.random.randint(5, 12)): angle = np.random.uniform(0, 2 * np.pi) length = np.random.randint(8, max_len) x2 = int(np.clip(x + length * np.cos(angle), 0, shape[1] - 1)) y2 = int(np.clip(y + length * np.sin(angle), 0, shape[0] - 1)) cv2.line(mask, (x, y), (x2, y2), 255, np.random.randint(min_w, max_w)) x, y = x2, y2 return maskstrokes控制笔刷条数,min_w和max_w控制缺损宽度。生成后保存为png而不是jpg,jpg压缩会改变掩码边界。训练集每张图建议预生成5到10种掩码,测试集固定一种掩码,这样指标才能对比。
3.3 数据加载与增强的代码实现
datasets.py的核心是继承torch.utils.data.Dataset并重写__getitem__。一个可用的实现如下:
import glob, random import numpy as np import torch from PIL import Image from torch.utils.data import Dataset class FixDataset(Dataset): def __init__(self, image_dir, mask_dir, size=256): self.image_paths = sorted(glob.glob(image_dir + '/*.png')) self.mask_paths = sorted(glob.glob(mask_dir + '/*.png')) self.size = size assert len(self.image_paths) == len(self.mask_paths), 'image与mask数量不一致' def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img = Image.open(self.image_paths[idx]).convert('RGB') mask = Image.open(self.mask_paths[idx]).convert('L') img = img.resize((self.size, self.size), Image.BILINEAR) mask = mask.resize((self.size, self.size), Image.NEAREST) if random.random() < 0.5: img = img.transpose(Image.FLIP_LEFT_RIGHT) mask = mask.transpose(Image.FLIP_LEFT_RIGHT) img_t = torch.from_numpy(np.array(img)).permute(2, 0, 1).float() / 127.5 - 1 mask_t = (torch.from_numpy(np.array(mask)).float() > 127).float().unsqueeze(0) return img_t, mask_t图像转tensor后归一化到[-1,1],与生成器输出层的tanh激活函数匹配。掩码resize用NEAREST,避免出现把0.5阈值误判的中间灰度。和训练YOLOv8自己的数据集类似,路径配对是最大坑:文件名不一致、目录里有隐藏文件都会让长度断言失败。我一般会在初始化时打印前几条路径,确认image和mask后缀一致再开始训练。
4. train.py训练入口与evaluation评估指标实操
4.1 训练脚本参数与启动命令
训练入口是training/train.py。启动前先确认GPU可用,最小建议是单卡8GB以上显存,batch_size从4开始,图像分辨率256。常用启动命令:
python training/train.py \ --data_dir ./dataset \ --checkpoint_dir ./results \ --batch_size 4 \ --lr 2e-4 \ --epochs 200 \ --image_size 256 \ --mask_mode free_form \ --gan_mode hinge--data_dir指向包含train和test_sets的上级目录。--mask_mode free_form表示训练时动态生成自由笔刷掩码,也可以换成fixed_center做小孔实验。--gan_mode控制判别器损失形式,hinge在训练稳定性上优于标准BCE。--lr是训练的关键,我一般把判别器学习率设为生成器的一半,防止判别器收敛太快导致生成器梯度消失。训练日志会周期性打印生成器总损失、L1损失、对抗损失和PSNR。如果前20轮L1下降明显但对抗损失不降,说明生成器在走向平均色块,需要调低L1权重或调高对抗权重。
4.2 PSNR、SSIM与LPIPS的计算逻辑
评估逻辑在metrics里。PSNR衡量像素误差,数值越高越好;SSIM衡量结构相似性;LPIPS用深度网络特征距离判断感知相似度,数值越低越好。参考实现:
# evaluation/evaluate.py 片段 import lpips from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim def calc_metrics(pred, ref, mask): pred = pred * mask + ref * (1 - mask) return { 'psnr': psnr(ref, pred, data_range=1.0), 'ssim': ssim(ref, pred, channel_axis=2, data_range=1.0), 'lpips': lpips_fn(torch.tensor(pred).permute(2, 0, 1).unsqueeze(0), torch.tensor(ref).permute(2, 0, 1).unsqueeze(0)).item() }pred * mask + ref * (1 - mask)让背景区域与参考图完全一致,避免大背景把PSNR拉高。PSNR高但LPIPS不理想,说明结果平滑但缺少纹理,SSIM此时往往也一般。LPIPS依赖预训练权重,运行前执行pip install lpips安装即可。评估时所有图片尺寸统一,否则feature map大小不一致会直接报错。
4.3 评估流程、可视化与常见陷阱
运行评估的通用方式:
python evaluation/evaluate.py \ --ckpt ./results/model.pth \ --test_dir ./test_sets \ --save_dir ./eval_output脚本会读取test_sets中的image和mask,输出修复图并保存。gimg.py和show_img.py负责将原图、mask、修复结果横向拼接,方便快速浏览。常见陷阱有三个:一是掩码resize时产生中间灰度,训练和评估的阈值不一致;二是通道顺序混乱,PIL读入是RGB,OpenCV读入是BGR;三是指标只算整图,没有限定修复区域。因此在评估脚本里,我会单独统计mask内部区域的PSNR,比整图数值更能反映修复质量。
5. 修复效果调优:掩码策略、感知权重与快速验证
5.1 一眼看出模型好坏的验证脚本
训练结束后,不要只盯指标,先跑一次快速推断,把图片和mask拼在一起看。加载权重时注意legacy.py兼容层,如果报键名不匹配,检查ckpt里是否有model字段:
# infer.py import torch from PIL import Image from torchvision import transforms ckpt = torch.load('results/model.pth', map_location='cpu') model.load_state_dict(ckpt['model'] if 'model' in ckpt else ckpt) model.eval() img = transforms.ToTensor()(Image.open('test.png').convert('RGB')) * 2 - 1 mask = transforms.ToTensor()(Image.open('mask.png').convert('L'))[:1] with torch.no_grad(): out = model(img.unsqueeze(0), mask.unsqueeze(0))前处理必须与训练保持一致:图像归一化到[-1,1],mask二值化。若权重是旧pickle格式,需要先经过legacy.py转换再加载,这一步文档没有写明时容易卡住。
5.2 按失败类型调参的顺序
大区域空洞出现模糊色块,优先调高对抗权重,从0.1提到0.5,同时把判别器学习率调低。细划痕修复后边缘锯齿明显,改为更小的笔画宽度和更多条数的掩码参与训练。修复区域纹理正确但整体偏色,检查图像归一化均值和训练图片色彩空间。训练损失正常但评估分数低,优先怀疑评估脚本的mask取反或resize方式不一致。最终确认时把原图、mask、修复图横向拼接,只看PSNR很容易被背景区域掩盖问题。
本文还有配套的精品资源,点击获取