☰
GFPGAN老照片修复实战:从原理到Python批量处理
2026/10/11 13:27:50 网站建设 项目流程

简介:基于GFPGAN算法的Python老照片修复设计源码,面向图像处理开发者与有老照片修复需求的个人用户,解决老照片模糊、人脸细节丢失、画质退化等问题;GFPGAN作为泛用性人脸先验引导的修复网络,结合生成对抗网络与人脸先验知识,在修复时保持人脸特征清晰真实,配合OpenCV、NumPy等库可形成完整修复流程。包内共51个文件,压缩包约6.09MB,以Python脚本、YAML配置、PNG/JPG样例图、Markdown文档为主。Python脚本覆盖推理、训练与工具函数,YAML配置运行参数,图片提供修复前后对比,MD文档写明安装与使用方式,另有预训练权重与测试数据,结构清晰便于二次开发。目前已有491人学习浏览该项目,可直接运行体验GFPGAN修复效果,也能作为自建照片修复工具或相关实验的代码参考与文档支撑。

1. 老照片修复,为什么偏偏是GFPGAN

拿一张十年前拍糊了的脸,丢给普通锐化工具,出来的效果不是“更清晰”,而是“更脏”——边缘出现白边,皮肤变成塑料,噪点被放大成麻点。老照片修复的真正难点不是提分辨率,而是要在放大两倍、四倍的同时,把丢失的面部结构“补”回来,并且补得自然。GFPGAN(Generative Facial Prior GAN)做的正是这件事:它不是靠插值算法去猜像素,而是用预训练的人脸先验知识去“重绘”一张脸,所以修复结果在观感上会明显好于传统超分方案。

这篇内容写给两类人:一是手头有一批老照片、想本地批量修复的从业者,二是想做图像修复产品原型、需要选型和落地的开发者。我会从GFPGAN的选型原理讲起,给出能直接跑通的完整Python代码,再把参数调试、批量处理、资源占用这些坑逐个拆开。整个过程不需要你有深度学习的理论底子,但需要你习惯用命令行,并且愿意折腾一两次环境。

2. GFPGAN是什么:从“超分辨率”到“人脸先验重绘”

2.1 为什么传统超分模型在老照片上翻车

老照片修复最常被拿来对比的模型是Real-ESRGAN,它做的是通用的图像超分:把低分辨率输入映射到高分辨率输出,训练时用大量的退化模拟(模糊、噪声、压缩痕迹)。问题是,老照片的退化不是单一的,扫描噪点、化学药水污渍、冲印时的对焦不准,这些退化叠加在一起,Real-ESRGAN这类通用模型会倾向于“顺着纹理猜”,结果是把噪点也当成纹理一起放大了。

GFPGAN走了另一条路。它引入了一个关键概念:人脸先验(Facial Prior)。模型在训练时不仅学“低清到高清”的映射,还学“一张正常的人脸应该长什么样”。具体来说,它用StyleGAN的预训练生成器作为先验知识库,修复时先把输入图像编码到StyleGAN的隐空间(latent space),再在这个空间里找到最接近输入的人脸向量,最后用生成器重建出高清人脸。这就像警察画嫌犯肖像:不是把模糊照片硬放大,而是根据轮廓特征“画”出一张符合人脸规律的高清画像。

这个设计的直接好处是:对于人脸区域,修复结果不会出现结构性畸形(比如眼睛错位、嘴巴歪掉);坏处也很明显——如果你要修复的是风景照、文字文档或者没有人脸的合影局外区域,GFPGAN的人脸先验反而可能帮倒忙。所以它在架构上做了区域控制:face_enhancement模块只作用于检测到的人脸框,背景部分交给另一个轻量级超分网络处理。

2.2 GFPGAN v1.4与v1.3:该选哪个权重

GFPGAN官方仓库维护了两个常用的发布版本权重,v1.3和v1.4,我建议直接选v1.4。

v1.3是早期版本,对应的训练数据和生成器是StyleGAN2,修复后的肤色偏暖、皮肤纹理偏“油画感”,对极端模糊的旧照片反而有奇效,因为它的生成器先验更强。v1.4换用了更新的人脸生成器,并且加入了更多真实退化数据的训练,整体效果更自然,皮肤毛孔和头发丝的处理接近真实照片,但在输入人脸特别模糊时,它“脑补”的幅度会收着一点,不会像v1.3那样大胆。

