☰
GFPGAN老照片修复Python实战:环境配置、批量处理与参数调优
2026/10/11 13:08:57 网站建设 项目流程

简介:这是一套基于GFPGAN算法的老照片修复Python设计源码,面向图像处理、深度学习开发者和AI影像修复爱好者。项目以泛用性人脸先验引导恢复网络为核心,通过大规模人脸数据学习先验知识,在修复老照片时能更好地保留人脸特征的清晰度与真实感,有效改善照片模糊、细节缺失及整体画质不足等问题。资源包共51个文件,压缩后约6.09MB,其中21个Python脚本构成核心实现,覆盖训练、推理、模型定义与数据集加载;7个YAML配置文件负责管理训练参数;6个PNG与2个JPG图片可用作输入样例或效果对比;5个Markdown与TXT文档提供环境安装、使用说明和开发指南;另有预训练权重、数据库文件及JSON、CFG等辅助配置。目前已有491人浏览学习。借助该源码包,读者可完整理解GFPGAN v1模型细节并快速复现修复流程,配合现成代码、配置与文档在训练或推理层面进行二次开发,显著降低图像修复算法的学习与落地门槛。

1. 把 GFPGAN 用在老照片修复上:这份 Python 源码到底提不提升效率

老照片修复不是新需求,但真正落到代码上时,多数人卡在“选哪套算法、环境能不能跑起来、修完是不是比原图更奇怪”这三个坎上。GFPGAN 这套基于生成对抗网络的人脸修复算法,专门解决旧照片里人脸模糊、五官变形、皮肤纹路丢失的问题,它不需要自己训练模型,直接加载预训练权重就能对单张图做增强。我这里拆的这份 Python 老照片修复设计源码,核心就是围绕 GFPGAN 把推理流程、参数调节、批量处理串成一套可用代码,适合想把“旧照片变清晰”这个动作做成稳定工具的 Python 从业者,也适合准备拿图像处理做课程设计的同学直接改写出完整项目。它的价值不在算法多新,而在能直接在本地复现,跑通之后改几个参数就能适应不同旧照片的损伤程度。

我最初拿到这类源码时习惯先问三个问题:依赖好不好装、模型权重从哪来、修复之后会不会把脸“脑补”成另一个人。GFPGAN 的答案是依赖可控、权重托管在 GitHub Release、保真度可以通过 fidelity_weight 调和。下面这几章,我会按“环境准备 → 核心推理 → 批量工程化 → 避坑 → 验证”的顺序,把每一步的命令、参数、翻车现场都写明白。

2. 环境准备:GFPGAN 的 Python 依赖版本是老照片修复里最容易翻车的环节

2.1 为什么 GFPGAN 对环境如此敏感:PyTorch、basicsr、facexlib 的三角关系

GFPGAN 不是一个孤立模型,它跑起来至少依赖三样东西:PyTorch 负责张量计算和 GPU 调度;basicsr 提供图像超分和修复的基础工具类;facexlib 负责检测人脸和对齐关键点。三个库之间存在隐式的版本配合,比如新版本的 basicsr 改了导出接口名,老代码调load_file_from_url的逻辑就可能断。PyTorch 更是如此,1.10 和 2.x 的torch.cuda行为差异不大,但某些本地编译的扩展容易因为 CUDA 版本不一致而直接报“undefined symbol”。

常见做法是先用 conda 建一个干净的 Python 3.8 环境,然后单独装各依赖。为什么选 3.8 而不是 3.10?因为 facexlib 和 basicsr 里某些模块用了过时的distutils调用,在更高版本 Python 里会直接报ModuleNotFoundError。这不算 GFPGAN 的锅,但确实是最常见的入门阻力。

2.2 从零把环境装到能跑:一份可以直接执行的 bash 步骤

拿到这套 Python 老照片修复源码后,第一步不是读代码,而是把环境对齐。我这里给出一份当前最稳妥的安装顺序,逐条复制即可。

# 创建独立虚拟环境,避免污染系统 Python conda create -n gfpgan python=3.8 conda activate gfpgan # 安装 PyTorch,这里以 CUDA 11.8 为例 pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu118 # 安装 GFPGAN 核心库和两个配套库 pip install gfpgan facexlib basicsr==1.4.2 # 安装图像处理基础库 pip install opencv-python pillow numpy

