
简介本资源为基于Python深度学习框架的GFPGAN图片修复算法实现源码面向具备一定Python编程与深度学习基础、希望深入研究图像修复与生成对抗网络的开发者可用于老旧照片修复、面部图像增强及数字取证等场景。压缩包共62个文件约6.22MB其中26个py文件承载算法核心实现与训练推理逻辑8个yml与2个yaml配置文件负责参数与实验设置5个md文档提供说明与常见问题另有png、jpg示例图、mdb数据集、pth权重及license等辅助文件。项目涵盖GFPGAN模型架构、StyleGAN2主干、ArcFace人脸识别模块、退化数据集构建与训练脚本等完整模块目录结构清晰便于按模型、数据、脚本分层研读。目前已有429人学习下载适合作为图像修复方向的实践参考与二次开发起点。1. 从一张糊掉的老照片说起GFPGAN 到底在修什么翻出十年前的家庭合影人脸只有指甲盖大小放大后五官糊成一团马赛克电商详情页里模特图被压缩过两轮皮肤纹理全丢只剩塑料感。这类问题不是简单锐化能救的——传统锐化只会把噪点一起放大而基于 Python 深度学习的 GFPGAN 图片修复算法走的是另一条路它不猜像素而是先认出「这是一张人脸」再用生成模型把丢失的高频细节重建回来。GFPGAN 全称是 Generative Facial Prior GAN核心思路是把预训练人脸生成模型里学到的「人脸长什么样」当作先验塞进修复网络里做引导。所以它特别擅长人脸区域对背景、文字、大面积纯色块的修复能力就一般。这套源码适合谁做老照片修复工具的产品团队、想跑通「检测修复」完整链路的算法工程师、以及需要把修复能力集成进自己 Python 服务的后端同学。它不是一个开箱即用的 App而是一套可以拆开、改参数、换模型的工程代码。2. GFPGAN 的修复链路拆解从退化图到清晰人脸要过几道手2.1 为什么不能直接拿超分模型硬套很多人第一反应是图片糊了上超分Super-Resolution不就行了我一开始也这么想结果翻车得很彻底。普通超分模型比如 ESRGAN 那类训练目标是「让整张图看起来更清晰」它对人脸没有概念。当输入人脸已经糊到五官错位超分模型会忠实地把错位的结构放大输出一张「清晰但不像人」的图——眼睛可能变成两个模糊的椭圆牙齿连成一片白。GFPGAN 的关键差异在于引入了人脸先验。它内部有一个预训练的 StyleGAN 人脸生成器这个生成器见过海量清晰人脸知道「正常人的眼睛该有什么结构」。修复时网络不是从零猜像素而是把退化图映射到生成器的隐空间附近再解码出清晰结果。这就像让一个画了十年人像的画师去补全一张模糊速写而不是让一个只会调对比度的修图师硬拉曲线。所以选型判断很简单修复目标以人脸为主用 GFPGAN修复目标是风景、建筑、文字换 Real-ESRGAN 或专用模型。两者也可以串联先 GFPGAN 修人脸再超分放大整体但顺序和权重需要调。2.2 三段式结构检测、对齐、修复各自在干什么把源码拆开看一次完整修复要过三个阶段。第一阶段是人脸检测。输入图先过一个人脸检测器常见做法是 RetinaFace 或 SCRFD输出每张人脸的边界框和五个关键点双眼、鼻尖、双嘴角。这一步决定了后面修复谁、修多大区域。检测漏了后面再强也白搭。第二阶段是对齐与裁剪。根据五个关键点做仿射变换把人脸旋转摆正、缩放到固定尺寸GFPGAN 常用 512×512。对齐做不好修复出来的人脸会歪或者比例失调。这一步的变换矩阵要保存下来修复完还要逆变换贴回原图。第三阶段才是修复网络本身。它接收对齐后的人脸图输出同尺寸的清晰人脸再按之前的逆矩阵贴回原图对应位置。背景区域通常保持原样或做轻微融合避免人脸和背景之间出现明显接缝。import cv2 import numpy as np # 假设已经拿到检测器输出的5个关键点顺序左眼、右眼、鼻尖、左嘴角、右嘴角 # 这是GFPGAN对齐阶段最核心的一步把歪脸摆正 def align_face(img, landmarks, output_size512): # 目标模板标准正脸的5点位置512尺寸下的经验值 dst np.array([ [192.98138, 239.94708], # 左眼 [318.90277, 240.19360], # 右眼 [256.63416, 314.01935], # 鼻尖 [201.26117, 371.41043], # 左嘴角 [313.08905, 371.15118], # 右嘴角 ], dtypenp.float32) src np.array(landmarks, dtypenp.float32) # 用相似变换估计仿射矩阵保留比例不拉伸 tform cv2.estimateAffinePartial2D(src, dst)[0] warped cv2.warpAffine(img, tform, (output_size, output_size), flagscv2.INTER_LINEAR, borderModecv2.BORDER_REPLICATE) return warped, tform # tform要留着做逆变换贴回这段代码的逻辑是把检测到的任意角度人脸通过相似变换映射到标准正脸模板上。estimateAffinePartial2D只允许旋转、平移、等比缩放不允许错切这样人脸不会被拉变形。borderMode用BORDER_REPLICATE是为了处理边缘像素避免黑边。参数output_size要和修复网络的输入尺寸一致GFPGAN 默认 512改成 256 会掉细节改成 1024 显存吃紧且收益有限。2.3 修复网络的输入输出与权重加载修复网络接收的是对齐后的 512×512 人脸张量数值范围归一化到 [-1, 1]。输出同样是 512×512 三通道图。源码里权重加载通常分两部分生成器主干权重和 StyleGAN 先验权重。加载时要注意通道顺序OpenCV 读进来是 BGRPyTorch 要 RGB这个转换漏了会导致修复结果颜色发蓝是新手最常见的翻车点之一。import torch from gfpgan_arch import GFPGANv1Clean # 以常见命名示意实际以你手上的源码为准 device torch.device(cuda if torch.cuda.is_available() else cpu) model GFPGANv1Clean( out_size512, num_style_feat512, channel_multiplier2, # 通道倍率2是标准1省显存但掉质量 decoder_load_pathNone, fix_decoderFalse, ) state_dict torch.load(gfpgan_weights.pth, map_locationcpu) model.load_state_dict(state_dict[params_ema], strictFalse) # strictFalse容忍先验部分缺失 model.eval().to(device) # 推理注意输入是RGB、[-1,1]、NCHW def restore_face(aligned_bgr): rgb cv2.cvtColor(aligned_bgr, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 tensor torch.from_numpy(rgb).permute(2, 0, 1).unsqueeze(0) tensor (tensor - 0.5) / 0.5 # 归一化到[-1,1] with torch.no_grad(): output model(tensor.to(device))[0] output output.squeeze(0).permute(1, 2, 0).cpu().numpy() output (output * 0.5 0.5).clip(0, 1) * 255 return cv2.cvtColor(output.astype(np.uint8), cv2.COLOR_RGB2BGR)channel_multiplier2是质量和显存的平衡点显存小于 6G 可以降到 1但皮肤纹理和发丝会明显变糊。strictFalse是因为先验权重和主干权重可能分开存放缺一部分也能跑但缺先验会退化成普通修复网络。归一化那两行是血泪经验忘了(x-0.5)/0.5输出会整体偏灰忘了转 RGB人脸发蓝。3. 在本地把 GFPGAN 跑起来环境、依赖与最小可复现脚本3.1 Python 环境与依赖版本怎么定这套源码对版本比较敏感尤其是 PyTorch 和 CUDA 的匹配。我一般会先确认显卡驱动支持的 CUDA 上限再选 PyTorch 版本最后装其余依赖。常见做法是用 conda 建独立环境避免和系统里的 Python 打架。conda create -n gfpgan python3.9 -y conda activate gfpgan # 以CUDA 11.8为例具体版本按你的驱动来 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy facexlib basicsrPython 3.9 是兼容性最稳的选择3.11 以上有些老依赖会编译失败。facexlib提供人脸检测和对齐的现成实现basicsr是很多修复项目的公共依赖。装完先跑一句python -c import torch; print(torch.cuda.is_available())返回 True 才算环境通了。返回 False 别急着往下走先查驱动和 CUDA 版本这是后面所有报错的根源。3.2 单张图片修复的完整调用流程把检测、对齐、修复、贴回串起来才是一次完整修复。下面是最小可复现脚本输入一张图输出修复后的图。import cv2 import numpy as np import torch from facexlib.detection import init_detection_model from facexlib.alignment import init_alignment_model detector init_detection_model(retinaface_resnet50, halfFalse, devicecuda) aligner init_alignment_model(awing_fan, devicecuda) def full_restore(img_path, model, device): img cv2.imread(img_path) h, w img.shape[:2] # 1. 检测人脸 with torch.no_grad(): bboxes detector.detect_faces(img, 0.5) # 0.5是置信度阈值 if bboxes is None or len(bboxes) 0: print(没检测到人脸直接返回原图) return img result img.copy() for box in bboxes: # box格式[x1,y1,x2,y2,score,landmark...] landmarks box[5:15].reshape(5, 2) # 2. 对齐 aligned, tform align_face(img, landmarks, 512) # 3. 修复 restored restore_face(aligned) # 4. 逆变换贴回原图 inv cv2.invertAffineTransform(tform) back cv2.warpAffine(restored, inv, (w, h), flagscv2.INTER_LINEAR, borderModecv2.BORDER_REPLICATE) # 用检测框做软融合避免边缘生硬 mask np.zeros((h, w), dtypenp.float32) x1, y1, x2, y2 [int(v) for v in box[:4]] cv2.rectangle(mask, (x1, y1), (x2, y2), 1.0, -1) mask cv2.GaussianBlur(mask, (0, 0), 8)[..., None] result (back * mask result * (1 - mask)).astype(np.uint8) return result检测阈值0.5是默认值侧脸或小脸多的时候可以降到 0.3但会引入误检。融合那一步用高斯模糊做软边sigma8是经验值太小会有硬边太大修复区域会被原图污染。逆变换必须用invertAffineTransform自己手写矩阵求逆容易出错。3.3 批量处理与显存控制单张跑通后实际项目往往是批量。批量最容易踩的坑是显存泄漏——每张图都新建 tensor 不释放跑几十张就 OOM。正确做法是复用模型、及时del中间变量、必要时torch.cuda.empty_cache()。import os from tqdm import tqdm def batch_restore(input_dir, output_dir, model, device, batch_size1): os.makedirs(output_dir, exist_okTrue) files [f for f in os.listdir(input_dir) if f.lower().endswith((.jpg, .png, .jpeg))] for fname in tqdm(files): try: out full_restore(os.path.join(input_dir, fname), model, device) cv2.imwrite(os.path.join(output_dir, fname), out) except Exception as e: print(f{fname} 处理失败: {e}) finally: torch.cuda.empty_cache() # 每张清一次防止碎片累积batch_size这里保持 1因为每张图人脸数量不定凑批反而复杂。empty_cache放在finally里保证异常时也释放。如果显存实在紧张把channel_multiplier降到 1或者把修复尺寸从 512 降到 256代价是细节损失。4. 参数调优与效果边界哪些图能修好哪些修了更糟4.1 三个必调参数修复强度、融合权重、检测阈值GFPGAN 的效果不是「一键完美」三个参数决定了最终观感。第一个是修复强度源码里通常体现为生成器输出的插值系数。有些实现允许你在原图和对齐修复图之间做线性插值系数 0 是原图1 是完全修复。我一般用 0.7~0.8全 1 会让人脸过于「网红感」皮肤纹理被抹平反而不真实。第二个是融合权重就是上面代码里的高斯 sigma。人脸和背景色差大时sigma 要调大做平滑过渡人脸占比小、背景复杂时sigma 调小避免把背景糊掉。第三个是检测阈值。老照片里人脸可能只有 30×30 像素阈值 0.5 直接漏检。降到 0.3 能检出但可能把窗户、花瓶误判成人脸。折中做法是先低阈值检测再按框大小和长宽比过滤。参数典型值调大后果调小后果修复强度0.7~0.8过度平滑塑料感细节没修回来融合 sigma6~10背景被污染人脸边缘有硬边检测阈值0.3~0.5误检增多小脸漏检4.2 什么图修得好什么图别浪费算力GFPGAN 的强项是「中等退化」的正脸或微侧脸分辨率不低于 64×64五官结构还在只是模糊、噪点、压缩伪影。这种输入修复后提升肉眼可见。它修不好的情况也很明确。第一人脸被大面积遮挡口罩、手、头发盖住半张脸先验补不出来被挡的部分会生成诡异内容。第二极端侧脸或俯仰角超过 45 度对齐阶段就摆不正修复必然歪。第三多人合影里的小脸检测框太小对齐后信息量不足修出来像贴图。第四非人脸区域比如衣服纹理、背景文字GFPGAN 基本无能为力别指望它。判断标准很简单把对齐后的 512 人脸图单独看一眼如果连你自己都认不出这是谁那修复结果也不会好。这时候应该换更强的检测对齐方案或者直接放弃这张脸。5. 避坑与排查那些让我重跑一整天的报错5.1 现象输出人脸发蓝或发灰原因颜色通道顺序错了或者归一化范围不对。OpenCV 默认 BGRPyTorch 模型训练时用的是 RGB归一化如果只除了 255 没做(x-0.5)/0.5输出会整体偏灰。解决在送入模型前cv2.cvtColor(img, cv2.COLOR_BGR2RGB)归一化严格按(tensor/255 - 0.5) / 0.5输出再反向还原。写个单元测试用一张纯色图跑一遍看输出颜色是否一致。5.2 现象CUDA out of memory但显存明明够原因PyTorch 的缓存分配器会保留已释放的显存块碎片化后即使总量够也分配不出连续空间。批量处理时不清理跑十几张就爆。解决每张图处理完调torch.cuda.empty_cache()或者用with torch.no_grad():包住所有推理。如果还不行把channel_multiplier降到 1修复尺寸降到 256。5.3 现象修复后人脸和脖子颜色对不上原因修复网络只处理了人脸框内的区域框外的脖子、耳朵保持原样。如果原图人脸偏暗、脖子偏亮修复后人脸被提亮接缝就明显。解决扩大裁剪框把脖子和耳朵包进去一起修复或者修复后做一次颜色迁移把修复区域的颜色统计对齐到原图。我一般用后者简单且不增加显存。5.4 现象检测不到人脸但肉眼明明看得见原因检测模型对低分辨率、低对比度、大角度人脸不敏感。老照片扫描件对比度低或者人脸只占几十像素默认阈值直接过滤掉。解决降低检测阈值到 0.3同时对输入做一次直方图均衡化提升对比度。如果还不行换更鲁棒的检测器或者手动标框跳过检测阶段。5.5 现象修复结果每张脸风格不一致原因生成器的随机性。有些实现在推理时没有固定随机种子或者对每张脸独立采样隐向量导致同一张图里多张脸修复风格漂移。解决推理时设torch.manual_seed(42)并确认模型处于eval()模式关闭 dropout 和 batch norm 的随机性。如果源码里隐向量是随机采样的改成用检测框位置做确定性映射。6. 进阶把 GFPGAN 接进自己的服务与效果验证跑通单机脚本只是第一步真正落地要考虑服务化和效果验证。我一般会把修复封装成一个 HTTP 接口输入图片 base64输出修复后 base64中间加队列控制并发避免多请求同时抢显存。from fastapi import FastAPI from pydantic import BaseModel import base64 app FastAPI() class Req(BaseModel): image_b64: str app.post(/restore) def restore(req: Req): data base64.b64decode(req.image_b64) arr cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR) out full_restore_array(arr, model, device) # 复用前面的逻辑改成接收数组 _, buf cv2.imencode(.png, out) return {image_b64: base64.b64encode(buf).decode()}并发控制用信号量限制同时推理数一般设为 1因为单卡跑 GFPGAN 已经吃满。多卡可以用进程池每张卡一个 worker。效果验证不能只看「感觉清晰了」。我习惯用两个指标一是人脸区域的 PSNR/SSIM和清晰原图对比看修复是否偏离二是人工盲评把原图、修复图、真实清晰图打乱让同事挑哪张最自然。PSNR 高不代表好看GFPGAN 有时 PSNR 一般但观感更好所以人工评是必须的。一个具体技巧修复前先对输入做一次轻度去噪比如非局部均值能显著减少修复网络把噪点当纹理放大的情况。这个预处理对老照片扫描件特别有效我现在的默认流程里都会加这一步。说到底GFPGAN 不是魔法它是一个有明确适用边界的工具。我踩过最大的坑就是拿它去修风景照结果浪费了一下午算力。后来养成习惯先看对齐后的人脸图认得出再修认不出就换方案。希望帮到你。本文还有配套的精品资源点击获取