实操建议:如果你的照片是民国时期那种严重颗粒化、面部结构都快看不清的,先试v1.3;如果是九十年代家庭合影、有轻微磨损但五官轮廓尚存,直接上v1.4。我一般会在批量脚本里同时下载两个权重,遇到效果不满意的单张照片再切换对比,这个成本只是多占1GB磁盘空间。

2.3 为什么当前推荐用Python调用而非ComfyUI节点

GFPGAN在图像生成社区里经常被封装成ComfyUI节点或WebUI插件,点几下鼠标就能出图。但我依然推荐用Python直接调官方repo,理由有两条:第一,插件节点为了通用性,把很多参数固化在了界面里,比如只暴露denoise_strength一个滑杆,而实际影响修复风格的还有bg_upsampler、padding_ratio等参数,用代码才能完整控制;第二,插件节点属于第三方维护,GFPGAN官方更新权重后,节点未必同步更新,直接调官方repo能第一时间拿到新模型和bug修复。

3. 本地跑通最小修复流程:环境搭建与首个推理脚本

3.1 环境搭建的版本选择与CUDA踩坑

GFPGAN依赖PyTorch和基础的图像处理库。这里有一个常见的翻车点:PyTorch的CUDA版本必须和显卡驱动版本匹配,否则推理时会报出“CUDA error: no kernel image is available for execution on the device”这类玄学错误。我的建议是,先确定你的显卡驱动支持的最高CUDA版本,再去PyTorch官网选对应的安装命令。如果只是做推理,不追求训练,装CPU版也不是不行——一张512×512的图,CPU推理耗时在30秒到1分钟,批量处理十几张还能忍,上百张就相当折磨了。

下面是完整的安装流程,假设你用conda管理环境:

# 创建独立环境,Python版本锁定3.10 conda create -n gfpgan_env python=3.10 -y conda activate gfpgan_env # 安装PyTorch,这里以CUDA 11.8为例;如果你是N卡且驱动支持更高版本,可以换成对应的cu121 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 克隆GFPGAN官方仓库并安装依赖 git clone https://github.com/TencentARC/GFPGAN.git cd GFPGAN pip install basicsr facexlib pip install -r requirements.txt # 安装训练依赖(仅推理可跳过) python setup.py develop

这段命令里需要注意两点:basicsr和facexlib是GFPGAN的两个核心依赖,前者提供了超分网络的基础模块,后者负责人脸检测和对齐;python setup.py develop会把当前目录注册为Python包,方便后面直接import gfpgan。如果跳过这一步,在别的目录下写脚本会报ModuleNotFoundError。

3.2 跑通第一张照片修复:最小推理脚本

装好环境后,先别急着上批量脚本。我建议先用官方提供的inference_gfpgan.py跑单张图,确认整个链路是通的,再写自己的封装。把一张照片放到inputs目录下,执行:

# 注意先把权重文件下载到 experiments/pretrained_models/ 下 python inference_gfpgan.py --input inputs/old_photo.jpg --output results/ --version v1.4 --scale 2

参数说明:--scale 2表示把图片放大两倍,老照片一般建议2倍,放大4倍容易出现面部虚焦感;--version v1.4决定加载哪个权重文件,仓库里的脚本会自动拼出GFPGANv1.4.pth这样的文件名。如果出现“pretrained model not found”错误,说明权重文件位置不对,需要放在experiments/pretrained_models/目录下。

运行结束后,在results目录下会得到对比图和单独的修复图。如果输入图里有多张人脸,GFPGAN会逐一检测并修复,然后在对比图里用红框标出每个检测到的人脸区域。这其实也是一次隐式的“人脸检测成功率”测试——如果某张侧脸没被框出来,说明它没被修复,后面批量时要回去找它。

3.3 封装成自定义函数:输入路径、输出路径与参数分离

官方脚本适合验证,但不适合批量。我的做法是封装一个函数,把输入路径、输出路径、scale、denoise_strength都作为参数暴露出来,这样既能单张调用,也能在循环里批量处理:

