简介这是一套基于GFPGAN算法的老照片修复Python设计源码面向图像处理开发者与老照片修复爱好者用于解决旧照片模糊、噪点、人脸细节失真等问题通过深度学习模型对人脸先验进行引导提升修复后的清晰度与真实感。源码包共51个文件压缩包大小6.09MB涵盖21个Python脚本、7个YAML配置、多张PNG/JPG样例图、Markdown文档、TXT说明、MDB数据库及PTH模型文件等脚本实现核心修复流程配置负责训练/推理参数文档指导环境安装与使用图片便于对比效果。包内还包含GFPGAN模型架构、训练入口、人脸关键点处理等模块并附预训练权重与测试数据可直接运行体验修复流程。目前已有491人浏览学习适合想要动手搭建GFPGAN环境、理解人脸先验修复原理或二次开发的老照片处理研究者。1. 老照片修复选GFPGANPython落地时人脸先验这条路为什么值得走老照片修复这个需求说起来简单做起来打架边缘要清楚、纹理要真实、颜色要自然更关键的是人脸要像“这个人”而不是AI凭空捏一张精致假脸。传统的去噪、去模糊、超分算法能把轮廓拉回来但人脸是高度结构化的对象退化一旦严重还原出来往往“不像本人”。GFPGANGenerative Facial Prior GAN走的是另一条路不靠算法从像素推测像素而是把退化人脸映射到预训练生成模型的隐空间让生成模型“记得”的人脸结构参与补全。在Python生态里落地尤其方便。这篇文章会从原理、源码、坑点到参数调优把这条路线讲透新手能跑通熟手能看到边界。2. GFPGAN原理与选型生成先验和两个必懂参数2.1 生成先验为什么“借”一张人脸记忆来补细节GFPGAN的名字拆开看Generative Facial Prior GAN核心在Facial Prior也就是人脸先验。这个先验不是统计意义上的“平均脸”而是借用预训练的StyleGAN2生成器。大致过程是一张退化人脸输入后先经过退化去除模块得到一个粗糙的恢复结果同时网络把人脸图像编码到StyleGAN2的隐空间W得到一组潜码接着潜码驱动生成器逐层产生特征图这些特征图包含生成器“记忆”的清晰人脸结构最后退化去除模块的特征和生成器特征做空间特征调制把结构信息逐步融回输入图像。这个思路和传统超分对比区别很清楚。传统方法里网络看到什么像素就重建什么像素退化严重时可用信息不足模型只能在像素层面“猜”。GFPGAN则是把人脸修复变成一个约束寻优问题既要满足输入图像的残余像素约束又要服从生成先验的概率分布。因为StyleGAN2在大量高质量人脸上训练过它内部特征图带有可信的五官结构和皮肤纹理分布所以最终输出看起来“合理”而不是“奇怪”。这也是GFPGAN被广泛应用、被多次复现和改造的根本原因。可以说它把老照片修复从“补像素”升级成了“在合理人脸分布里做选择”。理解这一点对后续参数调优有直接帮助。fidelity_weight调低之后输出会“变得年轻”或者“变得不像本人”本质上是生成先验在结果里占据了更大比重。明白这个原因你就不会把它当成随机出现的玄学问题而是知道背后有机制在起作用。2.2 与Real-ESRGAN、SwinIR对比选型边界在哪里做老照片修复很多人第一个想到的是通用超分。这里把选型边界讲清楚因为这些模型经常混着用选错会直接翻车。Real-ESRGAN是典型的通用超分模型。它在自然图像上表现优秀能把锯齿、模糊和压缩噪声清掉。但它缺少对人脸类别结构的强约束。退化严重时它可能把眼睛修成奇怪的形状因为模型不知道“眼睛应该长这样”。SwinIR则是纯Transformer结构局部和全局注意力让它在纹理重建上更细致但本质上同样是“从像素到像素”的映射没有生成先验兜底。GFPGAN的边界也很明确它主要对人脸区域有效。你拿一张风景老照片给GFPGAN输出往往不会比Real-ESRGAN更好有时候背景部分还会出现纹理不自然的区域。原因很简单生成先验是人脸先验对非人脸内容没有额外帮助。所以我在实际项目里的常见分工是GFPGAN负责修复人脸上的结构通用超分负责背景和衣物最后用蒙版把两段结果拼合。这个流程后面进阶章会给具体代码。这里还要提一下CodeFormer。很多人把GFPGAN和CodeFormer放在一起比较。两者都使用生成先验但CodeFormer用Transformer做潜码预测极端退化下的稳定性更好代价是参数量和推理时间都明显上升。处理大批量老照片时GFPGAN的性价比更突出。如果照片数量不多、单张质量极差且要求极限修复可以考虑CodeFormer。我的习惯是先跑GFPGAN效果不够再上CodeFormer做对比。这个顺序能省大量时间。模型核心思路人脸结构约束推理开销适合场景GFPGANStyleGAN2生成先验强中人脸修复、老照片人脸区域Real-ESRGAN通用退化超分弱中背景、风景、整体超分SwinIRTransformer超分弱中需要高频细节的重建CodeFormerTransformer潜码预测强高极端退化人脸的极限修复这个表是我的选型参考。实际项目里不是“谁替代谁”而是按区域和需求分工。人脸为主的老照片选GFPGAN风景建筑为主的老照片选通用超分两者都有就做区域合成。明确这个边界可以少走很多弯路。2.3 两个必懂参数fidelity_weight和sizeGFPGAN的推理接口里有两个参数几乎每次都要动。第一个是fidelity_weight也就是保真度权重。它的含义是输出结果在多大程度上保留输入图像在多大程度上相信生成先验。权重越大输出越贴近原图的姿态、轮廓和色彩但修复力度会被压制权重越小生成器自由发挥的空间越大细节补全越猛但五官可能被改动。我在实际项目里的经验是轻度退化照片用0.5到0.7主要清掉噪点和轻微模糊中度退化用0.3到0.5严重退化用0.1到0.3让模型更多依赖先验补全结构。这个值可以系统对比第6章会讲具体方法。第二个参数是size它控制送入生成器的人脸分辨率。常见的选择是512或1024。1024的细节明显更丰富皮肤纹理和眼睛高光都更真实但显存占用和推理耗时都会上涨。如果你的显卡显存不太充裕先跑512版本把效果验证通过再决定要不要上1024。还要注意size不仅影响生成器还影响人脸检测和裁剪逻辑。检测到的人脸会先对齐并缩放到size再送入网络。所以如果照片里的人脸区域本身非常小强行提高size并不会得到更多信息反而会把模糊放大。这种情况下更应该做的是配合通用超分先把人脸区域放大再做修复。参数调整没有固定答案要结合照片的退化程度来试。轻度照片你一看就知道不该把weight调太低不然脸就“换”了严重退化的照片也不敢把weight调太高不然结构补不回来。这两个参数加上前面的选型判断基本决定了修复效果的上限和下限。遇到效果不理想先别急着换模型先确认是否把这两个参数对应到了正确的退化程度。3. 环境搭建与最小推理命令3.1 环境依赖和踩过的版本坑GFPGAN项目基于PyTorch推理依赖不算复杂。我通常会准备一个独立的虚拟环境避免和别的项目互相污染。Python版本建议3.8以上PyTorch按你的显卡驱动选择对应版本。显卡相关的问题后面避坑章会单独展开这里先给一个能跑通的最小配置。python -m venv venv_gfpgan source venv_gfpgan/bin/activate pip install torch torchvision numpy opencv-python pillow这四条命令的含义第一二句创建并激活虚拟环境第三句安装基础依赖。torch和torchvision的版本要匹配否则import阶段就会报错。opencv-python和pillow负责图像读写numpy用来做张量转换。装完之后先做一个GPU可用性检查这是血泪经验。不要直接跑推理先确认环境是否真的能用上GPUimport torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only)如果第一句打印False后面所有推理都会慢到无法接受。常见的翻车原因有两个一是装成了CPU版PyTorch二是显卡驱动太老跟最新版CUDA不匹配。处理方式也很直接卸载torch重新安装对应版本或升级显卡驱动。检查这一步值得花两分钟后边省下的时间按小时算我每次搭环境都不会跳过。3.2 最小推理代码从加载模型到写回照片环境就绪后最核心的推理代码其实很短。常见做法是使用项目封装好的GFPGANer类它把“人脸检测、裁剪、修复、贴回”整个流程包在内部。下面是我平时用的最小代码import cv2 from gfpgan import GFPGANer # 初始化修复器 restorer GFPGANer( model_pathweights/GFPGAN.pth, # 换成你自己的权重路径 upscale2, # 放大倍数老照片通常用2 archclean, # 生成器结构推理用clean即可 channel_multiplier2, # 生成器通道倍率 ) # 读取照片OpenCV读进来是BGR格式 img cv2.imread(old_photo.jpg) if img is None: raise FileNotFoundError(照片路径不对检查一下) # 执行修复 cropped_faces, restored_faces, restored_img restorer.enhance( img, has_alignedFalse, # False表示输入不是对齐后的人脸 only_center_faceFalse, # 处理画面中所有检测到的人脸 paste_backTrue, # 修复后贴回原图 weight0.5, # fidelity_weight保真度权重按需调 ) cv2.imwrite(restored_photo.jpg, restored_img)这段代码的逻辑是GFPGANer初始化时加载生成器权重做好人脸检测和图像对齐的准备enhance方法先检测人脸裁剪并对齐到固定分辨率送入生成器修复再贴回原图。enhance返回三个值分别是裁剪出的原始人脸、修复后的人脸、修复后的整图。平时只需要取第三个restored_img。参数说明upscale2适合老照片因为老照片本身分辨率低修复后放大两倍看起来更舒服。archclean对应不包含额外退化分支的推理结构速度更快。weight先给0.5这是多数中等退化照片的中间值后面可以根据效果在0.1到0.7之间移动。如果你已经有对齐后的人脸图可以把has_aligned设为True跳过人脸检测环节。除了Python API很多源码包里也会带一个推理脚本通过命令行参数调起来。常见用法类似python inference_gfpgan.py \ --model_path weights/GFPGAN.pth \ --input old_photo.jpg \ --output output/ \ --upscale 2 \ --weight 0.5命令行方式适合快速验证单张效果批量处理我还是建议用Python API因为可以在循环里加容错和日志。3.3 批量修复写一个带容错的批处理脚本实际工作中很少只修一张。一翻老照片通常是一个文件夹。我习惯写一个批处理脚本用glob扫描目录逐张处理并将失败信息打印出来。这样一轮跑完回来只翻日志不用盯屏幕。import cv2 import glob import os from gfpgan import GFPGANer restorer GFPGANer( model_pathweights/GFPGAN.pth, upscale2, archclean, channel_multiplier2, ) input_dir input_photos output_dir output_photos os.makedirs(output_dir, exist_okTrue) for img_path in glob.glob(os.path.join(input_dir, *.jpg)): try: img cv2.imread(img_path) if img is None: print(f读取失败跳过: {img_path}) continue _, _, restored restorer.enhance( img, has_alignedFalse, only_center_faceFalse, paste_backTrue, weight0.5 ) out_path os.path.join(output_dir, os.path.basename(img_path)) cv2.imwrite(out_path, restored) print(f完成: {out_path}) except Exception as e: print(f处理失败: {img_path}, 错误: {e})这个脚本的容错逻辑值得保留。老照片扫描件质量参差有的文件本身就损坏有的照片里检测不到人脸这些情况都会被try-except拦住不会中断整个批次。continue关键字处理“读取失败”这类可跳过的情况。跑完一批后建议抽样检查输出尤其是那些原本人脸区域特别模糊的照片确认有没有被生成器改得不像本人。提示批量跑之前先拿三张不同退化程度的照片测试参数不要拿全部照片直接跑。参数不合适时批量跑完再返工时间成本很高。4. 源码结构与核心模块拆解4.1 整体目录推断工程结构的关键入口拿到一份GFPGAN设计源码不要急着从头读到尾。我的习惯是先看目录结构再找推理入口然后顺着数据流读核心模型。典型的工程结构大致如下gfpgan/ ├── gfpgan/ │ ├── __init__.py │ ├── utils.py # 人脸检测、图像对齐、颜色转换 │ └── archs/ │ ├── gfpgan_arch.py # 修复模型主结构 │ └── stylegan2_arch.py # 基于StyleGAN2的生成器模块 ├── weights/ # 预训练权重目录 ├── options/ # 配置文件训练或推理的yaml └── inference_gfpgan.py # 推理入口这个结构的核心在archs目录。utils.py里的函数通常包括调用人脸检测模型、根据关键点做仿射对齐、在RGB和BGR之间转换、把修复结果贴回原图。options目录下的yaml更多用于训练如果只做推理可以暂时不看。权重目录放预训练文件命名方式不同版本有差异选择你手里权重对应的配置即可。训练和推理的配置一般是分离的训练关心学习率、loss权重和数据路径推理只关心模型结构、输入分辨率和权重路径。读源码时我建议抓数据流而不是逐行阅读。图片经过人脸检测得到框和关键点根据关键点裁剪并仿射对齐送入生成器得到修复结果再根据原图框反向贴回。这个流程对应到代码里就是几个函数调用你花半小时能定位到具体位置后面调bug就有方向了。4.2 生成器架构特征提取和空间调制继续看生成器。GFPGAN的主结构通常是这样的一个退化去除分支常见实现是带跳跃连接的卷积网络负责从输入提取特征一个预训练StyleGAN2生成器分支负责提供人脸先验然后通过一系列特征调制层把两边融合。不同源码版本在细节上有差异但主流程相当一致。实际工程里的写法可能有差异我给出一个便于理解的简化结构class GFPGAN(nn.Module): def __init__(self, out_size512, channel_multiplier2): super().__init__() # 退化去除分支初步恢复并提取特征 self.degradation_net conv_block(3, 64, ...) # 生成先验分支StyleGAN2风格结构 self.generator stylegan2_module(...) # 调制融合层逐层融合先验特征 self.modulation_layers nn.ModuleList([...]) def forward(self, x): # x: 对齐后的人脸形状 [B, 3, 512, 512] # 1. 从退化图像提取特征 feat self.degradation_net(x) # 2. 编码到潜空间并生成先验特征 latent self.generator.encode(x) priors self.generator.generate(latent) # 3. 用先验特征调制恢复特征逐层融合 for mod_layer in self.modulation_layers: feat mod_layer(feat, priors) return feat这里的方法名和层定义是示意具体要看源码里的实际写法。但forward顺序很关键。如果输出颜色偏绿偏紫问题大概率在融合层的通道顺序或归一化方式如果人脸结构扭曲问题大概率在潜码编码环节也就是第2步。读代码时抓住这些锚点排查问题就不盲目。很多魔改版本会在前后端加卷积层调整通道数不要被这些细节绕晕主流程始终是“提取特征、生成先验、调制融合”三步。4.3 GFPGANer推理封装的真实逻辑GFPGANer是实际使用中接触最多的类。它的初始化会加载生成器权重、初始化人脸检测器、准备对齐工具。enhance方法是核心我来拆一下它内部的逻辑顺序def enhance(self, img, has_aligned, only_center_face, paste_back, weight): if not has_aligned: # 1. 人脸检测 boxes, landmarks self.detect_faces(img) if not boxes: return [], [], img # 没检测到人脸原样返回 # 2. 人脸裁剪和对齐 cropped [self.align_and_crop(img, b, l) for b, l in zip(boxes, landmarks)] else: cropped [img] restored [] for face in cropped: # 3. 归一化并送入生成器weight在这里生效 tensor self.img_to_tensor(face).cuda() with torch.no_grad(): out self.gfpgan(tensor, weight) restored.append(self.tensor_to_img(out)) if paste_back and not has_aligned: # 4. 按原检测框把结果贴回原图 result self.paste_back(restored, boxes) else: result restored[0] return cropped, restored, result这段是简化逻辑真实源码会更长但骨架一致。有几个容易忽略的细节。第一当人脸检测结果为空时enhance会直接返回原图这不一定是错误可能照片里确实没有正面人脸。第二weight参数不是跑完整个网络后做一次混合而是在调制融合过程中逐层生效所以你看到的效果是“结构调整保真约束”同时作用。第三paste_back依赖原始人脸框位置如果检测框偏移贴回的人脸会和背景有错位这个现象在多人照片里更常见。理解了这三个细节遇到输出图出现“人脸贴歪”这类情况你至少知道往哪个方向排查。5. 避坑指南GFPGAN落地最常见的5个问题5.1 现象显存OOM小图也爆显存原因很多人把一张几千像素的大图直接送进enhance没有做任何预处理。GFPGAN检测到人脸后会把人脸区域裁剪并缩放到固定尺寸但如果原图特别大、人脸数量又多人脸检测阶段和生成阶段的显存峰值都会超过预期。另外如果你选择的size是1024而显卡显存只有6G单张输入也可能爆显存。解决先把输入图片的长边降到一个合理范围比如2000像素以内。人脸特别多的合影可以分块或逐个人脸处理。还有一种做法是先用CPU做人脸检测确认人脸数量后再决定是否用GPU跑生成。我自己会在代码里加一个图像resize保护超过阈值就缩小避免批处理时因为少数超大图导致整体崩溃。5.2 现象输出整体偏色红花变成蓝花原因九成是RGB和BGR通道顺序问题。OpenCV读图是BGRPyTorch模型训练预处理通常用RGB。如果读图后直接归一化送模型输出再直接保存红蓝通道整体互换颜色就会非常奇怪。少数情况下fidelity_weight调得太低也会导致色温漂移但颜色不会是“通道互换”那种失真。解决在代码入口统一色彩空间约定。用cv2读图后转RGB送入网络拿到输出后再转回BGR保存。最稳妥的做法是封装一个小工具函数所有图像进出都走它避免每一处都手动转换。这个坑看起来小但排查起来很费时间因为输出看着“哪里都不对”很难第一时间想到是颜色空间问题。5.3 现象人脸被“换脸”了修复完不像照片里的人原因生成先验的副作用。当人脸退化严重、输入信息不足时生成器会用“记住”的常见人脸结构去补全甚至直接覆盖原有的五官特征。fidelity_weight设得太低时尤其明显。这个“替换”不是bug而是生成模型在信息不足时的合理行为。解决把weight调高例如从0.3调到0.6。如果还是不像可以先对人脸区域做一次轻度去噪降低退化的程度让模型不需要太多“脑补”。要正视一个边界极端模糊的人脸信息已经丢失没有任何参数能保证还原出本人身份。遇到这种情况我会和需求方说明只能做“看起来自然”的修复而不是“变回本人”。5.4 现象没有GPUCPU跑一张图要几分钟原因GFPGAN的生成器基于StyleGAN2计算量不小。CPU推理本来就不适合这类模型更麻烦的是如果安装的是CPU版PyTorch程序不会报错只会非常慢地跑完让你误以为哪里卡死了。解决优先确认torch.cuda.is_available()别在环境阶段就埋下性能隐患。如果确定要在CPU上跑可以缩小size到512并把只需要处理人脸的逻辑简化跳过不必要的后处理。控制预期CPU跑通流程可以但大批量生产还是需要GPU。不要在这个问题上投入过多优化时间性价比很低。5.5 现象多人合影只修复了中间那张脸原因GFPGANer的enhance方法里有一个only_center_face参数含义是“只处理画面中心的人脸”。很多人没注意它的行为导致多人群像只修了中间的人。另外人脸检测器的检测阈值偏高时小尺寸或侧面人脸会被漏掉。解决需要处理多人时显式设置only_center_faceFalse。如果漏检侧面或小人脸适当调低人脸检测的置信度阈值。注意阈值调低会增加误检比如把背景里的雕塑误认为人脸。这个阈值要根据批量数据的情况试没有绝对正确的值。另一个相关技巧是人脸框过大或过小都会影响贴回效果如果检测框严重偏离可以查看检测可视化结果而不是直接怀疑生成器。注意避坑有一个基本原则——改一个变量跑一次对照不要同时调多个参数。GFPGAN的参数之间会互相影响同时改几个出了问题很难定位到底是哪个引起的。6. 进阶三个让GFPGAN输出更可控的小技巧前面几章解决了跑通和排错最后分享三个我一直在用的控制技巧。它们不改变模型结构但能让输出更可控、调试更方便。6.1 用三张样本给fidelity_weight定标weight不是一个“设一次用一年”的参数。我的做法是挑三张有代表性的照片一张轻度退化、一张中度、一张严重每张跑0.1到0.7这组值把结果拼图对比。做完这组对比整批照片跑起来就有据可依轻度组用高weight严重组用低weight。对比时看两个维度身份相似度和纹理完整度不要只盯着“是否清晰”。我会把每次对比结果导出为一张对照图命名里带上日期后续复盘只看这张图。6.2 人脸和背景分开修复再合成GFPGAN强在人脸弱在背景。一个常见做法是人脸用GFPGAN修背景用通用超分处理再用羽化蒙版合成。关键代码是蒙版的构建和融合import cv2 import numpy as np mask np.zeros(face_result.shape[:2], np.float32) for x1, y1, x2, y2 in boxes: mask[y1:y2, x1:x2] 1.0 mask cv2.GaussianBlur(mask, (0, 0), feather)[..., None] blended (face_result * mask bg_result * (1 - mask)).astype(np.uint8)羽化半径feather要跟着人脸框大小走。框小就用小半径避免过渡把脸部的修复效果冲淡。这个技巧在多人照片里收益尤其明显背景衣物和墙面不会被人脸修复特有的纹理风格带偏。6.3 参数留痕让每一版输出都可回溯每跑一次实验把关键参数写进输出文件名比如restored_w05.jpg。隔天再回看你不会记得哪张图用了哪个weight。我所有的批量脚本都会在文件名里自动拼上参数这是最想推荐的一个习惯。老照片修复最贵的成本不是算力是反复验证效果所花的时间。希望帮到你。本文还有配套的精品资源点击获取