深度学习老照片修复:Python 构建从划痕检测到高清重建的完整管线

发布时间:2026/10/11 14:27:46
深度学习老照片修复:Python 构建从划痕检测到高清重建的完整管线 简介基于深度学习的Python老照片修复系统实现源码包面向对图像修复、生成对抗网络感兴趣的开发者与研究学习者针对历史照片常见的斑驳噪点、局部缺损、色彩失真及细节模糊等退化问题给出了一套完整的复现方案可辅助完成相关课题或毕业设计。方案以卷积神经网络为骨架结合对抗式生成网络与注意力机制通过预训练特征提取识别语义内容、多尺度特征融合重建缺失纹理、自适应色彩校正还原自然色调在保持结构连贯性与视觉真实性的前提下使PSNR与SSIM分别达到28.7dB和0.89。资源包为zip格式共98个文件以54个Python脚本为主要代码覆盖训练、测试、人脸检测、数据加载等模块另含4份PDF说明文档、1个mp4演示视频、gif动图和示例图片整体大小约52.18MB。目前已有70人浏览学习适合用于个人学习与项目复现。借助源码、项目结构文档和演示素材可快速理解老照片修复的完整流程与实现细节并可在现有框架上进行二次开发。1. 深度学习老照片修复从一张破损旧照到可复现的 Python 修复系统老照片修复在深度学习普及之前是个纯手工活PS 里一笔一笔补划痕、补噪点一张图没几个小时下不来。而现在的开源生态已经把这条路走通了用 Python 搭一个修复系统从划痕检测、去噪、补全到上色和超分流程化处理一张百年前的破损照片最后输出高清结果整个过程基本不需要人工干预。这套系统的核心是几个关键模型OpenCV 传统图像处理做预处理生成对抗网络GAN类模型做划痕修复和补全超分模型如 ESRGAN把低分辨率旧图拉回高清必要时再叠加 DeOldify 做黑白上色。这套技术栈放在个人学习场景里有非常强的落地属性因为每个环节都有现成的 Pre-trained 模型和 Python 库不需要自己造数据集也不需要高端 GPU 就能跑通。对于从业者来说这类系统的价值不在“它有多新”而在于整个 Pipeline 可以完整复现输入一张带折痕和噪点的旧照片中间每一步都能看到中间结果哪一步出问题可以在哪个环节定位修复。本文会把整个系统的拆解思路、关键代码实现、参数设定逻辑和踩过的坑完整写出来帮助你从零开始搭一套自己的修复系统。2. 核心架构把照片修复拆成“能看到中间结果”的五个模块2.1 预处理为什么先做划痕检测而不是直接丢给修复模型老照片问题通常是复合的既有物理损伤折痕、划痕、霉斑又有时光损伤噪点、失焦、褪色。如果直接把原图丢进修复模型模型会同时承受多种退化修复效果大概率是全方位的“糊”。常见的做法是先把图像本身的物理损伤识别出来生成掩码Mask再分步处理。这个思路脱胎于 CVPR 等学术工作里的“Mask 引导修复”策略从工程视角看有三个直接好处第一物理损伤区域通常只占全图的 5%-15%修复模型只关注这些区域参数量和推理时间都能降下来第二干净区域的原始纹理不会在修复过程中被“和平演变”保存了真实的历史感第三掩码可以让下游模型知道哪里是不可信区域对输出结果做加权融合视觉上更自然。划痕检测在传统 CV 里没有特别通用的一招但把图像转换成灰度、再做边缘检测和形态学闭运算能得到一个相当不错的初始掩码。import cv2 import numpy as np # 读取并灰度化降噪降低后续边缘检测的误报 img cv2.imread(old_photo.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray cv2.GaussianBlur(gray, (5, 5), 0.0) # 黑帽运算突出暗背景上的亮划痕白帽运算检测暗划痕/折痕 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15)) blackhat cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel) whitehat cv2.morphologyEx(gray, cv2.MORPH_WHITEHAT, kernel) # 合并两类划痕特征 hat cv2.addWeighted(blackhat, 0.5, whitehat, 0.5, 0) # 自适应阈值 形态学闭运算填补断裂的划痕线 _, mask cv2.threshold(hat, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8)) cv2.imwrite(mask.png, mask) print(划痕掩码生成完成白色区域为待修复区域)这段代码里面有两个关键参数可以直接解释清楚。GaussianBlur的(5, 5)是降噪窗口大小窗口越大细节损失越多测试下来 5x5 在保留纹理和压制噪点之间最平衡一旦调成 11x11细小划痕会和噪点一起被磨平掩码断裂明显变多。MORPH_CLOSE的(5, 5)则是把断开的划痕线段桥接起来如果划痕是头发丝那种极细的建议加大到(9, 9)但要注意别把假皱纹和纸张纹理也并进去。这个掩码生成步骤看着简单实际它是整套系统的地基——掩码的质量直接决定后续修复模型会不会在错误区域加工出奇怪纹理。2.2 主修复模型用 LaMa 还是 OpenCV 的 INPAINT_TELEA拿到了掩码后下一步就是把掩码区域的像素补出来。这里我建议分两档方案快速原型阶段直接用 OpenCV 内置的cv2.inpaint拿到可演示的结果同时感受一下修复边界的问题正式跑高质量版本时切到 LaMaLarge Mask Inpainting它是目前开源生态里综合效果最好的修复模型之一尤其对大块缺失区域的处理远好于传统算法。选 LaMa 还有个实际原因ONNX 权重可以直接在 CPU 上推理对没有 NVIDIA GPU 的同学非常友好一张 512x512 的图大概 3-5 秒完全在可接受范围。传统 inpaint 的优势是零依赖但它的算法本质是扩散式填充只适合边缘清晰的细线损伤碰到照片撕裂、霉斑成片的情况填充结果就是一片“糊掉的猜测”没有真实的纹理结构。LaMa 则是基于 Fourier 卷积的 GAN 架构能在大掩码下生成带结构感的纹理。两种方案的切换代码也很直观import onnxruntime as ort import cv2 import numpy as np # 方案 A传统快速修复适合细划痕效果下限低 res_telea cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA) cv2.imwrite(result_telea.jpg, res_telea) # 方案 BLaMa ONNX 推理适合大块缺失/复杂背景 session ort.InferenceSession(lama_fp32.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape # [1, 3, H, W] # 输入要求图像和掩码都 resize 到模型规定尺寸归一化到 [-1, 1] def preprocess(img_bgr, mask_gray, size512): img cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) img cv2.resize(img, (size, size)) / 127.5 - 1.0 mask cv2.resize(mask_gray, (size, size)) / 255.0 mask[mask 0.5] 1.0 mask[mask 0.5] 0.0 return img.transpose(2, 0, 1)[None].astype(np.float32), mask[None, None].astype(np.float32) img_tensor, mask_tensor preprocess(img, mask) out session.run(None, {input_name: img_tensor, mask: mask_tensor})[0][0] # 输出回 BGR 并还原到原图尺寸 out ((out.transpose(1, 2, 0) 1.0) * 127.5).astype(np.uint8) result cv2.resize(cv2.cvtColor(out, cv2.COLOR_RGB2BGR), (img.shape[1], img.shape[0])) cv2.imwrite(result_lama.jpg, result)这里有几个必须注意的细节。第一输入图像和掩码必须同时做 resize且要采用相同的缩放方式cv2 默认的 INTER_LINEAR否则掩码和原始图像的对齐关系错位修复区域会整体漂移第二归一化到[-1, 1]是 LaMa 训练时的数据分布千万不要自作聪明归一化到[0, 1]那会导致修复结果偏灰偏暗第三ort.InferenceSession的providers[CPUExecutionProvider]如果你有 GPU可以改成[CUDAExecutionProvider, CPUExecutionProvider]自动回退实测一张 512x512 的推理时间能从 4 秒降到 0.5 秒以内。我一般建议主修复阶段直接用 LaMa传统 inpaint 只作为快速比较基线因为两版结果的差异肉眼可见。2.3 背景看这里修复结果融合策略模型输出的修复图是直接替换原图但直接替换有个问题修复区域的色彩、亮度与周边原图可能存在轻微偏差融合不好会留下明显的“补丁感”。通用的融合策略是 alpha blending但简单的全局 alpha 会导致修复区域边缘出现半透明拖影。更稳的是使用羽化掩码feather mask让过渡区域的权重平滑变化# 掩码高斯模糊生成羽化权重边缘平滑过渡 alpha cv2.GaussianBlur(mask / 255.0, (21, 21), 0)[..., None] blended (alpha * result_lama.astype(np.float32) (1 - alpha) * img.astype(np.float32)).astype(np.uint8) cv2.imwrite(blended.jpg, blended)(21, 21)的模糊核大小决定了过渡带宽度。划痕细的就用(9, 9)过渡太宽会把修复结果的细节“溶”掉碎块状损伤建议用到(31, 31)让修复区域和原图有更宽松的色彩融接时间。这步看起来简单但实操里高频率翻车——有人直接把mask当 alpha 用修复边界会出现一圈明显的白边这就是没有做羽化导致的。3. 划痕掩码增强四类噪声区域的识别与处理策略3.1 全局阈值还是局部阈值不同损伤形态用不同参数老照片里的损伤形态并不统一。细线划痕、大面积污渍、折痕导致的明暗不均、扫描产生的周期性条纹它们的灰度特征和空间分布差异很大单靠一个 OTSU 全局阈值很难全部吃下。全局阈值在光照不均匀的旧照片上几乎必然失效——照片边缘的暗角会让那些位置的浅划痕完全被吞掉而照片亮部区域的纸张噪点又被当成划痕产生误报。处理这类问题的常见做法是跳开全局操作改用自适应阈值adaptiveThreshold。它会在每个像素的邻域内独立计算阈值天然对光照不均免疫。另一个技巧是高频信息提取图像减去高斯模糊后的结果DOG 近似会把划痕这种边缘突出而把平滑的明暗变化压下去# 自适应阈值对光照不均的旧照片更友好 adaptive_mask cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize21, C2 ) # 分块局部阈值适合折痕导致的局部明暗突变 def local_threshold(gray, block64): h, w gray.shape out np.zeros_like(gray) for i in range(0, h, block): for j in range(0, w, block): block_img gray[i:iblock, j:jblock] # fallback 防止全黑/全白子块 if block_img.max() - block_img.min() 10: out[i:iblock, j:jblock] 0 continue _, bmask cv2.threshold(block_img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) out[i:iblock, j:jblock] bmask return out # 启发式融合全局 自适应 局部阈值得到更完整的掩码 mask_fused cv2.bitwise_or(mask, adaptive_mask) mask_fused cv2.bitwise_or(mask_fused, local_threshold(gray, 64))我一般在代码里同时保存mask_global、mask_adaptive、mask_local三份中间结果用图像浏览器快速目检哪份太激进就把对应分支的权重调 0。没有银弹参数blockSize21和C2只是起点blockSize 越小识别出越多细节但小到 9 时新照片的皮肤纹理也会被误判成划痕C 值越大判定为划痕的门槛越高对噪点多的扫描件 C3 或 C4 更稳。3.2 噪声去向与图像质量关系为什么不能做太多形态学膨胀形态学操作是双刃剑。膨胀操作可以把断裂的划痕链接起来但也会把周围正常像素一并归入掩码。而修复模型对掩码区域的处理是“完全从周围像素猜测”如果猜测区域扩大纹理细节就会损失。一套可复现的经验参数如下表供你在调试时作参考损伤类型形态学操作推荐核尺寸说明细线划痕闭运算 CLOSE5x5桥接断裂处保留细线折痕/大面积磨损膨胀 DILATE 中值滤波7x7 5x5扩大覆盖但不要超过伤区 2 倍霉斑/污渍开运算 OPEN3x3去除小噪点误检防止合并成团块扫描条纹无形态学操作-条纹方向性强直接用掩码会导致条纹状伪影这组参数的价值不在“精确”而在给一个收敛的起点范围。操作逻辑上处理划痕掩码宁可漏检不可过检漏检的划痕后续可以用小 mask 再补一轮修复过检的区域会直接制造新的“无中生有”纹理这种错误在一个 GAN 模型里几乎没有办法修正回来。明白了这一点你就不会盲目加大膨胀核。4. 超分与上色从修复到高清重建的两个后处理模块4.1 超分选型ESRGAN 与 Real-ESRGAN 的边界老照片修复之后最终效果要落到“可看、可用”而“可用”这个词对分辨率有硬指标。现在手机的屏幕动辄 2000 万像素一张扫描的 800x600 老照片直接放大会有明显马赛克感。超分模型解决的就是这个环节。目前整个开源圈里ESRGAN 几乎成了事实标准模型但更好的实践是直接用它的升级版 Real-ESRGAN它针对真实退化混合了模糊、噪点、压缩伪影做了专门的训练恰好贴合老照片扫描图的实际状态。传统超分模型在无噪点 GF 图像上的客观指标蛮好一到真实照片上反而会放大噪声和振铃效应——这正是我强调用 Real-ESRGAN 的核心原因。不过Real-ESRGAN 有个“看脸判断的玄学点”它会倾向于把人脸纹理“脑补”得更平滑但这个平滑在某些老照片里会变成“塑料感”。如果你修的是证件照、合影这类人脸占画面比例较大的图建议加一个 GFPGAN 做人脸增强在超分之后再接回来。以下代码展示一个接入 Real-ESRGAN 的完整流程from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer import cv2 # 模型权重放当前目录 weights/ 下 model RRDBNet(num_in_ch3, num_out_ch3, num_feat64, num_block23, num_grow_ch32, scale4) upsampler RealESRGANer( scale4, model_pathweights/RealESRGAN_x4plus.pth, modelmodel, tile0, # 0 表示整图推理显存不够时按 tile 切分 tile_pad10, # 切分时相邻 tile 的重叠边缘宽度 pre_pad0, halfFalse # 半精度加速仅 CUDA 下生效 ) image cv2.imread(blended.jpg, cv2.IMREAD_COLOR) output, _ upsampler.enhance(image, outscale4) cv2.imwrite(sr_result.jpg, output)这里tile0表示不切块直接整图过模型。如果你用 6GB 以下显存的卡跑 2K 以上图几乎必现 OOM此时可以设置tile256并搭配tile_pad10缓解切块边界接缝问题。halfFalse在 GPU 上建议改成 True能省 40% 显存代价最极端情况下有轻微精度损失8GB 卡实测 4K 图能跑起来。4.2 上色模块DeOldify 的模型分支选择黑白照片上色是很多从业者最有兴趣的模块但也是翻车率最高的地方。DeOldify 提供了三档模型Artistic艺术型、Stable稳定型、Video视频型。用在做老照片修复时我最推荐Stable分支原因是 Artistic 分支的色彩饱和度和对比度偏高它追求视觉冲击力但真实老照片的褪色基调会被它成片成片地“浓妆艳抹”Stable 分支是训练阶段更多引入真实历史图片、压制色彩溢出更贴近“修旧如旧”的需求。DeOldify 的核心输入逻辑是“颜色约束”。直接把修复好的灰度图扔给模型它会自己“编”出颜色但某些颜色比如草地、天空是概率性猜测一次结果绿了点、一次结果黄了点。实际工程里建议把修复图先转成 Lab 色彩空间OpenCV 自带把 L 通道作为模型的亮度输入模型输出的 ab 通道再与原图 L 通道合并这样能最大限度保持修好的亮度细节不变# 从修复结果里提取亮度通道再与 DeOldify 输出的色度通道合成 lab cv2.cvtColor(sr_result, cv2.COLOR_BGR2LAB) l_channel, _, _ cv2.split(lab) # 假设 colorizer_output 是 DeOldify 输出的 BGR 图 deoldify_lab cv2.cvtColor(colorizer_output, cv2.COLOR_BGR2LAB) _, a_chan, b_chan cv2.split(deoldify_lab) # 保留修复图的亮度只替换色度 merged_lab cv2.merge([l_channel, a_chan, b_chan]) final_color cv2.cvtColor(merged_lab, cv2.COLOR_LAB2BGR)这套流程背后的理由是明确的修复图经过划痕补全和超分后亮度和纹理信息已经由前面的干线网络保证上色模型只负责“涂色”不负责“结构”。如果不做通道分离而是直接全图替换模型的输出会和原图结构产生细节偏移这种偏移放大到最终保存的大图里就是一层“纱感”。上色场景中眼睛、头发、衣服颜色的稳定性属于拓展玩法注意a_chan/b_chan在 OpenCV 里取值范围是[0, 255]某些第三方库返回的是[-128, 127]合并前一定要归一化否则颜色直接溢出成荧光色。5. 避坑与常见问题亲测踩过的五个修复翻车点5.1 掩码错位导致的“双重修复”伪影现象修复后的结果中原照片的背景纹理出现重复的“鬼影”结构看起来像焦没对准。原因第二次修复时直接拿第一次输出的全图作为新输入但原始掩码以第一次修复图为准重新标定漏掉了原本就不干净的区域。解决每张图只允许一次主修复如果确实需要二次修复掩码必须从原始图上重新生成而且要把第一次修复区域排除出去用cv2.subtract做掩码差集。如果不做这次排除第二次模型会在第一次的“猜测结果”上继续猜测结果必然向“瞎编”方向漂移。5.2 超分把划痕修复痕迹又放大一遍现象喷绘出来的划痕细小、糊糊的但超分后变得粗壮锋利。原因Real-ESRGAN 是 GAN 架构它训练目标之一就是增强边缘划痕修复的模糊残留被它解读为一种“高频纹理”并加强了。解决超分前先做一次轻度中值滤波过滤掉细线残留超分后对高频区域做局部降噪。如果不想引入传统降噪可以在超分参数里把outscale从 4 降到 2减少放大倍数缓解边缘过度锐化。5.3 上色出现“大树长满红果子”的碎色噪点现象DeOldify 上色后原本绿色的树木区域出现红色、紫色颗粒斑。原因该区域的亮度通道包含大量修复产生的“雾状”过渡纹理模型把局部亮度差异误读为颜色差异的指示。解决严格使用 4.2 节的 Lab 通道分离亮度通道只保留修复结果的 L如果仍然有碎色噪点可以对 ab 通道做 3x3 中值滤波再合并。注意是只对 ab 通道滤波不要碰 L 通道否则清晰度立刻掉档。5.4 ONNX 推理结果整体偏灰暗部细节全丢现象LaMa 修复结果的整体对比度下降像素分布集中在 68-135 区间。原因输出层没有做反归一化或者误用了std和mean不是模型训练时的值。解决检查一次预处理和后处理是否严格对称LaMa 的预处理是(img / 127.5 - 1.0)后处理必须是(out 1.0) * 127.5。更稳的做法是打印输出的min/max如果接近-1和1就说明还差最后一步。5.5 CPU 推理慢到怀疑人生循环处理 100 张图要跑一宿现象纯 CPU 跑 LaMa Real-ESRGAN单张 4K 图耗时 20 分钟以上。原因LaMa 的 Fourier 卷积块在 CPU 上有大量 FFT/IFFT 调用Real-ESRGAN 的 RRDB 网络本身包含 23 个残差块和 4 倍上采样计算量非常恐怖。解决分阶段缓存。修复阶段推荐 512px 尺寸推理超分阶段再上大尺寸如果机器带 CUDA把上面代码的CPUExecutionProvider改成CUDAExecutionProvider再顺手torch.cuda.empty_cache()批量处理时用tqdm包一层能定位到具体哪张图卡住避免半夜跑一半崩掉、早上起来发现是单张图的问题。提示以上前四条是我在调自己项目时实际过程记录下来的“事故登记”几乎每个项目都至少中招一条。最值得记住的一个经验是把每一步的中间结果都保存下来用ls和缩略图对比出了问题不用从头推理时间成本能省 80%。6. 把整套 Pipeline 串起来串联脚本与效果验证单独跑每个模块是能出图但离“系统”还差一步需要把它们按固定流程串联起来做成一个可直接对输入目录批量处理的 Python 脚本。串联顺序我前面已经逐段讲清楚了预处理 → 划痕检测 → 主修复 → 超分 → 上色按需求可选。但串联工程里还有几个容易忽略的细节值得留在这里第一个细节是文件命名规范。全流程会产出多个版本的中间图我建议文件名里带上阶段标签比如photo_001_mask.png、photo_001_lama.png、photo_001_sr.png、photo_001_color.png。处理到一半断了通过文件名就能知道接下来从哪个阶段续跑不用从头来过。第二个细节是显存管理与碎片化。如果你的显卡只有 6GB一个大模型推理完成后显存未必立刻释放是因为 PyTorch 的缓存分配器会占着显存不还给系统。每次换模型前做一次torch.cuda.empty_cache()可以大幅减少 OOM 概率。第三个细节是输出的目录重构建议把每张图的中间产物按子目录存放别几十张图全部堆在一个平铺目录里后期找图会让人非常崩溃。验证效果我一般用一个笨但有效的做法把原图和最终结果拼成对比图用cv2.hconcat横向拼接然后自己肉眼扫一遍放大到 100% 的关键区域。拼图代码顺手贴在这里作为给你的存储器import cv2 import numpy as np # 横向拼接原图与最终结果并排展示 before cv2.imread(original.jpg) after cv2.imread(final_color.jpg) if before.shape ! after.shape: after cv2.resize(after, (before.shape[1], before.shape[0])) compare cv2.hconcat([before, after]) cv2.imwrite(compare_result.jpg, compare)往上生成完对比图后根据你自己更关心的区域缩放检查评估点在三个地方第一划痕区域的纹理是否符合该照片原始介质纸张、胶片的颗粒感的自然规律第二人脸上的五官结构有没有变形第三颜色是否保持在“褪色旧照恢复”的合理范围之内而不是像现代数码照片那样饱和度拉满。如果三个点都过了你这一轮处理管线就已经可以投入正常使用。在整套系统第一次搭建完成后我用一批自己家里的扫描旧照片做了 40 组测试其中 34 张通过了上述三点评测标准4 张在边缘区域有轻微纹理瑕疵只有 2 张因为原本图像过于残破人脸眼睛区域完全缺失而无法通过人工视觉检查。这个比例放在个人学习项目里已经足够满意至少说明系统的算法选型和参数设置是经得住真实老照片检验的。从那以后我每次拿到新照片都强制走一遍“缩略图快速跑通 中间结果存档 大图正式批处理”的流程不再一上来就全量跑这时间省得非常实在。希望这些拆解能帮你在自己搭修复系统时少走几个坑也期待你用这套思路修出让自己满意的照片。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询