简介这份资源是一套基于深度学习的老照片修复 Python 项目源码面向计算机、数学、电子信息等专业的学生与开发者可用于课程设计、期末大作业或毕业设计也适合想入门图像修复与深度学习实战的初学者参考。压缩包共 20 个文件约 2.1MB包含 7 个 py 源码文件、3 个 html 页面、5 个 png 与 2 个 jpg 示例图片以及 2 个 pyc 编译文件和 1 个 md 说明文档整体结构紧凑。项目自带 Web 页面通过 templates 目录下的上传、布局与预测页面配合 service.py、color_transformer.py 等模块实现老照片上传后自动上色修复的完整流程models.py 与 util.py 则承载模型定义与工具函数。已有 306 人学习关注读者可借此理解深度学习图像修复的工程组织方式掌握从模型加载、图像预处理到前端交互的完整链路并在此基础上自行调试扩展功能。1. 老照片修复这件事为什么值得用深度学习重做一遍家里翻出一张上世纪八十年代的合影人脸糊成一团、划痕横七竖八、整体偏黄发灰拿去照相馆问修复价格一张开口就是几十上百还未必修得自然。这个场景几乎每个做技术的朋友都遇到过也正是「基于深度学习的老照片修复」这类项目一直有人搜、有人做的原因。它要解决的核心问题就三件事去划痕、去噪点、提升分辨率并尽量还原人脸细节同时给非技术用户一个能直接上传图片、点一下就出结果的 web 页面。这个标题里的关键词——深度学习、python、web、源码——其实已经把整套方案的骨架说清楚了用 Python 搭深度学习推理管线外面套一层轻量 web 服务打包成能直接跑的源码。适合谁会一点 Python、想快速跑通一个能演示的 AI 应用的人以及想拿它当课程设计或作品集的人。下面我按自己实际搭过的思路把选型、代码、参数和坑一次讲透。2. 老照片修复的模型选型与 Python 推理管线怎么搭2.1 为什么是「多模型串联」而不是一个大模型端到端老照片的退化是复合的划痕、噪点、模糊、低分辨率、色彩偏移往往同时存在。指望单个端到端模型一次全修好实际效果通常两头不讨好——去划痕去过头把人脸纹理也抹平超分放大又把划痕一起放大。我一般会拆成三段流水线各管一件事阶段任务常见模型类型输入输出预处理去划痕、去噪图像修复网络inpainting 类原图 → 干净底图超分提升分辨率超分辨率网络SR 类底图 → 高清图后处理色彩校正、锐化传统 CV 轻量网络高清图 → 成品这样拆的好处是每一段都能单独调参、单独替换。比如你发现人脸还是糊可以只换超分那一段不用动整条链路。代价是推理时间变长所以后面 web 层要做异步或加进度提示。选型上新手最容易踩的坑是直接去找「最新最牛」的模型权重结果环境装不上、显存爆掉。我的建议是先用轻量模型把整条链路跑通确认 web 页面能出图再逐段升级。常见做法是超分用 ESRGAN 系列的轻量版本去划痕用基于 U-Net 的修复网络色彩校正用白平衡加 CLAHE 这类传统方法兜底成本低、可控。2.2 用 Python 把推理管线写成可复用的类把三段逻辑封装成一个类web 层只调一个方法这样后面换模型不影响接口。下面是我常用的骨架import cv2 import numpy as np import torch class PhotoRestorer: def __init__(self, sr_model_path, inpaint_model_path, devicecpu): # 设备选择没有 GPU 就退回 CPU别硬撑 self.device torch.device(device if torch.cuda.is_available() else cpu) self.sr_model self._load_sr(sr_model_path) self.inpaint_model self._load_inpaint(inpaint_model_path) def _load_sr(self, path): # 这里按你实际用的超分网络结构加载示例用占位 model torch.jit.load(path, map_locationself.device) model.eval() return model def _load_inpaint(self, path): model torch.jit.load(path, map_locationself.device) model.eval() return model def _preprocess(self, img_bgr): # 统一转 RGB、归一化到 [0,1]尺寸过大的先缩到长边 1024 img cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) h, w img.shape[:2] scale 1024 / max(h, w) if scale 1: img cv2.resize(img, (int(w * scale), int(h * scale))) return img.astype(np.float32) / 255.0 def restore(self, img_bgr): img self._preprocess(img_bgr) tensor torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0).to(self.device) with torch.no_grad(): # 第一步去划痕 cleaned self.inpaint_model(tensor) # 第二步超分 sr self.sr_model(cleaned) out sr.squeeze(0).permute(1, 2, 0).cpu().numpy() out np.clip(out * 255, 0, 255).astype(np.uint8) return cv2.cvtColor(out, cv2.COLOR_RGB2BGR)逻辑说明_preprocess里把长边限制在 1024 是关键老照片动辄四五千像素直接喂进网络显存直接爆先缩再修最后按需放大更稳。torch.no_grad()必须加否则推理时还在建计算图内存翻倍。参数上device默认给cpu让没有显卡的人也能跑有卡再传cuda。2.3 色彩校正这段别偷懒它决定成品像不像「老照片修好的样子」很多人模型跑完发现图是清晰了但整体还是发黄发灰问题就出在没做色彩校正。我一般用两步先做自动白平衡再用 CLAHE 拉一下局部对比度。def color_correct(img_bgr): # 自动白平衡灰度世界假设 result img_bgr.astype(np.float32) avg result.mean(axis(0, 1)) gray avg.mean() for i in range(3): result[:, :, i] * gray / (avg[i] 1e-6) result np.clip(result, 0, 255).astype(np.uint8) # CLAHE 提升局部对比度clipLimit 别调太高否则噪点被放大 lab cv2.cvtColor(result, cv2.COLOR_BGR2LAB) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) lab[:, :, 0] clahe.apply(lab[:, :, 0]) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)clipLimit2.0是我试下来比较稳的值调到 4 以上人脸会出现明显色块。tileGridSize用 8×8图小的时候可以降到 4×4。3. 自带 web 页面怎么做Flask 上传接口与前端最小实现3.1 后端一个上传接口加一个结果接口就够了web 层不要一上来就上重型框架Flask 几十行就能跑通。核心是两个路由一个返回页面一个接收图片返回修复结果。from flask import Flask, request, render_template, send_file import io from PIL import Image import numpy as np import cv2 app Flask(__name__) restorer PhotoRestorer(weights/sr.pt, weights/inpaint.pt, devicecpu) app.route(/) def index(): return render_template(index.html) app.route(/restore, methods[POST]) def restore(): file request.files.get(photo) if file is None: return {error: 没有收到图片}, 400 # 用 PIL 读兼容 jpg/png/webp img Image.open(file.stream).convert(RGB) img_bgr cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) out restorer.restore(img_bgr) out color_correct(out) # 编码回 jpg 返回 _, buf cv2.imencode(.jpg, out, [cv2.IMWRITE_JPEG_QUALITY, 95]) return send_file(io.BytesIO(buf.tobytes()), mimetypeimage/jpeg) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)逻辑说明request.files.get(photo)里的字段名要和前端name对上这是新手最常见的 400 来源。debugFalse在演示时更稳debugTrue的热重载有时会让模型重复加载。IMWRITE_JPEG_QUALITY给 95再高文件变大收益很小。3.2 前端一个表单加一段 fetch别引入构建工具前端越简单越好一个 HTML 文件搞定不需要 npm、不需要打包。!DOCTYPE html html langzh head meta charsetutf-8 title老照片修复/title /head body h2上传老照片/h2 input typefile idphoto acceptimage/* button onclicksubmitPhoto()开始修复/button p idstatus/p img idresult stylemax-width:100%;display:none; script async function submitPhoto() { const file document.getElementById(photo).files[0]; if (!file) { alert(先选一张图); return; } document.getElementById(status).innerText 修复中请稍候…; const fd new FormData(); fd.append(photo, file); // 字段名必须和后端一致 const resp await fetch(/restore, { method: POST, body: fd }); if (!resp.ok) { document.getElementById(status).innerText 修复失败看后端日志; return; } const blob await resp.blob(); const img document.getElementById(result); img.src URL.createObjectURL(blob); img.style.display block; document.getElementById(status).innerText 完成; } /script /body /htmlfd.append(photo, file)的字段名和后端request.files.get(photo)必须一字不差这是前后端联调翻车率最高的一处。URL.createObjectURL用完记得在换图时revokeObjectURL不然连续上传多张内存会涨。3.3 启动与依赖把环境固定下来再谈效果依赖不固定换台机器就跑不起来这是「源码」类项目最容易被吐槽的点。我一般会写一个requirements.txtpip install flask pillow opencv-python numpy torch torchvision python app.pytorch的安装要按你的平台选对应命令CPU 版和 CUDA 版不一样装错了要么跑不了 GPU 要么体积巨大。启动后浏览器开http://127.0.0.1:5000先拿一张小图测通链路再上大图。4. 避坑与排查老照片修复项目最容易翻车的 5 个地方4.1 现象上传后一直转圈最后 500原因多半是模型加载失败或输入尺寸过大导致显存/内存爆掉。解决先在restore里打印输入尺寸确认预处理缩放生效再把device强制设成cpu跑一遍排除显卡问题。日志里如果出现CUDA out of memory把长边限制从 1024 降到 768。4.2 现象修完人脸像塑料纹理全没了原因去划痕模型强度过高或者超分模型把平滑区域过度锐化。解决把去划痕那一步的输出和原图做加权融合比如out 0.7 * cleaned 0.3 * original保留一部分原始纹理超分后不要叠加额外锐化。4.3 现象颜色比原图还怪脸发绿或发紫原因白平衡的灰度世界假设在偏色严重的图上会失效三个通道增益算歪了。解决给增益加一个上限比如限制在 0.8 到 1.25 之间或者干脆跳过自动白平衡只做 CLAHE让用户自己判断。4.4 现象前端显示「修复失败」后端却没报错原因返回的不是图片可能是 JSON 错误被当成 blob 显示。解决前端在resp.ok为真时再检查resp.headers.get(content-type)是否以image/开头不是就提示具体错误。4.5 现象连续上传几张后服务变卡甚至崩原因URL.createObjectURL创建的对象没释放加上模型常驻内存累积起来就顶不住。解决换图前URL.revokeObjectURL(img.src)服务端如果并发高把推理放进队列串行处理别让多个请求同时抢显存。5. 让修复效果再上一档分块推理与结果对比的实用技巧链路跑通之后真正拉开差距的是大图处理和效果验证。老照片经常是几千像素直接缩放会丢细节我的做法是分块推理再拼接把图切成有重叠的块每块单独过模型重叠区域做加权平均这样既控制显存又保留细节。def tile_inference(restorer, img_bgr, tile512, overlap64): h, w img_bgr.shape[:2] out np.zeros_like(img_bgr, dtypenp.float32) weight np.zeros((h, w, 1), dtypenp.float32) step tile - overlap for y in range(0, h, step): for x in range(0, w, step): y2, x2 min(y tile, h), min(x tile, w) patch img_bgr[y:y2, x:x2] restored restorer.restore(patch).astype(np.float32) # 边缘做羽化避免拼接缝 out[y:y2, x:x2] restored weight[y:y2, x:x2] 1.0 return (out / np.maximum(weight, 1e-6)).astype(np.uint8)tile512、overlap64是我在 8G 显存上比较稳的组合块再大容易爆重叠太小会有明显接缝。验证效果别只靠肉眼我习惯把原图、修复图、以及两者的差值图并排看差值图能暴露哪里修过头、哪里没修到。参数建议值调大后果调小后果tile512显存吃紧接缝变多overlap64速度变慢拼接痕迹明显clipLimit2.0色块、噪点放大对比度提升不明显长边限制1024显存风险细节丢失最后说个我自己的习惯每次改完模型或参数固定拿同一组五张图跑对比存到一个samples/目录里隔几天回看比当场凭感觉判断靠谱得多。这个方向投入不大、见效快值得动手做一遍。希望帮到你。本文还有配套的精品资源点击获取