简介基于GFPGAN算法的老照片修复Python设计源码面向图像修复、人脸增强方向的Python开发者和研究者用于将模糊、破损的旧照片恢复得更清晰自然。压缩包共51个文件、约6.09MB核心为21个Python脚本承担训练与推理流程7个YAML文件管理参数配置png/jpg图片提供输入输出样例md文档与txt说明辅助上手另有mdb数据记录和pth模型权重文件目录按gfpgan、models、archs、options等模块划分结构清晰。已有491人学习下载。借助这套工程读者可完整了解GFPGAN的代码组织方式、生成对抗网络的人脸先验修复思路并在自有数据集上尝试跑通修复流程项目附带配置、测试脚本、预训练权重及环境说明适合作为二次开发或课程设计的直接起点。1. 老照片修复先修脸为什么我把修复方案定在 GFPGAN 上拿一张上世纪八九十年代的合影人脸边缘几乎融进背景里。直接上普通超分结果是糊被放大成更糊用锐化人脸上又泛起一圈圈振铃。真正让人脸“重新变回人脸”的是这套基于GFPGAN算法的Python老照片修复设计源码。GFPGAN 的思路很直接不用普通卷积一味放大而是借助生成模型先“回忆”出一张清晰人脸长什么样再做退化去除。所以它对低分辨率、噪声、模糊混在一起的老照片人像特别有效。这篇笔记就讲它怎么选型、怎么在 Python 里跑通、批量修一批图时哪些参数必须调以及哪些老照片它其实管不了。2. GFPGAN 的修复逻辑与模型选型先搞懂它在做什么再动手2.1 人脸修复不是放大像素退化去除与生成先验老照片人脸的退化不是单纯的低分辨率。化学褪色、镜头失焦、扫描噪声、灰尘划痕叠加在一起高频细节实际上已经丢了。普通超分模型学的是“低分辨率到高分辨率的映射”它只能把模糊边缘锐化但补不出鼻子轮廓、睫毛方向、皮肤纹理这些物理上不存在的信息。GFPGAN 的做法是两路并行一路从输入图里提取人脸的身份特征另一路把这些特征送入一个预训练好的生成模型在 latent 空间里重新生成一张清晰人脸再与输入做退化对齐。换句话说它用生成先验“补”了原本丢失的信息这一步本质上是生成而不是放大。这也是为什么它修复出来的脸看起来惊艳但稍不注意也会出现“不像本人”的问题——因为它确实有“想象”的成分。理解这一点后面调 weight、调混合比例时才不会抓瞎。工程上还有个容易忽略的好处GFPGAN 不像传统修复管线那样依赖一堆手工设计的先验规则比如边缘保护、纹理合成。你给它一张糊脸它自己知道该往哪个方向修这大大减少了调参工作量。代价是模型体积和显存占用都不算小以及它对“人脸先验”的依赖决定了它只能修人脸区域背景和纸张纹理它并不负责。2.2 选哪个模型v1.2、v1.3、v1.4 还是 RestoreFormer官方仓库里你能拿到的模型大致分四个初次接触的人很容易随便下一个就开跑最后抱怨效果不对。其实四个模型定位差别挺明显模型特点适合场景GFPGAN v1.2修复保守身份保持好但细节增强弱轻微模糊、人脸结构还比较完好的老照片GFPGAN v1.3细节增强猛去模糊明显容易出现“塑料脸”人脸很糊、想突出修复效果时GFPGAN v1.4细节与身份平衡好肤色自然目前最推荐大多数老照片修复默认选它RestoreFormer对遮挡、眼镜、夸张姿态鲁棒极端退化、侧脸、多人小脸场景我的习惯是一律先跑 v1.4。如果某张照片修复后“太现代”再退回 v1.2 看身份保持效果。RestoreFormer 更适合戴眼镜、帽子这种结构化遮挡多的照片v1.4 遇到眼镜容易把镜框修变形。选型的另一个关键是 background upsamplerGFPGANer 里的bg_upsampler参数如果挂上超分模型背景会被一起增强旧照片的颗粒感和纸张纹理就被抹掉了。修老照片我一般把bg_upsamplerNone先把人脸修好背景是否处理再单独决定。2.3 推理管线拆开看检测、对齐、修复、回贴整条推理链路比单跑一个模型要长。GFPGAN 拿到输入图后第一步用轻量人脸检测器找出所有人脸框然后检测五个关键点双眼、鼻尖、嘴角两端再做仿射变换把人脸对齐到标准角度和尺寸。对齐之后才送进 GFPGAN 主模型做修复。最后一步paste_back把修复后的人脸用掩膜贴回原图。这个细节决定了你会遇到的一大类问题如果人脸检测失败后续所有步骤都不会执行输出图就是原样如果对齐旋转没算好贴回的人脸会歪。所以调试时别只盯着主模型先确认检测环节有没有出问题。多人合影默认只修中心区域最大的一张脸要修所有人脸必须把only_center_face设为False。3. 把 GFPGAN 跑起来Python 环境与最小可用脚本3.1 环境准备一套不出错的安装顺序GFPGAN 依赖基础超分库 basicsr、人脸工具库 facexlib以及 PyTorch。最容易翻车的点是安装顺序先装 PyTorch再装 basicsr最后装 GFPGAN 本身。反过来装basicsr 会在安装过程中重新编译算子跟已装的 PyTorch 版本对不上就会报一堆 CUDA 扩展错误。# 用 conda 建独立环境避免污染其它项目 conda create -n gfpgan python3.10 -y conda activate gfpgan # 按 PyTorch 官方命令装对应 CUDA 版本的 torch装 CPU 版也能跑只是慢 pip install torch torchvision # 先装两个关键依赖 pip install basicsr facexlib # 下载 GFPGAN 官方仓库源码并本地安装 git clone gfpgan 官方仓库地址 cd gfpgan pip install -r requirements.txt python setup.py develop这里解释几个关键点。Python 版本我固定用 3.103.11 和 3.12 也能跑但 facexlib 和 basicsr 的一些依赖对旧版 numpy 有要求被系统升级过 numpy 之后容易在运行时才报错。setup.py develop是开发者模式安装它不会把源码复制到 site-packages而是直接引用当前目录这样你后续改仓库里的代码立刻生效调模型结构时不用反复重装。如果看到No module named basicsr.utils这类错误八成是 basicsr 没装或者装错了目录重跑一次pip install basicsr即可。还有一点注意facexlib 首次运行会在用户目录下自动下载人脸检测模型权重如果下载失败代码会在检测那一步崩溃。解决办法是把同事或其它机器上已下载好的权重目录整个复制到对应路径下这比反复试网络稳得多。3.2 模型文件放对位置模型权重体积不小下载回来后先确认文件完整别只看了个文件名就扔进目录。权重文件名要和代码里model_path指向完全一致大小写、后缀都不能错。我一般单独建一个 weights 目录统一管理mkdir -p weights # 把从官方 release 页面下载的 GFPGANv1.4.pth 放到 weights 目录 ls -lh weights/GFPGANv1.4.pth如果执行ls显示的文件大小与官方标注差太多说明下载不完整加载时会报EOFError或者尺寸不匹配。另一个容易踩的点是用户图方便把模型名改成中文或者带空格读取路径一旦出错报错信息只有FileNotFoundError排查半天才发现是名字问题。3.3 单张照片修复的最小脚本下面这段脚本是能跑通的最小集不涉及批量处理方便你验证环境和模型是否正常import cv2 from gfpgan import GFPGANer # 初始化修复器 restorer GFPGANer( model_pathweights/GFPGANv1.4.pth, # 指向刚才放好的权重 upscale1, # 先不要放大保持原尺寸 archclean, # 官方仓库默认架构v1.4 用它 channel_multiplier2, # 必须与权重配套乱改会直接加载失败 bg_upsamplerNone # 关掉背景超分避免旧纸纹被抹掉 ) img cv2.imread(old_photo.jpg) assert img is not None, 图片读取失败检查路径与文件格式 # enhance 返回三个结果裁剪出的人脸、修复后的人脸、整张修复图 cropped_faces, restored_faces, restored_img restorer.enhance( img, has_alignedFalse, # 输入是自然图像需要内部做人脸检测 only_center_faceTrue, # 只修中心区域最大的人脸 paste_backTrue, # 把修复后的人脸贴回原图 weight0.5 # 0-1越大修复感越强越大越不像本人 ) cv2.imwrite(old_photo_restored.jpg, restored_img)代码本身很短但每个参数都值得说清楚。upscale1意思是让 GFPGAN 只做修复不做超分。老照片修复的核心诉求是还原清晰度而不是放大尺寸图像放大放到最后一步单独做更好控制。only_center_faceTrue时即使画面里有多张人脸也只处理中心区域面积最大的那张对于家人合影这种场景这个值要改成False。weight控制最终贴回时“修复脸”和“原脸”的混合比例0.5 是平衡点想让结果更自然更像本人往 0.3 调想修得彻底干净往 0.8 调。第一次跑如果显存不够优先把upscale改小其次把输入图先压缩到长边 1600 像素再处理。CPU 也能跑v1.4 模型约几百兆纯 CPU 处理一张图可能要几十秒到几分钟但不会报错。4. 把单张脚本做成批量修复流程从几张图到一批老照片4.1 批量修复脚本目录遍历与失败隔离修一批老照片时最怕的是某张图让人脸检测崩了整个脚本停住。批量脚本必须做到“单张失败不影响整批”还要把失败文件名打出来方便事后单独处理。我一般这样组织import os import cv2 from gfpgan import GFPGANer restorer GFPGANer( model_pathweights/GFPGANv1.4.pth, upscale1, archclean, channel_multiplier2, bg_upsamplerNone ) def fix_one(path_in, path_out, weight0.5): img cv2.imread(path_in) if img is None: print(f[skip] 读取失败: {path_in}) return False try: _, _, restored restorer.enhance( img, has_alignedFalse, only_center_faceFalse, # 合影也要修所有人脸 paste_backTrue, weightweight ) cv2.imwrite(path_out, restored) return True except Exception as exc: print(f[fail] {path_in}: {exc}) return False os.makedirs(output, exist_okTrue) for name in os.listdir(input): if not name.lower().endswith((.jpg, .jpeg, .png, .bmp)): continue ok fix_one( os.path.join(input, name), os.path.join(output, name) ) print(f{name}: {ok if ok else failed})这里有个工程细节输出目录与输入目录分开绝不在原图上覆盖写。修复结果如果不满意原图还在这是你的后悔药。only_center_faceFalse对合影是必须的否则一张合照只有中间的人被修复边上的人脸保持原样对比起来很突兀。批量跑的时候如果发现某些人脸修得过度再单独针对那几张把weight调低不要全局一刀切。遇到子目录结构时把os.listdir换成os.walk就能递归处理拿到root后在output下重建同名路径。还有一个常见需求是保留修复前后对比图。我习惯在写完restored后用cv2.hconcat把原图和结果横向拼接保存为一组方便批量验收时快速翻阅。4.2 修复前预处理去掉白边、折痕和网纹老照片大多是扫描件直接喂进模型会被白边干扰检测也会让模型把网纹当成画面细节。先做两步预处理能明显提升效果。第一步是自动裁掉白边import numpy as np img cv2.imread(scan_old.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 白边像素接近255阈值化为纯黑白后找最小外接矩形 _, th cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY) coords cv2.findNonZero(th) x, y, w, h cv2.boundingRect(coords) # 四周留2%的边避免把真正的照片边缘裁掉 margin_x int(w * 0.02) margin_y int(h * 0.02) x max(0, x - margin_x) y max(0, y - margin_y) img img[y:y h margin_y * 2, x:x w margin_x * 2] cv2.imwrite(scan_cropped.jpg, img)阈值取 250 是因为很多旧相纸底色不是纯白带一点淡黄阈值太高裁不掉太低会把人物白衣服裁进去。折痕处理我一般用快速傅里叶域滤波但那种方法参数敏感处理不好会出现水波纹。更保险的做法是先用cv2.medianBlur对折痕区域做局部模糊再用原图反向叠加修复。注意折痕不能交给 GFPGAN 修它只认人脸遇到跨脸部的划痕甚至可能把人脸修歪。网纹和折痕不同它是印刷品扫描后形成的规律纹理。对背景区域的网纹我会做低频抑制但人脸区域严重不建议直接滤波因为网纹频率和皮肤纹理频率重叠过滤网纹会顺带把皮肤细节一并抹掉。我的常规顺序是先裁白边再修折痕最后把人脸修复交给模型。4.3 修复后处理让脸不假、背景不糊GFPGAN 输出的人脸干净但偏平滑和老照片的粗糙颗粒感放在一起反而显得“假”。这类问题通过后处理混合能救回来大半。最简单有效的方法是把修复结果和原图按权重叠加blended cv2.addWeighted(restored, 0.8, img_cropped, 0.2, 0)0.8是修复图权重0.2是原图权重意思是保留大部分修复效果但把原图的颗粒质感透出来一点。如果原图太糊0.2原图部分会把模糊感带回来那就把原图权重降到0.1。这里要找平衡老照片修复追求的是“像老照片但又清晰”不是“完全新的高清图”。背景糊的问题另说。如果整张照片背景同样模糊而只有人脸特别清晰观感很割裂。我一般分两种情况处理一种是接受背景现状把修复后的人脸做边缘羽化再贴回另一种是对全图做一次轻量超分但这样会改变照片整体质感。我自己更常做的是前者毕竟背景模糊在旧照片里是可以被容忍的人脸糊才是最不能接受的。羽化贴回可以用cv2.seamlessClone做泊松融合它对肤色边界有天然过渡能解决“贴纸感”。人脸框和姿态检测得越准融合越自然。5. 避坑GFPGAN 修老照片最容易翻车的 5 个现场5.1 显存溢出RuntimeError: CUDA out of memory现象批量跑图跑到一半程序直接崩溃报CUDA out of memory。原因看起来单张图不大但人脸检测和生成模型会同时占显存且upscale4时会放大特征图显存占用翻数倍。解决第一upscale设为 1修复阶段不要放大第二批量脚本里每处理一张图后把中间变量释放用restored保存结果并del img, cropped_faces, restored_faces第三显存只有几 GB 的机器先把输入图长边压到 1600 像素。这些做完仍溢出就分块修复把一张图切成几个区域各自处理再拼回。5.2 输出和原图一模一样人脸检测根本没触发现象跑完一张侧脸照片输出文件名也生成了但用图片对比工具一看原图输出逐像素一致。原因facexlib 自带的检测器在目标人脸过小、侧脸角度过大、或被帽子头发遮挡时检测失败enhance没有检测到人脸就直接返回了原图。这种“静默失败”特别坑因为程序没报错你以为修完了。解决把only_center_faceFalse加上让检测器遍历全图找所有可能的人脸如果仍然没检测到在预处理阶段用 OpenCV 的人脸检测器把脸的区域裁剪出来单独放大 1.5 倍再送进去修复。别让整张图参与检测很多老照片人脸区域面积不到全图的十分之一放大后检测成功率会明显提高。5.3 脸修得不像本人生成先验收不住现象修复后五官精致、皮肤细腻但怎么看都不像照片里的人像是换了张“网红脸”。原因weight设置过大生成结果主导了混合结果身份特征被覆盖了。生成模型本来就有想象力参数越大它越敢“自由发挥”。解决把weight从 0.5 降到 0.3让原图人脸的结构信息多保留一些同时把输入人脸区域裁得稍大一点让模型看到更多脸型轮廓和发际线这些特征能约束生成方向。如果降到 0.2 还像别人那就要换 v1.2 模型试v1.2 的身份保持能力明显更强。超过 0.8 的weight除了极端的模糊照片其他场景不建议用。5.4 脸部与周围肤色脱节贴纸感严重现象人脸区域修复后肤色明显比脖子和手部亮或红边界清晰可见像贴了张新皮。原因模型对人脸区域做了独立照亮和肤色归一化而原图周围区域保留原始色调两者的光照信息不一致。解决最简单的办法是修复后对人脸区域做一次直方图匹配让脸部直方图向原图邻域对齐。用代码实现就是截取人脸框周边 20 像素区域作为参考直方图对修复后的人脸做cv2.matchHistograms。如果项目愿意投入一点工作量用seamlessClone替代直接贴回边界过渡会自然很多。5.5 模型加载报错channel_multiplier 与权重不匹配现象脚本初始化就报错提示张量尺寸对不上比如size mismatch for style_conv1.weight。原因channel_multiplier设错了。不同版本权重的通道倍率不同v1.4 对应 2某些旧权重对应 1混用就会出现尺寸不匹配。解决先确认你下载的权重文件名和代码里加载的是同一个再对照官方示例里的channel_multiplier值。改这个参数之前先看权重文件体积不同倍率的模型体积差很多基本一眼能判断。另外网上能搜到一些别人转换过的 ckpt 文件文件名和官方对不上加载逻辑也不一样尽量避免用这种来源不明的版本。6. 修完怎么验收从“看起来不错”到“修对了”6.1 用闪烁对比法做主观验收我看修复结果从来不看单张图而是用脚本把原图和修复图快速交替切换像幻灯片闪烁一样。人眼对变化区域非常敏感原图中脸上糊掉的轮廓在切换时会“跳动”这个跳动幅度直接告诉你修改了哪里。如果脸型、发际线、眼神方向都没跳只有皮肤纹理在变说明修复是收敛的如果整张脸都“跳”得厉害说明这次修复的改动超出了结构范围必须调低 weight。这个方法比盯着单张图更容易发现“修过了”的问题。6.2 两个能快速算的量化指标import cv2 # 拉普拉斯方差衡量图像锐利度 lap_var cv2.Laplacian( cv2.cvtColor(restored, cv2.COLOR_BGR2GRAY), cv2.CV_64F ).var() print(fsharpness: {lap_var:.1f})lap_var太小人脸依然糊太大则可能过度锐化出现振铃。每张图的合理区间不同不能跨图比较但同一张图修前修后这个值必然上升。另一个指标是 facexlib 人脸检测时返回的置信度修复后同一个人脸的检测分数会比原来高。如果修复后检测分数反而下降说明脸被改得偏离了人脸分布模型可能把脸“修歪”了。这两个指标只做辅助最终用闪烁对比法拍板。6.3 我的验收习惯分类处理而不是一把梭我现在处理一批老照片第一步永远是分类黑白照和彩色照分开单人照和合影分开糊脸照和轻微退化照分开。黑白照先修结构后上色彩色照直接修合影必须only_center_faceFalse严重糊脸用 0.7 的高 weight轻微退化用 0.3 保身份。分类之后批量脚本就变成了三个配置文件的拼装每组照片跑一组参数。之前我图省事一批老照片用同一套参数跑完结果黑白照肤色怪异、合影漏人、轻微退化被修成假脸三样问题凑齐差点整个返工。那次之后再做什么修复工程我都会在输出目录里留一份记录修复参数的文本文件至少写清楚模型名、weight、是否开背景超分、输入是否裁剪。这样一个人修完交付给另一个人后者能准确知道当时的处理路径而不是对着结果猜。老照片修复没有绝对标准答案但过程参数必须可复现。希望这篇笔记能帮你少走点弯路把我踩过的这些坑提前填平。本文还有配套的精品资源点击获取