
简介这是一份基于Python与深度学习的老照片修复项目完整代码包面向对图像恢复、卷积神经网络及生成对抗网络感兴趣的开发者和学习者。资源包含81个文件以54个py源码文件为核心覆盖模型构建、数据预处理、训练与推理全流程并内置旧照片与高清修复图配对训练数据作为训练示例同时附带17张png前后对比图、1张jpg和1张gif演示动画、4份pdf技术报告及1个mp4项目介绍视频可直观评估修复效果。压缩包整体约50.71MB目录划分清晰包含test_images、Face_Enhancement、models、options等模块便于按功能定位代码。内容提供完整训练脚本、人脸检测脚本与测试工具并配有requirements依赖文件涵盖数据归一化、损失函数与优化器设置、卷积与池化层设计等关键环节亦对生成对抗网络的生成器与判别器对抗训练策略有所体现可快速在TensorFlow、Keras或PyTorch环境部署运行。目前已有350人学习此项目适合想通过实战掌握AI老照片修复技术的Python开发者。1. 老照片修复不是套滤镜深度学习解决的是“信息重建”而不是“美化”一张上世纪的黑白合影扫描进电脑后划痕、霉斑、噪点布满画面人脸区域放大看已经糊成一团——这才是“严重退化”的老照片。传统方法只能做对比度拉伸和瑕疵涂抹对大块缺失和纹理崩坏无能为力修出来不是磨皮就是表情被抹掉。老照片修复项目用深度学习把“脏、裂、糊、噪”当成一个端到端映射输入退化图输出接近原始状态的干净图。它适合两类人一是要批量处理老旧影像的档案数字化从业者二是想在图像生成方向做落地练手的Python工程师。你不需要重新发明网络真正要串起来的是退化建模、模型选型、推理管线三件事。2. 老照片退化建模把“脏、裂、糊、噪”拆成可计算的问题2.1 老照片退化的四种典型损伤老照片的“脏”不是一种情况至少拆成四类。一是划痕和霉斑这类损伤在空间上是局部稀疏的但会横跨人脸或背景线性结构明显。二是高斯噪声和颗粒感扫描仪老化或底片显影不均匀都会带来灰度值随机跳动。三是模糊来自对焦不准、扫描抖动或快门慢等价于图像和二维卷积核做了卷积低频被保留、高频被抹平。四是压缩和色彩退化早年的JPEG压缩会在边缘周围出现振铃胶片的色彩通道还会随时间窜色。这四类退化对应不同的数学建模方式。划痕可以抽象成随机位置的线段或点簇噪点用高斯或脉冲分布描述模糊用一个特定尺寸的卷积核压缩伪影则直接走一次JPEG编码再解码。把这四者按随机权重叠加就能近似出“严重退化”的真实分布。光靠一张真实老照片是没法训练深度模型的因为没有对应的“干净原图”做监督。所以常见做法是先收集一批高清人脸和风景图自己合成退化图再让模型学习逆映射。2.2 用退化合成造训练对不让模型“没见过世面”合成退化的核心不是“自虐式地加噪声”而是要覆盖模型将来在真实场景里会遇到的边界情况。我一般会把退化强度分成三档轻度小划痕、轻微噪点、中度加上模糊和JPEG压缩、重度多划痕、大噪声、反复压缩。训练时按比例采样避免模型只擅长某一档同时在重度和中度之间连续插值。真实扫描照片会有摩尔纹、扫描拖影这类合成出不来的东西但你只要把基础退化做扎实模型至少能把60%的脏问题消掉剩下靠后处理兜底。这里给出一个最简合成脚本先把四种退化分别写成函数再按随机参数组合调用。注意所有操作必须作用在灰度或RGB三通道上保持一致否则模型学到的是“颜色无关的纹理猜测”。import numpy as np import cv2 def add_scratch(img, num_lines12, max_len0.3): h, w img.shape[:2] out img.copy() for _ in range(num_lines): angle np.random.uniform(0, np.pi) length np.random.uniform(0.1, max_len) * min(h, w) x0 np.random.randint(0, w) y0 np.random.randint(0, h) x1 int(np.clip(x0 length * np.cos(angle), 0, w - 1)) y1 int(np.clip(y0 length * np.sin(angle), 0, h - 1)) color 255 if np.random.rand() 0.5 else 0 thickness np.random.randint(1, 3) cv2.line(out, (x0, y0), (x1, y1), (color,), thickness) return out def add_noise(img, sigma12): noise np.random.normal(0, sigma, img.shape).astype(np.float32) return np.clip(img.astype(np.float32) noise, 0, 255).astype(np.uint8) def add_blur(img, kernel_size5): return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0) def add_jpeg(img, quality60): _, enc cv2.imencode(.jpg, img, [int(cv2.IMWRITE_JPEG_QUALITY), quality]) return cv2.imdecode(enc, 1)脚本里的每个函数都对应前面拆解的一类退化。add_scratch里num_lines控制划痕条数max_len限制划痕长度color随机取0或255是为了模拟白划痕和黑划痕两类视觉效果。add_noise的sigma单位是灰度值12表示标准差约12个灰阶肉眼能看到噪点但不会完全遮住纹理。add_blur的kernel_size必须用奇数否则cv2.GaussianBlur会直接抛错。add_jpeg的quality越低振铃越重60是“老扫描件”常见的劣化程度。组合调用时不要每次都四层全上否则模型会把所有图都当重度退化处理。我常用的策略是按0.6/0.3/0.1的概率选中度、重度、轻度档位中档只做噪声加模糊重档再加划痕和JPEG。这样合成的batch内退化分布宽训练出来的模型对“没见过的脏”的泛化能力更好。想更精细的话可以把划痕的透明度和颜色渐变也加进去但那些是锦上添花基础的四种先保证。2.3 退化参数怎么设一张表说清混合比例与强度参数选择有门槛但不用靠玄学按下面这张表设初始值再根据验证集效果微调即可。退化类型参数初始值调整方向划痕num_lines8-15模型漏修时增大输出变脏时减小划痕粗细thickness1-2老照片扫描件常用1笔痕迹可到3高斯噪声sigma8-16噪点残留明显时先降sigma别急着加层数高斯模糊kernel_size3-7模糊没消掉再调大过大会让脸成糊饼JPEG质量quality50-70出现振铃环线时调到40以下但要配合感知损失组合概率–中0.6/重0.3/轻0.1真实图偏轻就调高轻档比例参数表里最容易被忽略的是“调整顺序”。比如输出图上有噪点残留第一反应是加噪声强度让模型更适应但真实情况往往是模型对高频纹理吃了亏此时应该降低噪声强度、提高patch分辨率让模型看到更多真实细节。另一个常见误区是模糊核取得太大导致模型学会“去模糊等于磨皮”后续人脸细节全丢。模糊核的尺寸不要超过输入patch边长的十分之一这是个人工经验阈值超过这个比例训练会很慢且难收敛。3. 修复模型选型为什么不能直接套超分预训练权重3.1 生成式还是判别式修复不等于超分老照片修复和超分辨率长得像但目标不同。超分模型接受的是“清晰但低分辨率”的输入任务是补出高频细节修复模型接受的是“有噪声、有划痕、有模糊”的输入任务是先剔除这些伪信息再补结构。如果直接套用超分权重模型会把划痕当成真实边缘去增强结果就是每道划痕都被描得更粗、更黑。所以选型时要优先考虑那些在降质建模上做过专门设计的架构而不是无脑搬超分榜一。生成式和判别式的选择也在此分叉。纯判别式模型例如带L1损失的U-Net变体训练稳定、结果保守不会把眼睛画成另一个人但对大块缺口的“脑补”能力弱。生成式模型加了对抗损失纹理更锐利却容易产生幻觉——它会在不确定的地方凭空造出五官。对老照片修复来说我的倾向是“判别式为主、生成式微调为辅”先用判别式训练到loss平台期再以极小权重例如0.01叠加对抗损失finetune让模型只借GAN的边缘锐化能力不让它接管内容生成。3.2 常用骨架与损失函数L1、感知、对抗怎么配网络骨架方面轻量场景可以先从EDSR或RRDB这类超分骨架起步它们把残差结构做得很干净容易收敛。想省显存就用U-Net加残差块encoder-decoder结构天然适合像素级回归。带自注意力的SwinIR对纹理保持更好但显存占用高一截256x256输入在8GB卡上勉强能跑4K老照片必须切块。还有一类带人脸先验的方案适合人像占比高的照片但要注意先验过强时“换脸”风险后面避坑章细说。损失函数是修复项目里最值得调的部分推荐按下面这个公式起步import torch def repair_loss(pred, target, l1_weight1.0, percep_weight0.1, adv_weight0.01, use_ganFalse): l1_loss torch.abs(pred - target).mean() # vgg_loss 取 VGG19 某个中间层的特征距离需要按所选框架实现 percep_loss vgg_loss(pred, target) adv_loss torch.tensor(0.0) if use_gan: # discriminator 是预先训练好的判别器只输出一个真伪分数 adv_loss -torch.mean(discriminator(pred)) return l1_weight * l1_loss percep_weight * percep_loss adv_weight * adv_lossl1_loss保证像素级的结构对齐不管模型怎么发挥输出和原图的骨架不能偏。percep_loss取VGG某一层的特征做距离防止L1把纹理磨平它让输出在高频结构上与目标更接近。adv_loss是可选分支用判别器判断输出是否“像一张真实照片”权重必须压在0.01量级否则训练两三百步后就开始换脸。这里的vgg_loss和discriminator按你选的框架实现关键是三个权重的量级关系L1是地基感知是中间层对抗只做最后十步的抛光。另一个常被忽视的组件是噪声图输入。很多修复模型会额外接收一张噪声估计图让网络知道哪里退化重、哪里退化轻。这个先验在合成阶段就能拿到合成时记录了噪声sigma推理时则用退化图和它的自差来近似。加不进网络骨架时至少把它拼进退化图的通道里效果提升很直接。有人习惯用codex这类工具跑深度学习流程但损失权重这种细节还是要自己盯曲线工具顶多帮你把训练脚本串起来。3.3 输入输出与预处理灰度还是RGB归一化到多少老照片有黑白有彩色输入通道不是越多越好。黑白照片转成RGB三通道喂给模型会让模型误以为三个通道有独立的颜色信息容易在输出中编出色斑。常见做法是训练时黑白和彩色按比例混合推理时黑白图先复制到三通道但把模型最后一个卷积层的颜色输出压到原始灰度结构上用灰度投影或低通混合。RGB输入归一化到[0,1]或[-1,1]都行关键是训练和推理必须一致。我最常踩的坑是训练时归一化到[0,1]推理时忘除以255模型输出直接变黑或变白。预处理还有两个小细节。一是patch采样时随机旋转和翻转别让模型对人脸方向产生偏置。二是在训练集里混入少量真实扫描件哪怕没有干净原图只做半监督否则模型对扫描仪特有的摩尔纹毫无反应。摩尔纹频率高、呈周期性合成时用正弦条纹叠加也能模拟但真实扫描件的分布更散混几张进去成本很低收益明显。4. 用Python跑通修复管线从解压权重到切块推理4.1 环境准备Python版本、虚拟环境与依赖安装拿到zip压缩包后先别急着一键跑检查三件事Python版本是否匹配、目录有没有中文路径、权重文件是否完整。常见做法是解压到纯英文路径比如~/projects/old_photo然后为项目建独立虚拟环境。老照片修复项目通常依赖PyTorch、OpenCV、NumPy和tqdmPyTorch版本建议1.12以上Python用3.8到3.10之间太新的Python偶尔会和某些旧版CUDA工具链冲突。unzip old_photo_repair.zip -d old_photo_repair cd old_photo_repair python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install --upgrade pip pip install -r requirements.txt解压后项目目录一般长这样weights/放模型权重data/放输入输出图models/放网络定义scripts/放训练和推理脚本。requirements.txt里如果有torch建议先把torch单独装完再装其他包避免依赖解析器卡在版本组合上。权重文件如果体积很大几百MB先检查文件大小和解压是否完整zip包下载截断是常见的白忙活原因。4.2 推理脚本加载权重、切块、融合与边界处理推理的核心是“别让模型直接吃整张大图”。显存不是唯一约束更重要的是修复模型通常只在256到512像素的patch上训练过直接喂4K图会触发分辨率外推输出出现重复纹理和网格伪影。所以管线固定为切块、逐块推理、重叠融合三步。import numpy as np import torch def repair_image(img, model, device, patch_size512, overlap64, grayFalse): h, w img.shape[:2] out np.zeros((h, w, 3), dtypenp.float32) weight_map np.zeros((h, w, 1), dtypenp.float32) step patch_size - overlap for y in range(0, h, step): for x in range(0, w, step): y_end min(y patch_size, h) x_end min(x patch_size, w) patch img[y:y_end, x:x_end].astype(np.float32) / 255.0 if gray: # 灰度图复制成三通道保持通道间完全相等 patch np.repeat(patch[..., :1], 3, axis2) patch_t torch.from_numpy(patch).permute(2, 0, 1).unsqueeze(0).to(device) with torch.no_grad(): pred model(patch_t).squeeze(0).permute(1, 2, 0).cpu().numpy() pred np.clip(pred, 0.0, 1.0) * 255.0 out[y:y_end, x:x_end] pred weight_map[y:y_end, x:x_end] 1.0 repaired out / np.maximum(weight_map, 1e-6) return repaired.astype(np.uint8)这个脚本有三个关键点。第一step patch_size - overlap决定了相邻patch的重叠宽度建议overlap至少64像素小于32时缝线会非常明显。第二每个patch在送入模型前除以255归一化推理输出再乘回255这个来回如果漏一步结果要么发黑要么过曝。第三重叠区域用累加求平均来融合等效于一个均匀权重的低通混合能消掉大部分接缝想更平滑就把权重改成以patch中心为峰值的三角窗。实际推理时还有一个工程细节把batch维用起来。上面的循环逐patch推理512patch在消费级显卡上很快但如果机子是CPU推理建议把多个patch攒成batch一次性forward能省掉大量的Python循环开销。要注意把不同尺寸的patch统一pad到patch_size推理后再裁掉pad区域否则step循环会对不齐。模型加载时记得切到eval()模式BatchNorm在训练和推理模式下行为完全不同漏掉这步会让输出出现莫名的偏色。4.3 效果评估PSNR/SSIM之外还要看什么跑通管线后怎么判断“修复得好不好”PSNR和SSIM是基准指标但严重退化场景里这两个指标经常失真。PSNR对像素级偏移非常敏感老照片扫描时0.5像素的配准误差就能让PSNR掉2个dB但人眼看着并无差异。SSIM更关注结构可它对模糊有天然偏好把图磨平了SSIM反而高。所以我的评估清单是三样一是人工盲评找10张代表性样本让人分辨修复前后是否“变干净且不变形”二是人脸关键点对齐误差修复后人脸五官位置漂移超过阈值就算失败三是边缘结构相似度用Canny边缘做二值对比修复图应该保留退化图里的真实边缘同时去掉划痕产生的假边缘。python scripts/evaluate.py --input data/old --output result/repaired --reference data/clean评估脚本通常会输出一张逐图对比表和平均值。如果你手里没有干净参考图真实老照片常有这种情况那就退化合成验证把clean图合成degraded再修degraded看偏离clean多少。这是公认的“退而求其次但可用”的评估方式至少能度量模型的稳定性和参数敏感性而不是只看几个好看的数字。跑完评估后把每张图的PSNR和SSIM按从小到大排序重点看最差的几张能翻车的地方全在尾部样本里。注意如果你没有干净参考图退化合成验证是唯一能量化评估的方式别跳过它。5. 翻车排查老照片修复的5个高频坑与解决5.1 现象人脸被“画”成另一个人修复完的人脸五官立体、皮肤光洁但一看就不是原主甚至换了性别和年龄。原因有两个一是训练集里的人脸先验太强模型在信息不足时直接调用了记忆中的“标准脸”二是对抗损失权重过高判别器逼着模型输出极度锐利的人脸纹理。解决方法是先砍adv_weight从0.01降到0.001重训或finetune然后检查输入patch分辨率小于256像素时人脸信息太少模型只能猜。最后可以在损失里加一项身份一致性损失用预训练人脸识别网络提特征约束修复前后特征距离。对档案类修复我的态度是宁模糊不换脸身份比锐度重要。5.2 现象输出出现规则色斑和紫色伪影色斑通常呈斜向规则条纹或照片边缘的紫绿色块看着就不像照片自然发色。原因多是归一化不一致外加模型最后没有限制输出范围网络输出经过残差连接后可能超出[0,1]你如果直接乘255保存超出的通道就会被截断成奇怪的饱和色。解决办法是推理时把模型输出强制clip(0, 1)再转回uint8训练时损失计算也用clip后的值参与。色斑颜色偏向某一通道时检查预处理是否把RGB顺序搞错OpenCV读图是BGRPyTorch训练常用RGB通道顺序错乱会直接导致颜色学歪。5.3 现象大图爆显存切块后纵向缝线明显4K老照片直接推理几乎必爆显存哪怕显存够也会让模型分辨率外推输出全是网格伪影。切块能解决显存但切得不好会留下井字形缝线。原因是overlap太小或没有融合权重相邻patch的边界处模型看到的上下文不同输出有跳变。建议patch_size512, overlap64起步融合时用带中心权重的锥形窗还不行就检查模型内部是否有BatchNorm层BN在推理时会用训练集统计量遇到没见过的域会导致patch之间色调不一致换成InstanceNorm或GroupNorm通常能根治。5.4 现象黑白照片修完变成“彩色噪点图”输入是单通道灰度模型却输出满屏彩色闪烁点尤其皮肤区域最严重。原因是训练数据里彩色照片占多数模型把灰度输入当成了丢失色度信息的彩色图自己在三个通道里编造颜色。解决分两步训练时至少混入30%的真灰度样本并把灰度图复制成三通道喂入保持通道间完全相等推理时对灰度输入模型输出后强制取三通道均值或做颜色去饱和操作。这类问题在合成阶段就能预防第4章代码里的gray分支就是干这个的别嫌麻烦。5.5 现象loss正常下降但修复结果“一眼假”训练loss从0.1降到0.03验证图看着却像磨皮过头纹理全平。原因通常是退化合成强度没跟上模型能力模型已经把简单退化学完剩下真实照片里的复杂退化它没见过于是选择用“模糊”来逃避细节矛盾。解决方法是动态调整退化参数比如每训练若干轮把sigma和num_lines上限增加10%逼模型持续解更难的任务同时检查感知损失权重percep_weight太高会让模型过度平滑。这里最容易犯的错是只看PSNR不看纹理看到PSNR涨了就停训结果就是磨皮脸。6. 进阶给修复结果上保险——一致性校验与人工介入点模型不是万能的落地老照片修复必须设校验闸门。我常用的做法是三重校验第一重通道统计校验检查输出图像的直方图是否和输入在整体亮度上偏离过大老照片修复是保持光照结构的如果输出亮度跳变超过30%说明模型做了什么不该做的事第二重边缘一致性校验用Canny算子分别提取输入和输出的边缘图计算交并比划痕处的假边缘应该被消掉而真实轮廓线的保留率应超过85%第三重人脸关键点校验检测修复前后关键点坐标的欧氏距离超过阈值就标记为“需人工复核”。实际处理批次照片时我会把校验失败的照片单独归到review/目录不做自动覆盖。对这类照片有两种兜底手段一是降低修复强度把模型输出和原图按0.7比0.3混合牺牲干净度换真实感二是局部重修复只把失败区域裁出来用更小patch、更低对抗权重再跑一次。批量脚本里保留原图、退化图、修复图和差异热力图四份输出差异热力图用原图和修复图的像素差生成方便人工快速扫一眼就定位出模型“擅自改动”的地方。这套流程跑下来我最大的教训是老照片修复的验收标准必须由照片本身决定而不是由模型决定。遇到人脸、证件、历史文献这类照片我会直接关闭GAN分支宁可让画面保留一点点噪点也不能让它编造信息。现在接手任何修复任务第一步永远是先跑一张真实老照片盯着看模型是“在擦除”还是“在重绘”重绘倾向明显就回头调损失权重。这个观察动作比任何指标都灵敏希望帮到你。本文还有配套的精品资源点击获取