☰
DeOldify图像上色器源码解析:本地部署与批量处理实战
2026/10/3 20:58:55 网站建设 项目流程

简介:这份源码面向深度学习与图像处理方向的开发者、学生及研究者,提供一套可直接运行的DeOldify黑白照片上色Web应用实现,帮助理解生成式模型在图像着色任务中的工程落地方式。压缩包共139个文件,约4.28MB,以103个Python脚本为核心,承担模型加载、推理与Gradio界面逻辑;另有21个pyc字节码、2个C++与2个CUDA源文件用于算子加速,配合3个模板、4张PNG示例图及yml、json、license等配置与许可文件。已有207人学习下载。读者可从中获得完整的项目目录结构、fastai模型调用与上色推理流程、CUDA自定义算子的实现参考,以及基于Gradio的交互界面搭建思路,适合作为课程设计、毕业设计或图像修复类应用的二次开发基础。

1. 黑白照片上色这件事,DeOldify 源码到底能帮你省掉多少事

手里有一批老照片,想批量上色,用在线工具一张张传?几十张还能忍,几百上千张就是纯体力活。更麻烦的是,很多在线服务会压缩画质、限制分辨率,甚至把照片传到别人服务器上。基于深度学习的 DeOldify 图像上色器设计源码,解决的正是这个场景:把上色模型部署到本地,用 Python 跑推理,用 Gradio 搭一个可视化界面,想批量就批量,想调参就调参。这套源码适合两类人:一是想拿现成项目练手深度学习部署的开发者,二是真有老照片修复需求、又不想把数据交出去的人。它不是一个从零训练模型的教程,而是一个已经能跑起来的上色器工程,核心价值在于把 DeOldify 的推理流程、模型加载、界面交互打包成了可复现的代码结构。下面我按实际拆包和跑通的顺序,把这份源码讲透。

2. DeOldify 上色原理与源码结构:为什么它比普通 GAN 稳

2.1 从灰度到彩色的技术路线选择

图像上色本质上是一个病态问题:同一张灰度图,可以对应无数种合理的彩色结果。早期做法是让用户涂色块,后来变成基于参考图迁移颜色,再到现在用生成对抗网络直接预测。DeOldify 属于第三类,但它和普通 GAN 上色最大的区别在于训练策略。普通 GAN 容易出现颜色溢出、肤色发绿、天空发紫这类翻车现场,DeOldify 通过引入 NoGAN 训练思路和感知损失,把生成器的输出约束在更自然的色彩分布上。

源码里通常包含三个关键组件:生成器网络、判别器网络(训练阶段用)、以及推理封装。生成器主干常见的是 ResNet 或 U-Net 变体,负责从灰度图回归出 ab 色度通道,再和原始 L 通道合并回 RGB。判别器只在训练时参与,推理时完全不加载,所以如果你只是拿源码跑上色,显存占用主要来自生成器。这一点对部署很关键:很多人以为要跑 GAN 就得同时加载两个网络,其实推理阶段只需要生成器权重。

选型上,DeOldify 适合对自然度要求高、对速度要求相对宽松的场景。如果你要的是实时视频上色,那得换更轻量的模型;如果是单张或小批量老照片修复,DeOldify 的质感明显更稳。源码里一般会提供多个预训练权重,比如 Artistic 和 Stable 两个版本,前者色彩更浓、适合风景,后者肤色更准、适合人像。这个区分在参数说明里会再展开。

2.2 源码目录拆解与依赖清单

拿到一份 DeOldify 上色器源码,先别急着跑 main。我一般会先看目录结构,判断它是完整工程还是只抽了推理部分。典型结构如下:

deoldify-colorizer/ ├── app.py # Gradio 界面入口 ├── inference.py # 推理封装,核心上色逻辑 ├── models/ │ ├── generator.py # 生成器网络定义 │ └── unet.py # 编码器-解码器结构 ├── weights/ │ └── colorize_stable.pth ├── utils/ │ ├── image_utils.py # 图像预处理、后处理 │ └── colorize.py # 上色流程编排 ├── requirements.txt └── README.md

这个结构里,inference.py和utils/colorize.py是你要重点读的。前者负责加载权重、切换设备(CPU/GPU)、设置输入尺寸;后者负责把 PIL 图像转成模型需要的张量,再把输出转回图像。很多人跑不通,问题往往出在预处理没对齐:训练时用的归一化参数和推理时不一致,颜色就会偏。

