☰
GFPGAN老照片修复实战:人脸先验生成与Python落地指南
2026/10/9 17:08:18 网站建设 项目流程

简介:这是一套基于GFPGAN算法的老照片修复Python设计源码,面向图像处理开发者与老照片修复爱好者,用于解决旧照片模糊、噪点、人脸细节失真等问题,通过深度学习模型对人脸先验进行引导,提升修复后的清晰度与真实感。源码包共51个文件,压缩包大小6.09MB,涵盖21个Python脚本、7个YAML配置、多张PNG/JPG样例图、Markdown文档、TXT说明、MDB数据库及PTH模型文件等,脚本实现核心修复流程,配置负责训练/推理参数,文档指导环境安装与使用,图片便于对比效果。包内还包含GFPGAN模型架构、训练入口、人脸关键点处理等模块,并附预训练权重与测试数据,可直接运行体验修复流程。目前已有491人浏览学习,适合想要动手搭建GFPGAN环境、理解人脸先验修复原理或二次开发的老照片处理研究者。

1. 老照片修复选GFPGAN:Python落地时人脸先验这条路为什么值得走

老照片修复这个需求,说起来简单,做起来打架:边缘要清楚、纹理要真实、颜色要自然,更关键的是人脸要像“这个人”,而不是AI凭空捏一张精致假脸。传统的去噪、去模糊、超分算法能把轮廓拉回来,但人脸是高度结构化的对象,退化一旦严重,还原出来往往“不像本人”。GFPGAN(Generative 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和size

GFPGAN的推理接口里有两个参数几乎每次都要动。第一个是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可用性检查,这是血泪经验。不要直接跑推理,先确认环境是否真的能用上GPU:

import 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_path='weights/GFPGAN.pth', # 换成你自己的权重路径 upscale=2, # 放大倍数,老照片通常用2 arch='clean', # 生成器结构,推理用'clean'即可 channel_multiplier=2, # 生成器通道倍率 ) # 读取照片,OpenCV读进来是BGR格式 img = cv2.imread('old_photo.jpg') if img is None: raise FileNotFoundError('照片路径不对,检查一下') # 执行修复 cropped_faces, restored_faces, restored_img = restorer.enhance( img, has_aligned=False, # False表示输入不是对齐后的人脸 only_center_face=False, # 处理画面中所有检测到的人脸 paste_back=True, # 修复后贴回原图 weight=0.5, # fidelity_weight保真度权重,按需调 ) cv2.imwrite('restored_photo.jpg', restored_img)

这段代码的逻辑是:GFPGANer初始化时加载生成器权重,做好人脸检测和图像对齐的准备;enhance方法先检测人脸,裁剪并对齐到固定分辨率,送入生成器修复,再贴回原图。enhance返回三个值,分别是裁剪出的原始人脸、修复后的人脸、修复后的整图。平时只需要取第三个restored_img。

参数说明:upscale=2适合老照片,因为老照片本身分辨率低,修复后放大两倍看起来更舒服。arch='clean'对应不包含额外退化分支的推理结构,速度更快。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_path='weights/GFPGAN.pth', upscale=2, arch='clean', channel_multiplier=2, ) input_dir = 'input_photos' output_dir = 'output_photos' os.makedirs(output_dir, exist_ok=True) 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_aligned=False, only_center_face=False, paste_back=True, weight=0.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_size=512, channel_multiplier=2): 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读图是BGR,PyTorch模型训练预处理通常用RGB。如果读图后直接归一化送模型,输出再直接保存,红蓝通道整体互换,颜色就会非常奇怪。少数情况下,fidelity_weight调得太低也会导致色温漂移,但颜色不会是“通道互换”那种失真。

解决:在代码入口统一色彩空间约定。用cv2读图后转RGB,送入网络;拿到输出后再转回BGR保存。最稳妥的做法是封装一个小工具函数,所有图像进出都走它,避免每一处都手动转换。这个坑看起来小,但排查起来很费时间,因为输出看着“哪里都不对”,很难第一时间想到是颜色空间问题。

5.3 现象:人脸被“换脸”了,修复完不像照片里的人

原因:生成先验的副作用。当人脸退化严重、输入信息不足时,生成器会用“记住”的常见人脸结构去补全,甚至直接覆盖原有的五官特征。fidelity_weight设得太低时尤其明显。这个“替换”不是bug,而是生成模型在信息不足时的合理行为。

解决:把weight调高,例如从0.3调到0.6。如果还是不像,可以先对人脸区域做一次轻度去噪,降低退化的程度,让模型不需要太多“脑补”。要正视一个边界:极端模糊的人脸,信息已经丢失,没有任何参数能保证还原出本人身份。遇到这种情况,我会和需求方说明,只能做“看起来自然”的修复,而不是“变回本人”。

5.4 现象:没有GPU,CPU跑一张图要几分钟

原因:GFPGAN的生成器基于StyleGAN2,计算量不小。CPU推理本来就不适合这类模型,更麻烦的是,如果安装的是CPU版PyTorch,程序不会报错,只会非常慢地跑完,让你误以为哪里卡死了。

解决:优先确认torch.cuda.is_available(),别在环境阶段就埋下性能隐患。如果确定要在CPU上跑,可以缩小size到512,并把只需要处理人脸的逻辑简化,跳过不必要的后处理。控制预期:CPU跑通流程可以,但大批量生产还是需要GPU。不要在这个问题上投入过多优化时间,性价比很低。

5.5 现象:多人合影只修复了中间那张脸

原因:GFPGANer的enhance方法里有一个only_center_face参数,含义是“只处理画面中心的人脸”。很多人没注意它的行为,导致多人群像只修了中间的人。另外,人脸检测器的检测阈值偏高时,小尺寸或侧面人脸会被漏掉。

解决:需要处理多人时,显式设置only_center_face=False。如果漏检侧面或小人脸,适当调低人脸检测的置信度阈值。注意阈值调低会增加误检,比如把背景里的雕塑误认为人脸。这个阈值要根据批量数据的情况试,没有绝对正确的值。另一个相关技巧是,人脸框过大或过小都会影响贴回效果,如果检测框严重偏离,可以查看检测可视化结果,而不是直接怀疑生成器。

注意:避坑有一个基本原则——改一个变量,跑一次对照,不要同时调多个参数。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。我所有的批量脚本都会在文件名里自动拼上参数,这是最想推荐的一个习惯。老照片修复最贵的成本不是算力,是反复验证效果所花的时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询