import cv2 import torch from gfpgan import GFPGANer def restore_photo(input_path, output_path, version='v1.4', scale=2, denoise=0.75): # 初始化修复器,bg_upsampler设置为None表示背景不做额外超分 bg_upsampler = None # 创建GFPGAN实例 restorer = GFPGANer( model_path=f'experiments/pretrained_models/GFPGAN{version}.pth', upscale=scale, arch='clean', channel_multiplier=2, bg_upsampler=bg_upsampler ) # 读取图片,GFPGAN内部会自动处理BGR/RGB转换 img = cv2.imread(input_path, cv2.IMREAD_COLOR) if img is None: raise ValueError(f'无法读取图片: {input_path}') # 执行修复,返回值分别是修复后的图、修复后的人脸框、修复前的人脸框 _, _, _ = restorer.enhance( img, has_aligned=False, only_center_face=False, paste_back=True, weight=denoise ) # 保存结果 cv2.imwrite(output_path, img) print(f'[OK] {input_path} -> {output_path}')

这段代码里有几个参数值得重点说明。arch='clean'是固定的,对应v1.4权重;如果换用v1.3权重,arch要改回original,channel_multiplier也要从2改成1,两个版本是两套结构定义。weight=denoise是整套修复里最值得调的参数,它控制生成器先验与输入图像的融合比例——数值越大,修复结果越依赖“人脸先验”,对于严重模糊的照片效果更好,但可能丢失原图的真实细节;数值越小,越保留原图信息,适合本身清晰度尚可的照片。

has_aligned=False表示输入图是自然图像而不是已经对齐过的人脸,GFPGAN会先做一次人脸检测。only_center_face=False表示修复所有检测到的人脸,而不是只修画面中心的那张。paste_back=True表示把修复后的人脸贴回原图,而不是只输出裁剪后的人脸区域。

4. 修复质量的四个关键参数与效果验证方法

4.1 denoise_strength:先验与真实感的博弈

denoise_strength是GFPGAN里对结果影响最大的参数,没有之一。它的本质是生成器重建时的“去噪权重”——具体到推理脚本里,这个值会作为StyleGAN生成器输入latent code的截断阈值(truncation psi)和融合权重。我自己的经验区间是0.5到0.9:

  • 照片是那种带明显胶片颗粒的旧图,想要数字化之后的干净质感,用0.8以上;
  • 照片本身是九十年代彩色照片,失真不严重,只是分辨率低,用0.5到0.6,保留更多原始纹理;
  • 黑白合影照,人物多、每张脸占的面积小,用0.75作为默认值,既保证小脸区域能被“脑补”完整,又不至于让肤色变化太突兀。

调参时不要全凭肉眼感受,有一个能量化的判断方法:修复输出和原图做差分,计算差分图的峰值信噪比(PSNR)。如果PSNR高于28dB,说明修复结果和原图差异很小,这时候denoise_strength可以再加一点;如果低于20dB,说明已经改得面目全非了,需要往回降。

4.2 scale的选择:2倍与4倍不是越高越好

很多人拿到老照片第一反应是放大4倍,实际上这是最常见的翻车点。老照片的原始分辨率往往只有几百像素,人脸区域更是只有几十像素见方。放大4倍后,GFPGAN需要在这么小的区域里凭空生成细节,结果就是五官位置正确,但脸型被“风格化”——变得过于光滑,像CG渲染出来的人物。

我的经验是:扫描件原图长边超过1200像素的,放心用scale=2;长边只有500到800像素的老照片扫描件,先做一次scale=4,但修复完再用cv2.resize缩回2倍尺寸,利用多出来的像素冗余做降噪,效果通常比直接2倍更干净。这是我在处理大量馆藏老照片时学会的办法,本质上是用超分模型当降噪器用。

4.3 批量修复的目录设计与断点续跑

实际工作流里,你几乎不可能只是一张张手动跑。批量处理时要设计好目录结构,我的习惯是:

restore_project/ ├── inputs/ │ ├── album_1/ │ └── album_2/ ├── outputs/ │ ├── album_1/ │ └── album_2/ ├── failed.txt └── batch_restore.py

批量脚本里必须写失败日志和断点续跑逻辑。原因很简单:几百张图里总有那么几张因为人脸检测失败或图片本身损坏导致程序抛异常,如果不做容错,跑了一小时直接整体中断,前面的工作全白费。我的做法是把单张修复包进try/except里,失败时把文件路径写进failed.txt,用continue跳过;同时修完的文件会写入一个done.txt,下次运行时先读取done.txt,跳过已经处理过的图片。