依赖方面,核心是 PyTorch、torchvision、Pillow、numpy,界面用 Gradio。版本上,PyTorch 建议 1.10 以上,Gradio 用 3.x 或 4.x 都行,但要注意 Gradio 4 的 API 有变化,老代码里的gr.Interface参数名可能对不上。如果你环境里已经装了其他深度学习框架,注意别和 PyTorch 的 CUDA 版本冲突。常见做法是单独建一个 conda 环境:

conda create -n deoldify python=3.9 conda activate deoldify pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt

这里指定 Python 3.9 是因为部分老版本 torchvision 对 3.10+ 支持不完整,容易在加载权重时报UnpicklingError。CUDA 版本按你显卡驱动选,没有 GPU 就用 CPU 版 torch,只是单张 512 分辨率图片可能要等十几秒。

2.3 模型加载与推理流程的关键参数

推理流程可以拆成四步:读图、预处理、前向传播、后处理。每一步都有参数会影响最终效果。

import torch from PIL import Image from torchvision import transforms from models.generator import Generator device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 加载生成器,注意 weights_only 参数在 PyTorch 2.6+ 默认变为 True model = Generator() state_dict = torch.load("weights/colorize_stable.pth", map_location=device) model.load_state_dict(state_dict) model.to(device) model.eval() # 预处理:统一尺寸、转张量、归一化 preprocess = transforms.Compose([ transforms.Resize((512, 512)), transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) img = Image.open("old_photo.jpg").convert("RGB") input_tensor = preprocess(img).unsqueeze(0).to(device) with torch.no_grad(): output = model(input_tensor) # 后处理:反归一化、转回 PIL output = output.squeeze(0).cpu() output = (output * 0.5 + 0.5).clamp(0, 1) result = transforms.ToPILImage()(output) result.save("colorized.jpg")

这段代码里,Resize((512, 512))是硬约束。DeOldify 的生成器对输入尺寸敏感,非正方形输入会被拉伸变形,人脸会变胖。常见做法是保持长宽比,短边缩到 512,长边按比例算,再中心裁剪或 padding。源码里如果没做这步,你得自己补。

Normalize的 mean 和 std 都是 0.5,这是把像素从 [0,1] 映射到 [-1,1]。如果训练时用的是 ImageNet 的均值方差,推理时也必须一致,否则颜色整体偏灰或偏饱和。这个参数在utils/image_utils.py里通常有定义,改之前先确认训练配置。

model.eval()不能省。生成器里如果有 BatchNorm 或 Dropout,训练模式和推理模式输出完全不同。漏掉这行,同一张图每次跑出来的颜色都不一样,这就是典型的玄学翻车。

torch.no_grad()关掉梯度计算,显存占用能降三分之一左右。批量推理时尤其重要,不然几张图就把显存吃满。

3. Gradio 界面搭建与批量上色:从单张到队列

3.1 用 Gradio 包一个可交互的上色器

源码里带 Gradio 界面,说明作者考虑到了非技术用户。Gradio 的好处是几行代码就能把函数变成网页应用,支持上传图片、滑动条调参数、队列处理。一个最小可用的上色界面长这样:

import gradio as gr from inference import colorize_image def process(image, render_factor): # render_factor 控制推理分辨率,值越大细节越多但越慢 return colorize_image(image, render_factor=render_factor) demo = gr.Interface( fn=process, inputs=[ gr.Image(type="pil", label="上传黑白照片"), gr.Slider(minimum=7, maximum=45, value=35, step=1, label="渲染因子") ], outputs=gr.Image(type="pil", label="上色结果"), title="DeOldify 图像上色器", description="上传灰度或老照片,调整渲染因子后点击提交" ) demo.launch(server_name="0.0.0.0", server_port=7860)

render_factor是 DeOldify 里比较关键的参数,它决定推理时把图像缩放到多大。值越小速度越快但细节丢失,值越大细节保留越好但显存和时间上升。人像一般 30-35,风景可以到 40 以上。源码里如果把这个参数写死了,你可以从inference.py里把它提出来做成滑动条。

gr.Image(type="pil")指定输入输出都用 PIL 对象,省去手动转换。如果你用 numpy 数组,注意通道顺序是 RGB 还是 BGR,搞反了红蓝互换,人脸变阿凡达。