逻辑说明:conda create -n gfpgan python=3.8的作用是圈出一个隔离环境,让后续安装的 OpenCV、PyTorch 不会和系统里的其他语音识别、爬虫项目冲突。basicsr==1.4.2锁定版本很关键,我后来试过 basicsr 1.4.3 以上的版本,发现restorer.enhance接口的行为没有变化,但 GFPGAN 自带的一些推理脚本会因为 basicsr 内部mim检查不通过而提前退出。--index-url参数只针对 PyTorch 官方编译好的轮子,后续普通 pip 包不用加这个源。

2.3 模型权重才是真正的“源码本体”

很多人拿到 GFPGAN 相关源码包时误以为里面已经包含了训练好的模型,实际上 GitHub 上的设计源码通常只包含推理脚本、网络结构和配置文件,预训练权重动辄几百 MB,不会直接塞进 zip。我习惯把权重文件单独下载,然后按下面的目录结构放好。

# 创建预训练模型目录 mkdir -p experiments/pretrained_models # 下载 GFPGANv1.4 权重重 wget https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth -P experiments/pretrained_models/ # 下载用于人脸检测的辅助权重 wget https://github.com/TencentARC/GFPGAN/releases/download/v0.2.0/alignment_WFLW_4HG.pth -P experiments/pretrained_models/

参数说明:GFPGANv1.4.pth是人脸增强的主模型,所有修复结果都由它生成;alignment_WFLW_4HG.pth是 4 个 hourglass 组成的人脸对齐网络,负责找到眼睛、鼻子、嘴角的关键点,如果缺失,GFPGAN 会退回到简单的中心裁剪,模糊照片中人脸角度稍偏,修复效果就会明显变差。wget 下载慢的时候,我一般会从浏览器复制链接用 IDM 下载,然后手动放进experiments/pretrained_models,效果完全一样。

3. 核心推理链路:一张破损人脸照片如何变成高清图

3.1 最小修复脚本:GFPGANer 的几个参数决定了修复结果的天花板

环境就绪后,直接看大众最关心的推理代码。GFPGAN 的官方封装类叫GFPGANer,使用它不需要去推断网络结构内部的 Latent 走向,只需要理解几个对外参数即可。

import cv2 from gfpgan import GFPGANer # 初始化修复器 restorer = GFPGANer( model_path='experiments/pretrained_models/GFPGANv1.4.pth', upscale=2, # 放大倍数,旧照片一般 2 倍 arch='clean', # 网络结构选 clean,比 original 更稳 channel_multiplier=2, # 通道倍数与权重对应 bg_upsampler=None # 暂不修复背景,只为避免串色 ) # 读取图片 img = cv2.imread('old_photo.jpg') if img is None: raise FileNotFoundError('图片路径不对,请检查输入文件') # 执行增强 result, face_image, face_detail = restorer.enhance( img, has_aligned=False, # 输入图未经过人脸对齐 only_center_face=False, # 处理图中所有人脸 paste_back=True # 把修复后的脸贴回原位置 ) cv2.imwrite('restored.jpg', result)

逻辑说明:GFPGANer内部实际上做了一次“人脸检测 → 关键点对齐 → GFPGAN 推理 → 回贴”的完整流程。only_center_face=False对老照片特别重要,很多合影里除了中间主角还有侧脸亲友,如果不关掉这个开关,侧脸可能会被直接丢弃。paste_back=True让修复后的脸覆盖到原始背景上,背景受再严重的噪点影响也不会被人脸模型重画一遍。

upscale=2是旧照片最常用的档位。老照片普遍只有 300~800 像素边长,放大 2 倍后既能看清楚五官纹路,又不至于像 4 倍那样让背景的马赛克特征被过度放大。如果原始照片已经做过一次现代数码扫描,我一般会先用upscale=1只做人脸修复,避免出现“塑料感”的皮肤高光。

3.2 has_aligned 与 bg_upsampler:两个影响修复风格的隐形开关

has_aligned这个参数,官方注释写的是“输入图是否已经对齐”,但真正含义是“是否需要重新检测人脸”。如果输入是已经裁剪成正方形且人脸居中的图片,可以把它设为True,GFPGAN 会跳过人脸检测直接增强,节省约 30% 的推理时间。老照片扫描件几乎没有严格对齐的,所以日常处理我都是False,不要为了省时间跳过检测。

