简介:基于Python深度学习的GFPGAN图片修复算法实现源码,面向图像修复方向开发者与研究者,覆盖人脸修复、低分辨率照片增强、老照片翻新等典型场景,适合具备基础Python与GAN知识的读者直接学习与二次开发。压缩包共62个文件,主要包括26个py源码、多个yml/yaml配置文件与Markdown文档,另有png/jpg示例图、mdb数据库、pth模型文件及license等,整体6.22MB,结构清晰便于快速定位核心算法与训练推理逻辑。已有429人学习,具备一定参考热度。资源内含完整的训练与推理脚本、GFPGAN各版本网络结构实现、配置参数及使用说明,并附带演示图片与模型权重相关文件,可帮助读者理解生成对抗网络在图像修复中的落地应用,也能直接用于个人项目或学术实验。
1. 为什么还要做图片修复:GFPGAN 的定位与价值
一张 512 像素的糊脸图,普通超分模型放大后得到的是"更清晰的马赛克",GFPGAN 却能在放大的同时把眼睛、嘴巴、皮肤纹理按语义补回来。这个基于 Python 和深度学习框架实现的图像修复算法,把生成对抗网络、身份特征提取和 StyleGAN2 生成器串在一条推理链上,专门解决低质量人脸的修复与增强问题。源码包一共 64 个文件,其中 26 个 Python 源文件覆盖了网络定义、训练数据管线、推理入口和辅助工具,是典型的学术项目工程化结构。本文按照"架构 → 推理 → 训练 → 部署"这条链路拆开讲,重点落在模型各文件怎么组织、推理参数怎么选、自定义训练要动哪些配置,以及交付前容易踩的坑。适合已经跑过基础深度学习项目、想在图像修复方向深入复现并改写源码的工程师。
2. 源码布局与生成器架构:GFPGAN 不是单纯把 GAN 放大
2.1 26 个 Python 文件的分工:架构、训练、工具三类
把源码包解压后,可以看到项目根目录下有inference_gfpgan.py、train.py,核心代码全在gfpgan子包中。不要被 26 个 Python 文件的数量吓到,按职责归类后其实只有三块:网络结构定义、训练与数据逻辑、辅助脚本。下面这张表把关键文件与职责对应起来,后续排查问题时就清楚该打开哪个文件。
| 文件路径 | 职责 |
|---|---|
gfpgan/archs/gfpganv1_clean_arch.py | 主生成器,无 BatchNorm 版本,推理和训练行为一致 |
gfpgan/archs/stylegan2_clean_arch.py | StyleGAN2 骨干,提供注入式上采样通路 |
gfpgan/archs/gfpganv1_arch.py | 早期带 BN 的生成器版本,和 clean 版共存但通常不直接部署 |
gfpgan/archs/arcface_arch.py | ArcFace 人脸识别骨干,提取 512 维身份特征 |
gfpgan/archs/restoreformer_arch.py | RestoreFormer 风格的 Transformer 修复分支 |
gfpgan/models/gfpgan_model.py | 训练逻辑封装,组织 GAN loss 与感知 loss |
gfpgan/data/ffhq_degradation_dataset.py | 从 LMDB 读取 FFHQ 并做退化合成 |
inference_gfpgan.py | 推理入口,支持单张、批量以及背景上采样 |
train.py | 训练入口,解析 YAML 配置并拉起训练循环 |
scripts/convert_gfpganv_to_clean.py | 把带 BN 的旧权重转换成 clean 权重 |
scripts/parse_landmark.py | 为眼嘴增强分支准备特征点标签 |
archs目录下还有gfpgan_bilinear_arch.py和stylegan2_bilinear_arch.py,这两个文件对应使用双线性上采样替代转置卷积的兼容版本。如果训练时改了生成器的上采样方式,权重文件必须和架构严格对应,arch参数填错会直接抛出 shape mismatch。
2.2 GFPGANv1Clean 的 forward 骨架:身份码注入与特征空间修复
GFPGAN 的核心设计可以概括成一句话:先用退化图提取模糊的中间特征,再用 ArcFace 提供的身份码去引导 StyleGAN2 生成器逐级上采样。这样修复结果既能保留原本的身份信息,又能在特征空间里去除噪声和模糊。从gfpganv1_clean_arch.py里剥出来的逻辑骨架大致如下:
# 逻辑骨架,非完整源码,用于理解数据流向 def forward(self, x, return_latents=False): fea = self.encoder(x) # 退化图 -> 多尺度特征 identity_code = self.arcface(x) # 人脸 -> 512 维身份向量 latent = self.stylegan_decoder.style2latent(identity_code) # 身份码映射到 latent space out = self.stylegan_decoder(fea, latent) # 特征注入后逐级上采样修复 return out这段代码里最值得琢磨的是style2latent这一步。普通 GAN 修复模型是把整张图编码成一个全局向量,然后让生成器从零开始重建;GFPGAN 把身份特征的维度压得很低,生成器的语义主要由预训练的 StyleGAN2 权重决定,网络只需要学会在特征空间里做局部修正。这样做的直接好处是训练不容易崩,且对输入分辨率不敏感。
clean 版本把 BatchNorm 全部去掉,换成实例归一化和权重归一化的组合。原因很实际:BN 在训练时会统计当前 batch 的均值和方差,batch size 小的时候统计量抖动大,推理时又会切换到全局统计量,训练和推理行为不一致在 GAN 这种大模型上会被放大成明显的伪影。clean 架构在单卡小 batch 训练场景下明显更稳。
2.3 ArcFace 与 RestoreFormer:两个可选分支分别解决什么问题
arcface_arch.py提供了身份先验,它在整个人脸修复链路里几乎是必须的。没有身份约束,生成器很容易把一张人脸修成"看着自然但不像本人"的通用脸。ArcFace 分支在训练时参与身份一致性损失的计算,推理时提供 latent 引导,这就是修复结果能保留个人特征的原因。
restoreformer_arch.py是源码里保留但默认配置不常启用的分支。它设计用于非面部区域的语义修复,比如头发、衣物、背景中带有结构化纹理的部分。我在实际使用中一般把背景修复交给--bg_upsampler realesrgan,让独立的后台上采样模型去处理背景,主生成器专注五官区域。只有当背景里含有大量人脸相似结构时,再考虑打开 RestoreFormer 分支,代价是显存占用明显上升。
# gfpgan_model.py 中分支加载的简化示意 if opt.get('use_arcface', True): self.arcface = ArcFace(...) # 身份分支,默认开启 if opt.get('use_restoreformer', False): self.restoreformer = RestoreFormer(...) # 语义修复分支,按需开启3. 推理实操:inference_gfpgan.py 的参数、后台上采样与输出语义
3.1 环境准备:torch、basicsr、facexlib 与权重落位
源码包在requirements.txt里列明了依赖,我习惯用 conda 独立环境避免污染基础环境。基础安装命令如下,如果你的 CUDA 版本不同,torch 的安装方式要按自己的环境调整。
conda create -n gfpgan python=3.8 -y conda activate gfpgan pip install torch torchvision # 按本机 CUDA 版本安装 pip install basicsr facexlib pip install -e . # 以可编辑模式安装当前仓库这里有一个常见误区:直接pip install gfpgan虽然能装上包,但仓库根目录的inference_gfpgan.py是独立脚本,它依赖的是本地源码里的gfpgan.utils,与 PyPI 上的包版本不一定匹配。我一般用pip install -e .把当前目录安装成开发模式,这样改代码立即生效,脚本和包的版本也始终一致。权重文件需要手动放到experiments/pretrained_models/目录下,推理脚本默认从这里加载模型。
mkdir -p experiments/pretrained_models # 将下载好的权重放入该目录 ls experiments/pretrained_models3.2 命令行参数拆解:-i、-s、--bg_upsampler、--only_center_face
inference_gfpgan.py的参数设计得很集中,绝大多数场景只需要调整五六个。下面这张表是我实际使用中会关注的参数,括号里是常用取值。
| 参数 | 作用 | 我的使用习惯 |
|---|---|---|
-i | 输入路径,文件或目录 | 批量处理时直接传目录 |
-o | 输出目录 | 按任务分文件夹,避免覆盖 |
-s | 上采样倍数 | 修复老旧照片用 2,超分场景用 4 |
--bg_upsampler | 背景上采样器,realesrgan或none | 追求整体效果用realesrgan,只想做人脸修复用none |
--bg_tile | 背景上采样分块大小 | 显存不足时从 400 降到 200 |
--face_upsample | 先对人脸区域单独超分再融合 | 小脸区域多的图建议开启 |
--only_center_face | 只处理图像中心人脸 | 多人合影且目标明确时使用 |
--aligned | 输入是否已按人脸对齐裁剪 | 直接喂cropped_faces下的图时开启 |
一个典型的完整命令长这样:
python inference_gfpgan.py \ -i inputs/whole_imgs \ -o results \ -s 2 \ --bg_upsampler realesrgan \ --bg_tile 400 \ --face_upsample-s 2表示把输入图像的长边放大到原来的两倍,如果原图本身分辨率和清晰度尚可,-s 2的修复痕迹最轻;-s 4会让背景区域有更强的锐化感,但如果背景本身很模糊,放大后反而会暴露压缩伪影。--bg_tile是分块参数,显存不够时优先调它而不是调-s,因为分块大小直接控制背景模型单次计算的数据量。
3.3 代码走读:从整图输入到四类输出的处理链
看懂了参数,再看脚本内部就轻松了。推理脚本的初始化部分核心是构造一个GFPGANer实例,如果需要背景增强,会先创建一个 RealESRGAN 的背景上采样器:
# inference_gfpgan.py 初始化逻辑(简化) from gfpgan.utils import GFPGANer bg_upsampler = None if args.bg_upsampler == 'realesrgan': from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer bg_upsampler = RealESRGANer( scale=4, model_path=args.bg_model, model=RRDBNet(num_in_ch=3, num_out_ch=3), tile=args.bg_tile, ) restorer = GFPGANer( model_path=args.model_path, upscale=args.upscale, arch='clean', bg_upsampler=bg_upsampler, face_upsample=args.face_upsample, )RealESRGANer的scale=4是背景模型内部的固定上采样倍数,与命令行里的-s是两回事。主修复器会把输入图按upscale参数调整到目标尺寸,背景模型再做额外的细节增强。理解这个双阶段结构对调参会很有帮助:-s控制整体的输出尺寸,bg_upsampler控制背景纹理的锐化程度。
处理每张图时,脚本内部先做人脸检测,把人脸从背景中裁剪出来,对齐后送入 GFPGAN 生成器,背景部分走背景上采样器,最后按原位置融合回去。这一步由GFPGANer.enhance完成:
cropped_faces, restored_faces, restored_img = restorer.enhance( img, has_aligned=args.aligned, only_center_face=args.only_center_face, )返回值有三个列表:cropped_faces是检测并裁剪出的原人脸,restored_faces是修复后的人脸,restored_img是融合后的完整图片。脚本会把它们分别保存为不同的后缀文件。所以一次推理得到的不只是最终图,还有中间产物,这对排查问题很有价值。如果最终效果不对,先看restored_faces里的人脸是否正常:人脸正常而整体图有问题,说明融合或背景环节出错;人脸本身就有伪影,问题在生成器输入或权重。
4. 自定义训练:退化数据管线、YAML 配置与 train.py 的串联
4.1 FFHQDegradationDataset:LMDB 存取与退化合成
想要拿自己的数据训练 GFPGAN,第一步是理解数据管线。gfpgan/data/ffhq_degradation_dataset.py负责从ffhq_gt.lmdb读取高质量人脸图,并按预设的退化流程生成低质量输入。LMDB 是一种高效的键值存储,这里用它代替一堆散落的 PNG 文件,避免大量小文件的随机 IO 拖慢训练。
# ffhq_degradation_dataset.py 逻辑骨架 class FFHQDegradationDataset: def __getitem__(self, index): gt = self._load_lmdb(index) # 512x512 的高清人脸 lq = degrade(gt) # 模糊 + 下采样 + 噪声 + JPEG 压缩 landmark = self._load_landmark(index) # 眼睛、嘴巴特征点 return {'gt': gt, 'lq': lq, 'landmark': landmark}degrade这一步的质量直接决定模型最终能修到什么程度。GFPGAN 的退化合成遵循"真实感退化"策略:高斯模糊的核大小、下采样倍率、噪声强度、JPEG 质量因子都不固定,而是在一定范围内随机抽取。这样做的目的是覆盖更广的低质量分布,修复模型才能应对真实场景里的老照片扫描件和网络压缩图。
源码里单独提供了tests/test_ffhq_degradation_dataset.py和对应的 YAML 配置。我在换数据集时一定会先跑一遍这个测试脚本,把lq和gt成对可视化,确认退化强度符合预期再启动训练。否则训了一天发现退化太轻,模型只学会了超分没学会修复,返工成本很高。
4.2 train_gfpgan_v1.yml 逐段拆解与关键超参
训练配置集中在options/train_gfpgan_v1.yml和train_gfpgan_v1_simple.yml两个文件里。YAML 的结构大致分成网络、数据、路径、训练、日志五个部分。下面的节选保留了常见字段:
network_g: type: GFPGANv1Clean out_size: 512 num_style_feat: 512 channel_multiplier: 2 decoder_input_scale: 1 datasets: train: type: FFHQDegradationDataset gt_path: data/ffhq_gt.lmdb io_backend: lmdb use_hflip: true train: total_iter: 450000 lr_g: !!float 1e-4 lr_d: !!float 4e-4 beta1: 0.9 beta2: 0.99 path: pretrain_network_g: experiments/pretrained_models/GFPGANv1.pthnetwork_g里的channel_multiplier是生成器宽度系数,调大能提升生成细节但显存翻倍。decoder_input_scale控制解码器输入特征图的缩放比例,一般保持默认。train部分里lr_g和lr_d分别是生成器和判别器的初始学习率,判别器比生成器快四倍是 GAN 训练里常见的设定,目的是让判别器先"跟上"生成器的更新节奏。
pretrain_network_g路径下的预训练权重是训练起点。GFPGAN 的常见训练策略是:用预训练的 StyleGAN2 生成器权重初始化解码器,ArcFace 权重也直接加载预训练模型,训练只微调修复相关的部分。这样做比从零训练快几个量级,也更稳定。
4.3 train.py 入口与训练循环:GAN loss 和感知 loss 如何组织
启动训练的命令很简洁,所有参数都走 YAML。
python train.py -opt options/train_gfpgan_v1.ymltrain.py内部调用的是 Basicsr 的train_pipeline。这条管线会依次完成:加载配置、初始化日志器、构建数据集与数据加载器、构建生成器和判别器、进入迭代训练循环。
# train.py 入口逻辑(简化) import argparse from basicsr.train import train_pipeline def main(): parser = argparse.ArgumentParser() parser.add_argument('-opt', type=str, required=True, help='训练配置文件路径') args = parser.parse_args() train_pipeline(args.opt)每一次迭代中,模型先更新判别器,再更新生成器。生成器的损失通常由四部分组成:GAN loss 让输出看起来真实,感知 loss 约束整体结构和纹理不失真,L1 loss 约束像素级接近,身份一致性 loss 让人脸仍是同一个人。身份 loss 的权重在 YAML 里通过network_g附近的参数控制,我习惯把身份 loss 权重设得偏高一点,尤其是做人像修复时,因为"像本人"比"像素接近"更重要。
4.4 显存不足时的调参策略:simple 配置、混合精度与 batch 选择
GFPGAN 完整配置在单张 24G 显存的卡上勉强能跑,资源受限时优先使用train_gfpgan_v1_simple.yml。这个配置去掉了部分可选分支,降低 decoder 的计算负载,适合单卡小显存环境先跑通流程。下面是几个常见调整项:
| 调整对象 | 完整配置 | 显存紧张时的做法 | 副作用 |
|---|---|---|---|
| batch_size | 8 | 2~4 | 梯度噪声变大,可配合调低学习率 |
| 混合精度 | 关闭 | 开启 AMP | 数值精度略降,但对 GAN 训练通常可接受 |
| 梯度累积 | 无 | 累积两到四步 | 等效增大 batch,但会拖慢单步速度 |
| 生成器宽度 | channel_multiplier=2 | 降至 1 | 输出纹理细节变弱 |
开启混合精度需要在 YAML 的train部分配置use_amp: true,Basicsr 管线会自动处理 GradScaler。注意混合精度与某些自定义 loss 的数值稳定性需要额外观察,如果训练过程中 loss 出现 NaN,先关掉 AMP 跑一百步确认是否由精度导致。显存实在不足时,降低channel_multiplier比降低batch_size效果更直接,因为生成器宽度影响的是每张图的显存占用,而 batch size 影响的是梯度统计质量。
5. 模型转换、landmark 与批量验证:交付前最后一步
5.1 convert_gfpganv_to_clean.py:把 BN 权重折叠干净
训练早期版本保存的权重可能带 BatchNorm,但要部署时我们通常希望用 clean 架构。scripts/convert_gfpganv_to_clean.py做的就是这件事:把 BN 层的缩放和偏移吸收进前一层的卷积权重,然后从 state dict 里移除 BN 参数。转换后生成器在推理时不再依赖任何 batch 统计量,单张图输入和批量输入的结果完全一致。
python scripts/convert_gfpganv_to_clean.py \ --input experiments/pretrained_models/GFPGANv1.pth \ --output experiments/pretrained_models/GFPGANv1_clean.pth转换完不要直接上生产,先拿同一张图分别用转换前后权重推理,对比restored_img。理论上视觉差异应该很小,如果背景或人脸出现明显色差,说明原权重里的 BN 统计值和卷积权重并没有被正确折叠,检查转换脚本是否覆盖了所有 BN 层。
5.2 parse_landmark.py:眼嘴增强需要什么标签
GFPGAN 在意眼睛和嘴巴区域单独增强,这依赖人脸特征点标签。parse_landmark.py负责批量解析 FFHQ 数据集中每张人脸的眼睛、眉毛、嘴巴位置,并把结果缓存到 pth 文件。训练数据管线在加载数据时会读取这些标签,用来引导增强分支只对关键区域做更大力度的修复。
python scripts/parse_landmark.py \ --gt_path data/ffhq_gt.lmdb \ --save_path data/eye_mouth_landmarks.pth运行一次即可,之后训练多个版本都能复用。如果换成自己的数据集,特征点解析这一步要重新执行,且要确认特征点坐标的尺度与数据集中图像的尺寸一致,否则区域 mask 会整体偏移。
5.3 批量推理验证的小技巧
交付前我会用下面的循环把测试集批量过一遍,配合--only_center_face只处理画面中心的人脸,既节省时间又能快速发现问题:
for f in inputs/whole_imgs/*.jpg; do out="results/$(basename "${f%.*}")" python inference_gfpgan.py \ -i "$f" \ -o "$out" \ -s 2 \ --only_center_face \ --bg_upsampler realesrgan done批量跑完后不要只看缩略图,放大到 100% 检查人脸边缘和背景交界处。如果restored_faces清晰但融合后边界发虚,把--bg_tile调大重新跑;如果背景纹理出现过强的水波纹,则改成--bg_upsampler none,只靠 GFPGAN 主体做修复,整体效果可能反而更干净。
本文还有配套的精品资源,点击获取