server_name="0.0.0.0"让局域网内其他设备也能访问,方便你在手机上测试。生产环境记得加认证,Gradio 支持auth参数。

3.2 批量处理与队列机制

单张上色只是玩具,真正干活要批量。Gradio 的队列机制可以防止并发请求把显存打爆:

demo = gr.Interface(...) demo.queue(max_size=8, concurrency_count=1) demo.launch()

concurrency_count=1表示同时只处理一个请求,其他排队。GPU 显存小的话这个值必须设成 1,不然两个请求同时进来直接 OOM。max_size是队列上限,超过就拒绝,避免无限堆积。

如果要处理整个文件夹,更稳的做法是绕过界面,直接写脚本:

import os from PIL import Image from inference import colorize_image input_dir = "old_photos" output_dir = "colorized" os.makedirs(output_dir, exist_ok=True) for filename in os.listdir(input_dir): if not filename.lower().endswith((".jpg", ".jpeg", ".png")): continue img_path = os.path.join(input_dir, filename) img = Image.open(img_path).convert("RGB") result = colorize_image(img, render_factor=35) result.save(os.path.join(output_dir, filename)) print(f"done: {filename}")

这个脚本里,convert("RGB")很重要。有些老照片是 CMYK 或灰度模式,不转 RGB 直接送进模型会报通道数不匹配。render_factor统一设 35 是折中值,你可以按图片类型分组处理:人像一组用 32,风景一组用 40。

批量处理时建议加个异常捕获,单张失败不影响整体:

try: result = colorize_image(img, render_factor=35) result.save(os.path.join(output_dir, filename)) except Exception as e: print(f"failed: {filename}, reason: {e}")

常见失败原因包括:图片损坏、尺寸过小(小于 64 像素)、显存不足。记录失败文件名,回头单独处理。

3.3 参数调优与效果对比

render_factor之外,还有几个参数影响效果。源码里如果有watermark选项,那是给输出图加暗水印的,修复老照片时记得关掉。post_process参数控制是否做锐化和色彩增强,默认开启,但有些图锐化过度会显得假,可以关掉对比。

我一般会拿同一张图跑三组参数做对比:

参数组render_factorpost_process适用场景
A30True人像、肤色要求高
B38True风景、建筑
C42False细节丰富、不想过度处理

跑完把三张图并排看,选最自然的。注意,DeOldify 对黑白分明、对比度高的原图效果最好;如果原图本身发灰、噪点多,先做去噪和对比度拉伸再上色,效果提升明显。这一步在utils/image_utils.py里可以加,用 PIL 的ImageEnhance就行。

4. 避坑与排查:跑 DeOldify 源码时最容易翻车的五个地方

4.1 现象:加载权重报 UnpicklingError 或 KeyError

原因通常是 PyTorch 版本不匹配。PyTorch 2.6 之后torch.load的weights_only默认变成 True,老权重文件里如果有自定义类,就会反序列化失败。另一个原因是权重文件和模型定义对不上,比如用 Artistic 的权重加载 Stable 的生成器结构。

解决:先试torch.load(path, map_location=device, weights_only=False)。如果还报 KeyError,打印state_dict.keys()和model.state_dict().keys()对比,看缺了哪层。常见的是生成器最后一层卷积的通道数不一致,这时候要么换权重,要么改模型定义。

4.2 现象:上色结果整体偏灰或偏绿

原因一般是归一化参数不一致。训练时用的 mean/std 和推理时不同,或者后处理反归一化算错了。另一个可能是输入图像本身是 RGBA 四通道,送进模型前没转 RGB,多出来的 alpha 通道被当成颜色通道处理。

解决:检查utils/image_utils.py里的Normalize参数,和训练配置文件对齐。输入图统一convert("RGB")。如果还偏,手动在输出后做一次白平衡,用PIL.ImageOps.autocontrast拉一下。

4.3 现象:CUDA out of memory

原因:render_factor设太大,或者批量处理时没有及时释放中间变量。DeOldify 生成器在 512 分辨率下单张推理大约占 2-3GB 显存,如果同时跑多个请求,很快爆。

解决:把render_factor降到 30 以下,或者在推理代码里加torch.cuda.empty_cache()。批量处理时用for循环逐张跑,不要试图一次把所有图堆成 batch。如果显存实在小,切 CPU 推理,只是慢。