bg_upsampler是背景增强器,常见做法是传入一个RealESRGANer实例,让背景和人脸同步变清晰。我在第一次跑的时候为了省显存把它设为None,结果人脸清晰得毛孔可见,背景还是原样模糊,照片整体视觉很“分裂”。如果要用背景增强,需要注意前置安装 realesrgan。

from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2) bg_upsampler = RealESRGANer( scale=2, model_path='experiments/pretrained_models/RealESRGAN_x2plus.pth', model=model, tile=400, tile_pad=10, pre_pad=0 )

参数说明:tile=400表示把输入图切成 400x400 的小块分别放大,最后拼接回去,这样做的直接收益是单张 2000x3000 的扫描老照片也不会撑爆显存。tile_pad=10是相邻 tile 之间的重叠像素,防止拼接处出现亮度跳变。如果你手里的图片噪声很大,建议先用cv2.fastNlMeansDenoisingColored做一次轻度去噪,再交给 GFPGAN,因为增强模型对高频噪声会被当成细节保留。

4. 批量修复工程化:把源码包改成能直接吃一整个目录的 Python 工具

4.1 目录遍历与文件命名:比你想象中更容易踩的坑

当客户手里有 200 张民国时期合影时,一张张调用enhance并不现实。源码包通常给的是单图推理入口,我拿到后会先加上目录遍历逻辑。这一步有两个容易翻车的细节:一是glob匹配到的文件顺序不稳定,需要排序保证输出和输入的一致性;二是 Windows 下路径中的反斜杠在拼权重路径时必须转义或改用 Path 对象。

from pathlib import Path import cv2 from gfpgan import GFPGANer def init_restorer(model_path): return GFPGANer( model_path=model_path, upscale=2, arch='clean', channel_multiplier=2, bg_upsampler=None ) def batch_fix(input_dir, output_dir, model_path): input_dir = Path(input_dir) output_dir = Path(output_dir) output_dir.mkdir(parents=True, exist_ok=True) # 只处理常见图片后缀,且排序保证稳定 files = sorted([p for p in input_dir.iterdir() if p.suffix.lower() in {'.jpg', '.jpeg', '.png'}]) if not files: print('输入目录里没有任何 jpg/png 文件') return restorer = init_restorer(model_path) for idx, img_path in enumerate(files): try: img = cv2.imread(str(img_path)) if img is None: print(f'{img_path.name} 读取失败,可能是文件损坏') continue restored, _, _ = restorer.enhance( img, has_aligned=False, only_center_face=False, paste_back=True ) out_name = f'{idx:04d}_{img_path.stem}_restored.jpg' cv2.imwrite(str(output_dir / out_name), restored) print(f'完成 {idx + 1}/{len(files)}:{img_path.name}') except Exception as err: print(f'{img_path.name} 修复失败,原因:{err}') if __name__ == '__main__': batch_fix('old_photos', 'restored_photos', 'experiments/pretrained_models/GFPGANv1.4.pth')

逻辑说明:f'{idx:04d}_{img_path.stem}_restored.jpg'这种命名方式可以保证输出文件顺序与输入一致,同时保留原始文件名便于追溯。try...except非常重要,老照片里经常混入空白页、纸张背面、已经碎成多格的图片,cv2 读取失败时直接跳过比让整个批次中断更实用。输出目录自动创建用mkdir(parents=True, exist_ok=True),避免手动去建目录。

4.2 加一个 argparse 命令行入口,让非 Python 用户也能操作

源码包如果只提供函数接口,同事用起来会痛苦。我一般会补一个main.py,把批量修复的命令行参数暴露出去,这样操作者不用改代码就能换输入输出目录和放大倍数。

import argparse from pathlib import Path from batch_restore import batch_fix def main(): parser = argparse.ArgumentParser(description='基于 GFPGAN 的老照片批量修复工具') parser.add_argument('--input', type=str, required=True, help='输入照片目录') parser.add_argument('--output', type=str, default='restored', help='输出目录') parser.add_argument('--model', type=str, default='experiments/pretrained_models/GFPGANv1.4.pth') parser.add_argument('--upscale', type=int, choices=[1, 2, 4], default=2, help='放大倍数') parser.add_argument('--only_center', action='store_true', help='只修复中心人脸') args = parser.parse_args() batch_fix( input_dir=Path(args.input), output_dir=Path(args.output), model_path=args.model, upscale=args.upscale, only_center_face=args.only_center ) if __name__ == '__main__': main()