4.4 验证修复质量:不能只看“变清晰了”

修复完成后的验证,新手看“清晰度”,熟手看三个细节:眼睛瞳孔是否出现不自然的对称高光、牙齿是否糊成一片白色、耳廓边缘是否有锯齿状伪影。GFPGAN生成的伪影非常典型——它会在瞳孔里加一个理想的圆形反光点,这在真实老照片里几乎不可能存在。如果你发现所有修复的人脸瞳孔都带一个位置几乎相同的高光点,说明denoise_strength开太高了。

另一个需要警惕的问题是“人脸克隆”:当一张照片里有多个人脸且都比较小时,GFPGAN偶尔会把所有人脸都修复成同一张脸的变体——因为它们编码到的latent code太接近。这时候需要把only_center_face设为True只修主体人脸,其他侧脸放弃,或者回到原图重新调整对齐后再修。

5. GFPGAN老照片修复的常见问题与排查记录

5.1 修复后人脸出现“塑料肌”,皮肤毫无纹理

这个现象在彩色照片上尤其明显,修复结果像游戏CG人物,皮肤光滑到反光。

原因是denoise_strength设置过高,生成器先验完全压过了输入图像的真实纹理。GFPGAN的生成器从StyleGAN继承了一个特点:它生成的人脸皮肤是“干净”的,没有真实皮肤那种微观肌理。解决方法是降低weight参数到0.5以下,同时把bg_upsampler换成RealESRGAN,让背景和皮肤区域的纹理由超分网络贡献,而不是由人脸生成器完全接管。

5.2 检测不到人脸,整张图原样输出

输入一张侧面45度角的老照片,修复后结果和原图几乎一样,没有任何变化。

原因是GFPGAN默认使用retinaface_resnet50做人脸检测,它对大角度侧脸的召回率不够,检测不到人脸框,修复流程直接跳过。解决方法是先对图像做一次人脸检测预处理,如果检测不到,就把图像旋转15到30度再试;或者换用facexlib里的其他检测模型——不过我实际用下来,最稳妥的还是旋转后重试,因为换模型意味着要重新下载权重并处理对齐逻辑,成本更高。

5.3 修复后整张图的颜色偏灰,饱和度下降

输出的人脸修复得很自然,但整张图看起来像蒙了一层灰,色彩发闷。

这是因为老照片原图本身偏黄偏暗,GFPGAN对人脸区域重绘时的色彩分布以训练数据(现代数码照片)的统计均值为基准,修复后的人脸颜色自然、但和原图的复古色调不匹配。解决方法是修复后做一次色彩迁移:用OpenCV计算原图与修复图的亮度均值,将原图的色偏信息叠加回去。代码上就是cv2.addWeighted把原图和修复图按1:1混合,再提升饱和度。

5.4 黑白照片修复后,肤色像涂了一层灰紫色

老照片翻拍的黑白图像在修复后经常出现肤色蜡黄或灰紫的情况,这是因为原图已经丢失了色彩信息,GFPGAN的生成器却强行给它分配了一套“合理肤色”,然后又叠加了灰度图的亮度分布。

处理这类照片前需要先做一个预处理:把灰度图复制到三个通道,做一次自动白平衡,让亮度分布更均匀,再进GFPGAN。修复完成后如果想保持黑白质感,就手动cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)转回灰度。这也是我处理馆藏老照片时最常用的流程。

5.5 显存不足程序崩溃,或者OOM后残留进程占显存

处理多人合影时,每个人脸框都会单独送入生成器,如果一张照片检测出几十个人脸,显存占用会瞬间拉满,程序直接报torch.cuda.OutOfMemoryError。

解决思路有两个:一是手动限制每个人脸框的大小,在enhance前把检测框的最大边长缩到512像素以内,GFPGAN内部会自动缩放,但小框对显存的占用会指数下降;二是在批量脚本里量力而行,设置torch.cuda.empty_cache()在每张图处理后回收缓存。需要注意,OOM崩溃后显存不会自动释放,再次运行前要检查进程管理器里是否残留了Python进程,有就杀掉,否则下一轮还会继续崩溃。

6. 把修复流程固化成可交付脚本:进阶技巧与量化验证