4.4 现象:Gradio 界面能打开但上传图片后没反应

原因:Gradio 版本差异导致回调函数参数不匹配。Gradio 4 里gr.Interface的inputs和outputs行为有变化,老代码可能用了已废弃的参数。另一个原因是函数内部抛异常但被 Gradio 吞了,界面只显示错误不显示堆栈。

解决:在process函数里加try/except并print异常,看终端输出。确认 Gradio 版本,pip show gradio,如果是 4.x 而代码是按 3.x 写的,要么降级pip install gradio==3.50.2,要么按 4.x 文档改。

4.5 现象:上色后人物脸部出现色块或颜色溢出

原因:生成器对肤色区域的预测不稳定,尤其是原图人脸区域对比度低、细节少的时候。另一个原因是render_factor太低,模型没有足够分辨率去区分皮肤和背景。

解决:人像图把render_factor提到 35 以上,并开启post_process。如果还有色块,先用人脸检测把脸部裁出来单独上色,再贴回去。源码里如果没有这个逻辑,可以用face_recognition或 OpenCV 的 Haar 级联补一个。

5. 进阶技巧:用 ONNX 导出提速与分辨率分块推理

5.1 导出 ONNX 模型绕过 PyTorch 依赖

PyTorch 推理在 CPU 上偏慢,如果部署环境不方便装完整 PyTorch,可以把生成器导出成 ONNX,用 onnxruntime 跑。导出脚本如下:

import torch from models.generator import Generator model = Generator() state_dict = torch.load("weights/colorize_stable.pth", map_location="cpu") model.load_state_dict(state_dict) model.eval() dummy_input = torch.randn(1, 3, 512, 512) torch.onnx.export( model, dummy_input, "deoldify_generator.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch", 2: "height", 3: "width"}, "output": {0: "batch", 2: "height", 3: "width"}}, opset_version=11 )

dynamic_axes让导出的模型支持可变尺寸输入,这样你就不用固定 512 了。opset_version=11兼容性较好,onnxruntime 1.12+ 都支持。导出后用onnxruntime.InferenceSession加载,CPU 上速度大概能提升 20%-30%,显存占用也更低。

注意,导出前必须model.eval(),否则 BatchNorm 的统计量不对,输出会偏。导出后拿同一张图对比 PyTorch 和 ONNX 的输出,差异应该在 1e-4 以内,超过说明有层没对齐。

5.2 高分辨率图片的分块推理

DeOldify 原生支持的最大分辨率有限,直接跑 4K 图会爆显存。常见做法是分块:把大图切成有重叠的小块,逐块上色,再拼回去。重叠区域用加权融合,避免接缝。

import numpy as np from PIL import Image def colorize_large(img, tile_size=512, overlap=64): w, h = img.size result = Image.new("RGB", (w, h)) for y in range(0, h, tile_size - overlap): for x in range(0, w, tile_size - overlap): box = (x, y, min(x + tile_size, w), min(y + tile_size, h)) tile = img.crop(box) colorized_tile = colorize_image(tile, render_factor=35) result.paste(colorized_tile, box[:2]) return result

这个简化版直接 paste,接缝处会有痕迹。要消除接缝,得在重叠区做线性渐变融合:对每个像素,根据它到块边界的距离算权重,两块结果加权平均。代码稍长,但效果值得。我一般对超过 2000 像素的图才用分块,小图直接跑更快。

分块还有个坑:每块单独上色,颜色可能不连贯,天空一块偏蓝一块偏紫。解决办法是先用整图跑一个低分辨率的色彩参考,再对每块做颜色迁移。这个属于进阶操作,源码里不一定有,但值得自己补。

5.3 我踩过的坑和固定习惯

最早跑这份源码时,我没注意render_factor和显存的关系,直接设了 45 跑一张 4K 图,结果 CUDA OOM 三次,重启内核两次。后来养成习惯:先拿一张小图测通流程,确认权重加载、预处理、后处理都对齐,再上批量。每次改render_factor或换权重,都强制跑一遍对比图,确认颜色没偏。还有,torch.load的weights_only参数在 PyTorch 2.6 之后一定要显式写,不然老权重直接报错,这个坑我替你们踩过了。从那以后我每次部署新环境,都先跑一个最小推理脚本验证,再动界面和批量逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询