参数说明:--upscale choices=[1, 2, 4]直接从入口限制了乱填参数的可能。--only_center用store_true,不传参数时是False,适合合影;传了则只修中间人脸,适合单人脸生活照。我建议在实际项目中把fidelity_weight也暴露成命令行参数,因为不同老照片对“还原”和“创新”的看重程度不一样,后面我会细说这个参数。

5. 避坑与常见问题:跑老照片修复时最容易翻车的五个现场

5.1 显存不足(OOM),程序直接崩掉

现象:遇到 3000x4000 像素的高清扫描件,脚本报CUDA out of memory,而小图正常。

原因:GFPGAN 默认会把整张图提交到显存,扫描件本身分辨率高,再加上背景增强器tile=0时甚至会对整图做超分,显存占用直接翻好几倍。

解决:先降低upscale=1,只修人脸五官;再把背景增强器的tile设为 256 或 400,让超分分块执行。还有一个土办法是先把图缩小到最长边 1600 像素再修复,完成后单独对脸部区域做一次放大,效果肉眼几乎无差别。

5.2 权重文件下载卡在 99%,或者连接超时

现象:wget下载GFPGANv1.4.pth时网络中断,重试断点续传无效;部分资源托管在国外服务器上,下载速度极慢。

原因:权重文件体积大,散落在 Release 资产里,国内直接访问不稳定,源码包里通常不会内置大文件。

解决:不要反复wget重试,直接用浏览器下载工具(IDM 或迅雷)下载,完成后校验文件大小是否与 Release 页面标注的字节数一致。我见过最隐蔽的问题是下载得到一个 html 错误页,但文件后缀是.pth,加载时直接报pth file does not exist。建议下载完先看一眼文件头,用 Python 跑一下torch.load确认不是文本文件。

5.3 人脸修复后背景糊成一片,和脸完全不协调

现象:输出图中人脸皮肤纹理清晰,头发边缘锐利,背景却保留了原始噪点,整体效果像“抠图贴上去”。

原因:bg_upsampler=None时只启用了人脸增强分支,背景通道原样输出,而人脸又经过了上采样,自然会产生分辨率差。

解决:按第 3 章的方式配置RealESRGANer作为bg_upsampler。如果机器显存实在不够,至少把背景做一次轻度的resize到与脸同样尺寸,再用cv2.addWeighted把背景锐化一点应急。注意不要用 GFPGAN 的修复结果直接贴回原图后再次整体 resize,那会把已经修复好的脸重新变糊。

5.4 黑白老照片人脸被“脑补”出奇怪彩色

现象:一张百年黑白照片修复后,皮肤出现了粉红色斑块,嘴唇被涂成鲜艳的红色,衣服颜色也不自然。

原因:GFPGAN 在训练阶段见过大量彩色人脸,输入单通道灰度图被复制成三通道后,模型会按照概率分布“猜测”肤色,但老照片的灰度分布和现代彩色摄影的转换结果并不一致,导致猜测偏差。

解决:修复前先不对图片做任何 RGB 转换,修复后再把输出转回灰度。常见做法是修复后保留灰度背景,只把脸部区域替换成修复结果的灰度版本,并降低fidelity_weight,让模型不要过度发挥颜色。如果需要保留黑白质感,可以在enhance返回的face_image上使用cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)后,再用paste_back手动贴回。

5.5 CPU 机器跑一张图要十几分钟,等得没法干活

现象:没有独立显卡的笔记本上,单张 500 像素图推理耗时 5 分钟以上,批量修复几乎不可用。

原因:GFPGAN 的生成器是 StyleGAN2 尺寸规模,卷积计算量非常大,CPU 推理时 PyTorch 默认只利用单线程,速度惨不忍睹。

解决:先检查是否真的没可用的 GPU,torch.cuda.is_available()输出 False 时,给模型推理加上torch.set_num_threads(8)提升多核利用率。同时把facexlib的人脸检测器device参数固定成 CPU,否则每次检测都会在 CPU/GPU 之间反复传数据。最有效的方案是使用低分辨率粗跑,只对脸部裁剪区域做一次高倍增强,把全图增强和背景增强全部关掉。