6.1 一个更接近生产环境的修复脚本结构

我最终沉淀下来的脚本不是裸调GFPGAN,而是加了三个前置处理:自动白平衡、灰度图分支处理、人脸检测失败旋转重试。这三个处理加起来不到60行代码,但能把修复成功率从70%提升到90%以上。

def preprocess_for_gfpgan(img): """ 修复前预处理: 1. 灰度图转三通道并做白平衡 2. 长边超过1600像素时先缩小,防止小脸被忽略 """ if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # 白平衡:灰度世界假设 avg_b = np.mean(img[:, :, 0]) avg_g = np.mean(img[:, :, 1]) avg_r = np.mean(img[:, :, 2]) avg = (avg_b + avg_g + avg_r) / 3.0 img[:, :, 0] = np.clip(img[:, :, 0] * (avg / avg_b), 0, 255) img[:, :, 1] = np.clip(img[:, :, 1] * (avg / avg_g), 0, 255) img[:, :, 2] = np.clip(img[:, :, 2] * (avg / avg_r), 0, 255) h, w = img.shape[:2] max_side = max(h, w) if max_side > 1600: scale_factor = 1600 / max_side img = cv2.resize(img, (int(w * scale_factor), int(h * scale_factor)), interpolation=cv2.INTER_AREA) return img

这段预处理的核心逻辑是:灰度世界假设把偏色拉回中性,缩小长边是为了让检测模型不至于在过大的画幅里忽略小尺寸人脸,因为retinaface对大于一定尺寸的人脸框会直接跳过。

6.2 用差分图定位修复伪影的三步法

修复结果的验收不能只靠肉眼看,我习惯用差分图做量化定位:把修复图和原图缩到同一尺寸,做绝对差分,然后高斯模糊,再用固定阈值二值化。伪影会以高亮区域的形式显示出来。看差分图时重点观察三个位置:瞳孔、牙齿和发际线。如果高亮区域集中在这几个位置且成片出现,说明生成器先验主导了修复,这时要么降低denoise_strength,要么考虑改用v1.3权重。

def locate_artifacts(orig, restored, threshold=50): """ 定位修复伪影: 返回与restored同尺寸的二值图,白色区域即改动较大的位置 """ h, w = restored.shape[:2] orig_resized = cv2.resize(orig, (w, h), interpolation=cv2.INTER_AREA) diff = cv2.absdiff(orig_resized, restored) diff_gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY) diff_blur = cv2.GaussianBlur(diff_gray, (5, 5), 0) _, mask = cv2.threshold(diff_blur, threshold, 255, cv2.THRESH_BINARY) return mask

threshold设置成50时,基本只有结构性改动(眼睛、嘴、轮廓重绘)会被标出来;如果改成20,会把颜色调整、亮度变化也标出来,适合检查整体风格迁移程度。我会在批量跑完后,随机抽5%的结果图做这个检查,确认没有“千人一面”的问题。

6.3 关于GFPGAN的一个清醒认知:它不是万能的

用了大半年GFPGAN之后,我最大的一个教训是:它擅长修脸,不擅长修照片。如果一张老照片的核心问题是划痕、霉斑、折痕,GFPGAN几乎帮不上忙——它的目标函数里没有“去除划痕”这一项,划痕经过超分后反而变得更粗更明显。正确的工作流应该是:先用传统的去划痕工具(如OpenCV的inpaint或者Photoshop手修)处理物理损伤,再交给GFPGAN做人脸增强,最后统一做降噪和调色。

还有一个经常被忽略的点:GFPGAN不适合修复包含文字的旧书页或证件扫描件。文字经过生成器重建后会“被写字”——笔画变得圆润模糊,甚至缺笔少画。如果你要修的是带文字的档案照片,记得把paste_back设为False,只取人脸区域修复后再靠外部脚本贴回去,别让文字区域碰生成器。

最后分享一个我自己的习惯:处理任何一批老照片前,先拿三张不同年代、不同清晰度的样图跑一遍完整流程,分别检查人脸检测成功率、伪影情况和处理耗时。这三张样图的通过率基本能代表整批照片的下限——样图全部合格再开批量,能省掉大量返工时间。希望这些经验能帮你少踩几个坑,也期待看到你修出来的老照片比我修得更好。

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

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

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

立即咨询