2026/9/28 11:58:11

基于深度学习的图像修复系统实战:从CNN编解码到GAN对抗训练

基于深度学习的图像修复系统实战:从CNN编解码到GAN对抗训练 简介这是一套面向计算机相关专业毕业设计、课程设计及机器学习入门者的深度学习图像修复项目资料核心是用卷积神经网络与对抗式训练策略实现图像缺失区域的智能补全可处理划痕修复、噪点消除与局部遮挡还原等任务适合需要完整实战案例的学习者。资源包共18个文件约5.61MB以png与jpg示例图片、py算法源码、md项目说明及zbak备份文件为主另含gitignore等配置项目录结构清晰便于按模块查阅。项目提供完整算法实现、技术文档、环境配置说明与示例数据集文档中附有从环境部署到模型训练的逐步操作指南可帮助读者复现训练流程并理解网络结构与损失设计。目前已有84人学习适合作为毕业设计参考或课程作业案例。1. 图像修复系统到底在修什么从破损照片到可控生成很多人第一次听到「基于深度学习的图像修复系统」脑子里浮现的是把一张划痕老照片丢进去、几秒后焕然一新。真做过就知道图像修复Image Inpainting修的不是「旧」而是「缺」——它要在一张图里已知区域的基础上把缺失或损坏的像素合理地补出来。老照片去划痕、电商图去水印、人脸遮挡补全、医学影像去伪影本质都是同一个问题给定掩码mask标出待修复区域让模型生成语义合理、纹理连贯的内容。这个方向之所以值得投入是因为它比纯分类任务更能体现深度学习工程能力你要处理数据配对、掩码生成、损失设计、训练稳定性、推理拼接每一步都有坑。用 Python 落地一套可跑通的图像修复系统既能当深度学习实战项目案例也能直接迁移到去水印、去物体等真实需求。本文面向想动手复现的从业者从环境配置讲到训练排错把能抄的代码和参数边界都摊开。适合有 Python 基础、想搞懂 gan 图像修复和 CNN 编解码结构的人新手能跟步骤走熟手能直接看参数和踩坑部分。2. 图像修复的技术路线选型从 CNN 编解码到 GAN 对抗2.1 为什么主流方案是「生成器 判别器」而不是单纯回归如果只用 CNN 做像素回归输入破损图输出完整图算 L1/L2 损失训练会很稳但结果往往模糊。原因很直接缺失区域有无数种合理填法L2 损失会让模型输出这些可能性的「平均值」而平均值在视觉上就是糊的。这就是图像修复里最经典的玄学——损失降得很低图却没法看。GAN 的思路是引入判别器让它判断「这张图是真实完整图还是模型补出来的」生成器为了骗过判别器就必须生成更锐利、更符合真实分布的纹理。常见做法是生成器用编解码结构Encoder-Decoder或 U-Net判别器用 PatchGAN对局部图块判真假损失用「L1 重建损失 对抗损失」加权组合。L1 保证整体结构不跑偏对抗损失负责纹理细节。选型上我一般这么分如果只是去固定位置水印、掩码规则U-Net L1 就够训练快、可控如果要修不规则破损、追求纹理真实感上 GAN如果缺失区域很大超过图像 40%普通卷积会失效得用带部分卷积Partial Conv或门控卷积Gated Conv的结构让网络只对有效像素做卷积。2.2 环境配置Python、PyTorch 与依赖的版本对齐环境是新手翻车最多的地方。深度学习环境配置的核心矛盾是 CUDA 版本、PyTorch 版本、显卡驱动三者必须对齐。我一般推荐用 conda 建独立环境避免污染系统 Python。# 创建独立环境python 版本选 3.9 或 3.10兼容性最好 conda create -n inpainting python3.10 -y conda activate inpainting # 安装 PyTorch注意 cu118 要和你的驱动匹配 # 驱动版本 520 一般可用 cu118驱动旧就换 cu116 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 其余依赖 pip install opencv-python pillow numpy tqdm tensorboard装完必须验证 GPU 是否真的可用很多人装完发现跑的是 CPU训练慢十倍还找不到原因import torch print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available()) print(device:, torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU)如果cuda available是 False先查驱动版本nvidia-smi再对照 PyTorch 官网的版本矩阵重装别硬扛。用 vscode 配置 python 环境的话记得在右下角把解释器切到刚建的inpainting环境否则终端里装对了、编辑器里还是老环境这种坑很隐蔽。2.3 数据集与掩码配对样本怎么造图像修复的数据是「成对」的一张完整图ground truth一张加掩码后的破损图掩码本身。公开数据集常见的是 Places2、CelebA但实际项目里更常用自己的图。核心操作是拿完整图随机生成掩码盖上去破损图作为输入完整图作为监督目标。掩码生成有两种主流规则掩码矩形、线条和不规则掩码随机游走生成的自由形状。不规则掩码更接近真实破损但训练更难。下面是一个不规则掩码生成的最小实现import numpy as np import cv2 def random_free_form_mask(h, w, num_strokes8, max_vertex12): 随机游走生成不规则掩码返回 0/1 矩阵1 表示待修复区域 mask np.zeros((h, w), np.uint8) for _ in range(num_strokes): # 随机起点 start_x, start_y np.random.randint(0, w), np.random.randint(0, h) pts [(start_x, start_y)] for _ in range(np.random.randint(3, max_vertex)): # 每步随机方向移动步长控制掩码粗细 dx np.random.randint(-w // 8, w // 8) dy np.random.randint(-h // 8, h // 8) nx np.clip(pts[-1][0] dx, 0, w - 1) ny np.clip(pts[-1][1] dy, 0, h - 1) pts.append((nx, ny)) cv2.polylines(mask, [np.array(pts)], False, 1, thicknessnp.random.randint(8, 24)) return masknum_strokes控制笔画数量越多掩码越碎thickness控制笔画粗细直接决定缺失面积比例。我一般把缺失比例控制在 10%~30%超过 40% 后普通卷积结构基本补不出合理内容得换部分卷积。生成掩码后记得做形态学膨胀让边界更自然否则模型会学到「掩码边缘一定是硬边」这种捷径。3. 用 PyTorch 搭一套可训练的图像修复网络3.1 生成器U-Net 跳连为什么能保住结构生成器我一般用 U-Net 变体编码器逐层下采样提取语义解码器逐层上采样恢复分辨率关键是编码器和解码器之间加跳连skip connection把浅层的高频细节直接送到对应层。没有跳连深层特征虽然语义强但空间细节丢光了补出来的区域和周围对不上。import torch import torch.nn as nn class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch, stride1): super().__init__() self.block nn.Sequential( nn.Conv2d(in_ch, out_ch, 4, stride, 1, biasFalse), nn.BatchNorm2d(out_ch), nn.LeakyReLU(0.2, inplaceTrue), ) def forward(self, x): return self.block(x) class UNetGenerator(nn.Module): def __init__(self, in_ch4, out_ch3, base64): super().__init__() # 输入是 破损图(3) 掩码(1) 4 通道 self.e1 ConvBlock(in_ch, base, 2) # 256 - 128 self.e2 ConvBlock(base, base*2, 2) # 128 - 64 self.e3 ConvBlock(base*2, base*4, 2) # 64 - 32 self.e4 ConvBlock(base*4, base*8, 2) # 32 - 16 self.bottleneck nn.Sequential( nn.Conv2d(base*8, base*8, 3, 1, 1), nn.ReLU(inplaceTrue)) # 解码器上采样后与编码器特征拼接 self.d4 nn.ConvTranspose2d(base*8, base*4, 4, 2, 1) self.d3 nn.ConvTranspose2d(base*8, base*2, 4, 2, 1) self.d2 nn.ConvTranspose2d(base*4, base, 4, 2, 1) self.d1 nn.ConvTranspose2d(base*2, out_ch, 4, 2, 1) self.out nn.Sigmoid() def forward(self, x): e1 self.e1(x); e2 self.e2(e1) e3 self.e3(e2); e4 self.e4(e3) b self.bottleneck(e4) d4 torch.cat([self.d4(b), e3], dim1) d3 torch.cat([self.d3(d4), e2], dim1) d2 torch.cat([self.d2(d3), e1], dim1) return self.out(self.d1(d2))in_ch4是因为要把掩码当额外通道喂进去让网络明确知道哪里要修。base64是通道基数显存不够就降到 32。跳连用torch.cat拼接注意拼接前通道数要对齐这是新手最容易报的维度错误。输出用 Sigmoid 把像素压到 0~1配合后面的 L1 损失。3.2 判别器与损失PatchGAN 怎么判真假判别器用 PatchGAN它不输出单个真假值而是输出一个 N×N 的矩阵每个元素代表原图一个感受野图块的真假。这样对局部纹理更敏感参数也少。class PatchDiscriminator(nn.Module): def __init__(self, in_ch3, base64): super().__init__() self.net nn.Sequential( ConvBlock(in_ch, base, 2), ConvBlock(base, base*2, 2), ConvBlock(base*2, base*4, 2), nn.Conv2d(base*4, 1, 4, 1, 1), # 输出 patch 级真假图 ) def forward(self, x): return self.net(x)损失组合是训练成败的关键。我一般用 L1 权重 1.0、对抗损失权重 0.05~0.1。对抗损失权重太高训练会震荡甚至崩太低纹理又不够锐利。这个比例没有理论最优得看数据。import torch.nn.functional as F def generator_loss(fake_out, real_img, fake_img, l1_weight1.0, adv_weight0.05): l1 F.l1_loss(fake_img, real_img) # 重建损失保结构 adv F.binary_cross_entropy_with_logits( # 对抗损失保纹理 fake_out, torch.ones_like(fake_out)) return l1_weight * l1 adv_weight * adv def discriminator_loss(real_out, fake_out): real_loss F.binary_cross_entropy_with_logits( real_out, torch.ones_like(real_out)) fake_loss F.binary_cross_entropy_with_logits( fake_out, torch.zeros_like(fake_out)) return (real_loss fake_loss) * 0.5判别器训练时记得对 fake 分支做detach()否则梯度会回传到生成器把两个网络的更新搅在一起。这是 GAN 训练里最隐蔽的 bug 之一。3.3 训练循环一个 epoch 里到底发生了什么把上面拼起来一个最小训练循环如下。注意生成器和判别器要交替更新且判别器更新时冻结生成器梯度。def train_one_epoch(gen, disc, loader, opt_g, opt_d, device): gen.train(); disc.train() for broken, mask, real in loader: broken, mask, real broken.to(device), mask.to(device), real.to(device) inp torch.cat([broken, mask], dim1) # --- 更新判别器 --- opt_d.zero_grad() fake gen(inp).detach() # detach 切断生成器梯度 real_out disc(real) fake_out disc(fake) d_loss discriminator_loss(real_out, fake_out) d_loss.backward(); opt_d.step() # --- 更新生成器 --- opt_g.zero_grad() fake gen(inp) fake_out disc(fake) g_loss generator_loss(fake_out, real, fake) g_loss.backward(); opt_g.step() return g_loss.item(), d_loss.item()优化器用 Adam学习率生成器 2e-4、判别器 2e-4beta 设 (0.5, 0.999)——这是 GAN 训练的常用配置beta1 用 0.5 而不是默认 0.9是为了减少动量带来的震荡。batch size 看显存256×256 的图一般 8~16。训练时用 tensorboard 记录 g_loss、d_loss 和验证图光看 loss 数值判断不了修复质量必须看图。4. 图像修复训练避坑那些让模型白跑的细节4.1 损失降了但图是糊的现象L1 损失稳定下降验证集 PSNR 也不低但修复区域明显模糊、和周围有接缝。原因对抗损失权重太低或者判别器太弱生成器退化成纯回归。解决把adv_weight从 0.05 提到 0.1检查判别器是否正常更新d_loss 不应长期接近 0。如果判别器碾压生成器降低判别器学习率或加标签平滑。4.2 训练中途 loss 突然变 NaN现象跑了几百步后 g_loss 或 d_loss 变成 nan之后全废。原因多半是学习率过高导致梯度爆炸或者 BatchNorm 在小 batch 下统计量不稳。解决把学习率降到 1e-4加梯度裁剪torch.nn.utils.clip_grad_norm_(gen.parameters(), 1.0)batch 太小时把 BatchNorm 换成 InstanceNorm。4.3 掩码边缘出现明显色差现象修复区域内部还行但边界一圈颜色对不上像贴了块补丁。原因训练时掩码边缘太硬模型没学到平滑过渡或者损失只在掩码内计算边界没人管。解决掩码生成后做高斯模糊再二值化让边界软化损失里对掩码膨胀一圈的区域额外加权。4.4 验证集效果好但真实破损图修不了现象在自己造的随机掩码上表现很好换成真实划痕、水印就崩。原因训练掩码分布和真实破损分布不一致模型过拟合了「随机游走」这种形状。解决训练掩码里混入真实破损样本或者用多种掩码生成策略矩形、线条、不规则按比例混合提升泛化。4.5 显存不够、batch 只能设 1现象256×256 的图 batch 设 4 就 OOM。原因U-Net 跳连拼接后通道数翻倍中间特征图占显存大。解决把base从 64 降到 32用混合精度训练torch.cuda.amp或者把图裁到 128×128 训练、推理时再放大。混合精度一般能省 30%~40% 显存代价是要处理 loss scaling。5. 推理部署与效果验证把模型变成能用的工具5.1 推理时的拼接与后处理训练用固定尺寸真实图片尺寸不一推理要处理任意分辨率。常见做法是滑动窗口切块、逐块修复、再拼回去重叠区域做加权融合避免接缝。简单场景也可以直接 resize 到训练尺寸修完再放大但会损失细节。torch.no_grad() def inpaint_image(gen, img_path, mask, device, size256): gen.eval() img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] img_r cv2.resize(img, (size, size)) / 255.0 mask_r cv2.resize(mask, (size, size)) broken img_r * (1 - mask_r[..., None]) # 掩码区域置零 inp torch.cat([ torch.from_numpy(broken).permute(2,0,1).float(), torch.from_numpy(mask_r).unsqueeze(0).float() ], dim0).unsqueeze(0).to(device) out gen(inp).squeeze(0).permute(1,2,0).cpu().numpy() # 只把掩码区域替换回原图保留未破损部分 out_full img_r * (1 - mask_r[..., None]) out * mask_r[..., None] out_full (out_full * 255).astype(np.uint8) return cv2.resize(out_full, (w, h))关键点是最后只替换掩码区域未破损像素原样保留否则模型会把好区域也「重画」一遍反而引入误差。size要和训练尺寸一致不一致效果会明显下降。5.2 效果验证别只看 PSNRPSNR、SSIM 是常用指标但它们和人的主观感受相关性有限——PSNR 高的图可能依然糊。我一般三个维度一起看一是定量指标PSNR/SSIM在掩码区域单独算二是视觉抽查随机抽 20 张看接缝和纹理三是下游任务验证比如修复后的人脸图能不能被识别模型正确识别这比指标更接近真实价值。验证维度指标/方法关注点像素级PSNR、SSIM仅掩码区数值高不代表不糊作参考感知级FID、人工打分纹理真实感GAN 方案重点看任务级下游识别/检测准确率修复是否真的有用稳定性不同掩码比例下的表现泛化能力别只测一种掩码5.3 一个提升纹理真实感的技巧两阶段训练直接端到端训 GAN 容易震荡。我常用的技巧是两阶段第一阶段只用 L1 损失训生成器让它先学会「补出大致合理的结构」收敛快且稳第二阶段加载第一阶段的权重加入判别器和对抗损失微调。这样既避免了早期 GAN 的不稳定又能拿到对抗损失带来的纹理提升。第二阶段学习率要调小一般 1e-4 甚至 5e-5否则会把第一阶段学好的结构破坏掉。血泪经验是别一上来就追求端到端 GAN先让 L1 版本跑通、能出图再逐步加对抗损失。我见过太多人卡在 GAN 训练不收敛上其实退一步用回归先验证数据管线和网络结构能省下大量调试时间。这套方案值不值得做取决于你的缺失区域比例和真实感要求——规则小面积缺失L1 版本就够用大面积不规则破损才值得上 GAN 和部分卷积。希望帮到你。本文还有配套的精品资源点击获取