简介:这份资源面向希望上手图像自动着色与深度先验学习的 Python 开发者与计算机视觉学习者,核心是两套预训练着色模型(eccv16 与 siggraph17)的推理代码,可对黑白照片实时上色,并支持用户引导式着色。包内共 23 个文件,以 py 脚本与 pyc 缓存为主,辅以 jpg、jpeg、png 示例图片及 license、md、txt 等说明文档,压缩包约 4.47MB,体量轻便,便于快速部署与二次开发。已有 680 人学习下载。读者可借助 demo_release.py 直接运行命令行着色流程,理解从 RGB 转 Lab、缩放至 256×256、着色后与原分辨率拼接再转回 RGB 的完整预处理与后处理链路;colorizers 模块中的 base_color、eccv16、siggraph17 与 util 等文件则展示了模型加载与推理封装方式,配合 imgs 与 imgs_out 中的输入输出样例,可对照验证着色效果,适合作为深度着色方向的入门实践与实验基线。
1. 黑白照片自动上色:从“能跑”到“敢用”之间差了什么
手里有一批老照片,或者从档案库里扒出来一堆灰度扫描件,想批量变成彩色——这个需求在修复、电商老图翻新、影视素材预处理里非常常见。使用深度神经网络的自动着色,说白了就是让模型学会“看到灰度像素,猜出对应的 a、b 色度值”,把 L 通道保留、ab 通道预测出来再拼回 RGB。它解决的不是“艺术创作”,而是批量化、可复现、色彩不飘的上色问题。适合两类人:一类是刚配好vscode python 环境、想找一个完整python 代码练手的;另一类是有实际修复需求、想评估这套方案到底能不能替代手工上色的从业者。彩色图像着色这件事,模型选型、色彩空间、损失函数三处没对齐,出来的结果就是“灰蒙蒙”或者“满脸橘色”,这篇把这条链路拆开讲。
2. 彩色图像着色的技术底座:为什么是 Lab 而不是 RGB
2.1 从“预测像素”到“预测色度”的范式转换
早期做法是直接让网络输出 RGB 三通道,结果普遍发灰。原因很直接:RGB 三个通道高度耦合,亮度信息混在三个通道里,网络要同时学“这里多亮”和“这里什么颜色”,任务太重。主流方案改用Lab 色彩空间,把图像拆成 L(亮度)和 a、b(两个色度轴)。输入只给 L,网络只负责预测 a、b,亮度信息原样保留。这样任务从“重建整张图”降级成“预测两个通道”,收敛快、颜色也更干净。
这个思路在基于深度学习卷积神经网络的上色论文里基本是共识。网络结构上,编码器负责把 L 压成特征,解码器再上采样回原尺寸,中间常接 skip connection 保留边缘。也有用 U-Net 变体的,本质一样:输入单通道,输出双通道。
2.2 损失函数:L1 管结构,分类管颜色
只用 L1 或 L2 损失,模型会倾向于输出“平均色”——也就是灰褐色,因为它在像素级误差上最安全。解决办法是把 ab 空间量化成 313 个色块(bin),把回归问题转成分类问题,用交叉熵训练,再配合 L1 做结构约束。这就是经典实现里的做法:criterion = CrossEntropyLoss() + λ * L1Loss()。
参数上,λ 一般取 0.5 到 1.0。太小颜色饱和但结构糊,太大结构清晰但颜色灰。我一般从 0.5 起步,看验证集的上色饱和度再调。
2.3 数据准备:ImageNet 就够,但要注意灰度化方式
训练数据不需要专门的“灰度-彩色”配对,任意彩色图都能用:读进来转 Lab,取 L 当输入,ab 当标签。常见做法是用 ImageNet 或 COCO 的子集。这里有个容易翻车的点:灰度化必须用 Lab 的 L 通道,不能用cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),后者是加权平均,和 Lab 的 L 不完全等价,会导致训练和推理时的输入分布不一致。
import cv2 import numpy as np def load_image(path, size=256): img = cv2.imread(path) # BGR img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (size, size)) lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) # 转 Lab L = lab[:, :, 0] # 亮度通道 ab = lab[:, :, 1:] # 色度通道 L = L.astype(np.float32) / 255.0 # 归一化到 [0,1] ab = ab.astype(np.float32) / 128.0 # ab 范围约 [-128,127] return L, ab这段代码的关键在最后两行归一化:L 除以 255 落到 [0,1],ab 除以 128 落到约 [-1,1],这是大多数实现的标准做法。size=256是训练分辨率,推理时可以改大,但要注意网络是全卷积的,尺寸不固定。如果显存吃紧,降到 128 也能跑,只是细节会丢。
3. 用 PyTorch 搭一个能跑的上色网络:结构、训练、推理
3.1 网络结构:编码器下采样 + 解码器上采样
下面是一个可以直接跑的最小实现,编码器用几层卷积加 stride 下采样,解码器用转置卷积上采样,最后输出 313 维的分类 logits。
import torch import torch.nn as nn class ColorNet(nn.Module): def __init__(self, num_bins=313): super().__init__() # 编码器:输入 1 通道 L,逐层下采样 self.encoder = nn.Sequential( nn.Conv2d(1, 64, 3, stride=2, padding=1), nn.ReLU(), nn.Conv2d(64, 128, 3, stride=2, padding=1), nn.ReLU(), nn.Conv2d(128, 256, 3, stride=2, padding=1), nn.ReLU(), ) # 解码器:上采样回原尺寸 self.decoder = nn.Sequential( nn.ConvTranspose2d(256, 128, 3, stride=2, padding=1, output_padding=1), nn.ReLU(), nn.ConvTranspose2d(128, 64, 3, stride=2, padding=1, output_padding=1), nn.ReLU(), nn.ConvTranspose2d(64, num_bins, 3, stride=2, padding=1, output_padding=1), ) def forward(self, x): feat = self.encoder(x) out = self.decoder(feat) # [B, 313, H, W] return outnum_bins=313是 ab 空间量化后的色块数,这个数字来自经典论文的量化方案,不要随意改,改了要和量化表对应。output_padding=1是为了让上采样后的尺寸和下采样前对齐,不加的话输出会比输入小一圈,拼接时会报错。
3.2 训练循环:分类损失 + L1 结构约束
from torch.utils.data import DataLoader, Dataset class ColorDataset(Dataset): def __init__(self, paths, size=256): self.paths = paths self.size = size def __len__(self): return len(self.paths) def __getitem__(self, idx): L, ab = load_image(self.paths[idx], self.size) return torch.from_numpy(L).unsqueeze(0), torch.from_numpy(ab) def train(model, loader, epochs=10, lr=1e-4): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) opt = torch.optim.Adam(model.parameters(), lr=lr) ce = nn.CrossEntropyLoss() l1 = nn.L1Loss() for epoch in range(epochs): for L, ab in loader: L, ab = L.to(device), ab.to(device) logits = model(L) # [B, 313, H, W] # 把 ab 量化成 bin 索引作为分类标签 target = quantize_ab(ab).long().to(device) loss = ce(logits, target) + 0.5 * l1(logits.softmax(1), ab.permute(0,3,1,2)) opt.zero_grad() loss.backward() opt.step()quantize_ab是把连续的 ab 值映射到 313 个 bin 的函数,实现时用最近邻查表即可。损失里ce管颜色分类,l1管结构,系数 0.5 是经验值。lr=1e-4配 Adam 比较稳,太大容易颜色崩,太小收敛慢。batch size 视显存定,256 分辨率下 16 到 32 都行。
3.3 推理与回填:把预测的 ab 拼回 RGB
def colorize(model, gray_path, size=256): model.eval() L, _ = load_image(gray_path, size) with torch.no_grad(): logits = model(torch.from_numpy(L).unsqueeze(0).unsqueeze(0)) ab = decode_ab(logits) # 从 313 bin 还原成 ab 两通道 lab = np.concatenate([L[:, :, None] * 255, ab], axis=2).astype(np.uint8) rgb = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB) return rgbdecode_ab是量化的逆过程:取 logits 的 argmax 得到 bin 索引,再查表还原成 ab 值。注意 L 要乘回 255 再和 ab 拼接,因为 OpenCV 的 Lab 转换要求 L 在 [0,255]、ab 在 [0,255] 偏移后的范围。这一步范围搞错,出来的图会偏色或者全黑,是新手最常见的翻车点。
4. 上色效果调优:参数、后处理与批量落地
4.1 三个必调参数:温度、饱和度、分辨率
推理阶段有三个参数直接决定观感。温度(temperature)作用在 softmax 上,温度低颜色更确定、更饱和,温度高更柔和但可能发灰,一般 0.5 到 1.0。饱和度增益是在 ab 上乘一个系数,1.0 是原样,1.2 到 1.5 能让颜色更“跳”,但超过 1.5 容易溢出。推理分辨率比训练分辨率大时细节更好,但网络没见过大尺寸,边缘可能出伪影,常见做法是推理用 256 或 512,再和原图做引导滤波融合。
| 参数 | 常用范围 | 调大效果 | 调小效果 |
|---|---|---|---|
| 温度 | 0.5 ~ 1.0 | 颜色柔和、偏灰 | 颜色饱和、对比强 |
| 饱和度增益 | 1.0 ~ 1.5 | 颜色鲜艳 | 颜色寡淡 |
| 推理分辨率 | 256 / 512 | 细节多、伪影风险高 | 稳定但糊 |
4.2 后处理:引导滤波保住边缘
网络输出的 ab 是低分辨率的,直接上采样会有颜色溢出到边缘外。常见做法是用原图的 L 通道做引导图,对 ab 做引导滤波(guided filter),让颜色跟着边缘走。OpenCV 的ximgproc.guidedFilter可以直接用,半径 8、eps 1e-2 是常用起点。这一步做完,头发丝、树枝这类细结构的颜色会干净很多。
4.3 批量处理与工程化注意
批量跑的时候,别一张张读、一张张推理,用 DataLoader 或者自己写个队列,把 IO 和计算重叠起来。另外,输出格式统一成 sRGB,如果下游是印刷或视频,还要考虑色彩管理。文件命名建议保留原文件名加后缀,方便回溯。如果要做成服务,把模型加载一次常驻显存,别每次请求都重新 load,那个开销比推理本身还大。
5. 避坑与排查:上色翻车的五个真实场景
5.1 输出全灰或全褐
现象:推理出来的图几乎没颜色,或者整体偏土黄。原因:损失函数里 L1 权重太大,模型退化成输出平均色;或者训练轮次不够,分类头还没学会。解决:把 L1 系数降到 0.1 到 0.5,增加训练轮次,检查量化表是否正确加载。
5.2 颜色溢出到背景
现象:人物边缘、物体轮廓外出现色块。原因:ab 上采样时用了双线性插值,没有边缘约束。解决:加引导滤波后处理,或者网络里加 skip connection 把浅层特征引到解码器。
5.3 推理尺寸和训练不一致导致报错
现象:换了大图推理,拼接时维度对不上。原因:网络下采样和上采样倍率不匹配,output_padding没设对。解决:确保输入尺寸是 2 的整数次幂倍,或者推理前 resize 到训练尺寸再放大。
5.4 显存爆了
现象:训练到一半 OOM。原因:batch size 太大,或者 313 维输出在 256 分辨率下显存占用高。解决:降 batch size,用混合精度训练(torch.cuda.amp),或者把分辨率降到 128。
5.5 颜色在不同图片间跳变
现象:同一批图,有的偏蓝有的偏黄。原因:推理时没有固定随机种子,或者温度参数在变。解决:推理阶段关掉 dropout、固定温度,确保每次前向一致。
6. 把上色接进实际工作流:一个可复用的验证习惯
模型训完不是终点,怎么判断它“能用”才是。我一般会固定一组回归测试图:人像、风景、建筑、老照片各几张,每次改完参数都跑一遍,肉眼对比。比肉眼更靠谱的是算PSNR 和 SSIM,但这两个指标对上色任务参考有限,因为颜色预测本来就有多解。更实用的是看颜色分布的直方图,和原图对比,如果 ab 直方图整体偏移,说明模型有系统性偏色。
另一个习惯是保留中间结果:L 通道、预测 ab、回填后的 RGB 都存下来。出问题时能快速定位是输入的问题、网络的问题还是后处理的问题。这个“后悔药”机制在调参阶段能省大量时间。
如果要把这套东西产品化,建议把模型导出成 ONNX,推理端用 onnxruntime,CPU 上也能跑到可接受的速度。批量任务用队列加多进程,别用单线程硬扛。最后一句血泪经验:上色模型的泛化能力高度依赖训练数据的分布,如果你的目标场景是特定年代的老照片,最好拿一批同年代的图做微调,否则再好的通用模型也会在肤色和天空色上翻车。希望帮到你。
本文还有配套的精品资源,点击获取