6. 进阶玩法:用效果对比图和量化指标给修复结果验尸,而不是只靠肉眼

6.1 用 PSNR 和 SSIM 判断修复是不是真的“变好”

老照片修复的主观性太强,肉眼看着清晰了,但可能五官已被暴力重绘。源码包里通常没有验证脚本,我习惯自己加一段对比逻辑,分别计算修复前和修复后的基本质量指标。

import cv2 import numpy as np def psnr(original, restored): mse = np.mean((original.astype(np.float32) - restored.astype(np.float32)) ** 2) if mse == 0: return 99.0 max_pixel = 255.0 return 10 * np.log10((max_pixel ** 2) / mse) original = cv2.imread('old_photo.jpg') restored = cv2.imread('restored.jpg') # 保证尺寸一致 if original.shape[:2] != restored.shape[:2]: restored = cv2.resize(restored, (original.shape[1], original.shape[0])) print('PSNR:', psnr(original, restored))

逻辑说明:PSNR 只看像素差异,数值越高代表和原图越接近。但对老照片来说,PSNR 并不是越高越好,因为修复必须改变像素才能带来更清晰的观感。我一般用它来排除“过度创新”——如果一张噪点极重的照片修复后 PSNR 高达 45,说明模型几乎没做任何改动,相当于白跑。真正的参考价值是把同一张图用不同参数跑两遍,对比 PSNR 的差值来观察参数敏感度。

SSIM 更贴近人眼感知,计算结构相似度。这个指标可以用 sklearn 的structural_similarity一行算出来,但它对亮度偏移敏感,修复结果整体提亮后 SSIM 会偏低。因此我通常输出一个对比表:原图、修复图、修复并与原图对齐后的 PSNR、SSIM,以及fidelity_weight的取值。当fidelity_weight从 1.0 降到 0.5 时,PSNR 会明显下降,但细节纹理往往更自然,这个数值恰好在“忠于原图”和“复原清晰”之间提供了决断依据。

6.2 局部手工校正:眼睛和嘴角区域单独再修一次

GFPGAN 对整张人脸的生成结果通常比较稳定,但老照片常在眼部有划痕、在嘴角有反光。我的习惯是先跑一次全脸修复,然后用人脸关键点把眼睛区域框出来,再用小尺寸的 crop 二次调用enhance,最后通过 alpha 混合只替换局部。这样能避免第二次增强整张脸时,出现左右眼不对称的“恐怖谷”效果。

face_crop = img[y-20:y+h+20, x-20:x+w+20] enhanced_face, _, _ = restorer.enhance(face_crop, has_aligned=False) # 将增强后的局部放回原图坐标 img[y-20:y+h+20, x-20:x+w+20] = cv2.resize(enhanced_face, (w+40, h+40))

参数说明:这里的x, y, w, h来自人脸检测框,20是向外扩展的像素余量,作用是让修复后的脸部边缘与周围肤色的过度更自然。局部二次修复的upscale建议和全局一致,不要在这里突然改成 4 倍,否则局部精细度会超越周围区域,视觉上更假。

6.3 把修复参数固化成 JSON 配置,方便日后复现

源码包里如果每次调参都改 Python 代码,后期客户回访时根本记不住某张照片用了什么参数。我后来把所有修复参数写进一个config.json,每张照片修复前自动读取,修复后把参数连同结果一起写进日志。这样哪张图效果客户满意,我可以直接复现同一套参数跑同批次其他照片。

{ "upscale": 2, "fidelity_weight": 0.7, "bg_upsampler": "realesrgan", "tile": 400, "only_center_face": false, "face_detector": "retinaface" }

这段 JSON 里fidelity_weight是很多人忽略的参数,它在 GFPGAN 源码中控制“保持原图特征”的权重,数值越接近 1,输出越接近输入;接近 0 时,模型会把脸往“标准好看”的方向拉。老照片通常选择 0.5 到 0.7,既能补细节,又不会把老人修复成年轻明星脸。从那以后,我每次批量修复都会强制走一遍“配置固化 → 批量跑 → 抽 3 张给客户确认参数”的流程,不再靠碰运气式调参,这让我少走了很多冤枉路。希望这份基于 GFPGAN 的 Python 老照片修复源码笔记,能帮你把修复流程变得更可信、更可复制。

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

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

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

立即咨询