
简介这份资源面向希望上手图像自动着色Colorization的 Python 开发者与深度学习爱好者提供基于深度神经网络的实时用户引导着色实现核心思路是借助学习到的深度先验为灰度图像上色。包内共 23 个文件以 6 个 py 脚本与 5 个 pyc 编译文件为主辅以 jpg、jpeg、png 等示例图片、requirements.txt 依赖清单、README.md 说明文档及 LICENSE 授权文件压缩包约 4.47MB结构紧凑便于快速部署。代码包含 eccv16 与 siggraph17 两套预训练着色器并给出 Lab 空间转换、256x256 缩放、着色后与原分辨率拼接再转回 RGB 的完整预处理与后处理流程可直接对示例图运行验证效果。目前已有 680 人学习下载适合想研究深度先验着色、复现经典模型或将其集成到自身图像处理流程中的读者参考。1. 从一张泛黄老照片说起深度神经网络自动着色到底在做什么翻出家里二十年前的老照片扫描出来全是灰蒙蒙的色调人脸发青、天空发白想上色又不会 PS 分层调色——这是很多人第一次接触「彩色图像着色」的真实动机。所谓自动着色本质是让深度神经网络学会一件事给定一张灰度图或 Lab 色彩空间里的 L 通道预测出对应的 a、b 两个色度通道再拼回 RGB 输出彩色图。它解决的不是「画得像不像」而是「颜色分布合不合理」——草地该偏绿、天空该偏蓝、皮肤该偏暖这些统计规律被网络从海量彩色图里学到了。适合谁想给老照片批量上色的普通用户、做图像修复预处理的算法工程师、以及想拿一个完整可跑通的深度学习项目练手 Python 的入门者。标题里「python 代码 下载」意味着你拿到的不该是一篇论文而是一份能python train.py就跑起来的工程。下面我按自己复现过的路径把选型、代码、参数和翻车点讲清楚。2. 着色网络的选型为什么是 U-Net 加分类头而不是直接回归2.1 回归 a、b 通道的致命问题颜色会「糊成一坨」最直觉的做法是让网络直接回归两个色度值损失用 L2。我最早就是这么干的结果所有输出都偏灰黄饱和度极低。原因在于一张灰度图对应的彩色解本身是多模态的——同一件衣服可以是红的也可以是蓝的L2 损失会把所有可能解取平均平均下来就是灰色。这是着色任务最经典的坑也是「自动着色」和普通超分任务最大的区别。常见做法是把着色转成分类问题把 a、b 空间量化成 313 个色块ab 值网格每个格子代表一种颜色网络对每个像素预测它属于哪个色块的概率分布再用 softmax 交叉熵训练。推理时取概率最大的色块或做加权期望颜色就鲜活了。这个 313 色块方案来自 Zhang 等人 2016 年的论文思路是目前复现最稳的路线。2.2 U-Net 编码器-解码器低层特征别丢着色需要同时理解「这是什么物体」语义决定该用什么颜色和「边界在哪」空间决定颜色不能串。纯分类网络如 VGG 直接接全连接会丢掉空间信息上色后边缘糊。U-Net 的跳跃连接把编码器的高分辨率特征直接接到解码器边界就保住了。编码器我一般用 ResNet-18 或 MobileNetV2 的前几层做骨干前者精度稳后者在 CPU 上也能跑符合热词里 mobilenetv2 代码 的检索习惯。2.3 损失函数分类损失 类别重平衡313 个色块里灰色系低饱和度占了绝大多数像素直接训练网络会倾向全预测灰色。必须给每个色块按其在训练集里的出现频率倒数加权让稀有鲜艳色也有足够梯度。这一步不做你训出来的模型就是「高级灰度图生成器」。import torch import torch.nn as nn # 313 色块量化ab 空间范围 [-110, 110]网格 10x10 步长 # 实际实现常用 313 个聚类中心这里用网格近似说明逻辑 def build_class_weights(ab_pixels, num_bins313, grid10): ab_pixels: (N, 2) 训练集所有像素的 ab 值 返回每个色块的权重用于 CrossEntropyLoss 的 weight 参数 a_idx ((ab_pixels[:, 0] 110) / 220 * grid).long().clamp(0, grid - 1) b_idx ((ab_pixels[:, 1] 110) / 220 * grid).long().clamp(0, grid - 1) bin_idx a_idx * grid b_idx # 简化映射真实实现用 kmeans 中心 counts torch.bincount(bin_idx, minlengthnum_bins).float() weights 1.0 / (counts 1e-6) weights weights / weights.sum() * num_bins # 归一化均值拉到 1 return weights # 训练时 # criterion nn.CrossEntropyLoss(weightclass_weights)上面这段是权重构造的核心逻辑先统计每个色块在训练集里的像素频次取倒数再归一化。参数grid10是简化版真实工程里用 kmeans 在百万级 ab 像素上聚出 313 个中心效果更均匀。weight传给 CrossEntropyLoss 后稀有色的损失会被放大网络不敢再偷懒全预测灰。注意权重别拉太猛均值控制在 1 附近否则训练震荡。3. 用 Python 把着色模型跑起来数据、训练、推理三段代码3.1 数据准备ImageNet 子集 Lab 转换训练数据用彩色图即可不需要成对的灰度-彩色标注因为灰度图是从彩色图算出来的。我一般从 ImageNet 或 COCO 里抽 1~5 万张转成 Lab 空间L 通道做输入ab 通道做标签。import numpy as np from PIL import Image from skimage import color def load_image_as_lab(path, size256): 读图 - 缩放 - 转 Lab - 归一化 img Image.open(path).convert(RGB).resize((size, size)) rgb np.asarray(img) / 255.0 lab color.rgb2lab(rgb) # L: 0~100, a/b: -128~127 L lab[:, :, 0:1] / 50.0 - 1.0 # 归一化到 [-1, 1] ab lab[:, :, 1:3] / 110.0 # 归一化到约 [-1, 1] return L.astype(np.float32), ab.astype(np.float32) # 数据集里 __getitem__ 返回 (L, ab)L 是网络输入ab 是标签size256是权衡显存和细节的常用值想上 512 得把 batch 降到 4 以下。L/50-1把亮度拉到 [-1,1]ab/110把色度拉到约 [-1,1]两个通道量级一致训练更稳。别用rgb2lab后直接除 255那样 a、b 范围不对网络学不动。3.2 训练循环分类头 加权交叉熵import torch from torch.utils.data import DataLoader def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for L, ab in loader: L, ab L.to(device), ab.to(device) # 把 ab 映射到 313 个色块索引 a_idx ((ab[:, 0] 1.0) * 5).long().clamp(0, 9) b_idx ((ab[:, 1] 1.0) * 5).long().clamp(0, 9) target a_idx * 10 b_idx # (B, H, W) pred model(L) # (B, 313, H, W) loss criterion(pred, target) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader) # 典型配置 # optimizer torch.optim.Adam(model.parameters(), lr1e-4) # scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5)target的构造把连续 ab 离散成 0~99 的索引这里用 10x10 简化真实是 313pred是网络输出的 313 维 logits。lr1e-4是 Adam 的稳妥起点太大颜色会崩太小收敛慢。每 10 个 epoch 学习率减半防止后期震荡。total_loss用来观察是否下降如果卡在 5.5 附近不动八成是权重没加或数据没归一化。3.3 推理与上色把预测色度拼回 RGBfrom skimage import color torch.no_grad() def colorize(model, gray_path, device, size256): model.eval() L, _ load_image_as_lab(gray_path, size) L_tensor torch.from_numpy(L).permute(2, 0, 1).unsqueeze(0).to(device) pred model(L_tensor) # (1, 313, H, W) prob torch.softmax(pred, dim1) # 取概率最大的色块中心作为 ab简化用网格中心反算 idx prob.argmax(dim1).squeeze().cpu().numpy() a (idx // 10) / 5.0 - 1.0 b (idx % 10) / 5.0 - 1.0 ab np.stack([a, b], axis-1) * 110.0 lab np.concatenate([L * 50.0 50.0, ab], axis-1) rgb color.lab2rgb(lab) return (rgb * 255).astype(np.uint8)推理时softmax后取argmax是最快的方式但颜色会偏硬想要柔和可以取期望值概率加权求和色块中心。lab2rgb前记得把 L 反归一化回 0~100ab 乘回 110否则颜色全错。这一步是新手最容易翻车的地方——训练对了推理拼错量纲出来一片紫绿。4. 着色效果调优三个必调参数和它们的边界4.1 色块数量313 不是随便定的色块太少比如 64颜色过渡生硬天空会出现色带太多比如 1000每个类样本稀疏训练不收敛。313 是经验值覆盖了人眼可分辨的主要色域。如果你只做老照片肤色、天空、植被为主可以降到 128 并针对性加权训练更快。4.2 温度系数控制颜色饱和度推理时给 softmax 加个温度 Tsoftmax(pred / T)。T 小于 1 会让分布更尖锐颜色更饱和T 大于 1 更柔和偏灰。我一般从 T0.8 起调老照片想要自然就 1.0想要鲜艳就 0.6。这个参数没有训练成本是后期调色最划算的旋钮。4.3 输入分辨率与感受野256 分辨率下网络感受野要覆盖至少 1/4 图宽才能判断「这是天空」。如果骨干太浅比如只堆 4 层卷积大区域颜色会判断错。用 ResNet-18 做编码器时到第 4 个 stage 感受野约 200 像素够用。想上 512 分辨率要么加深网络要么用空洞卷积扩感受野否则边缘颜色会串。参数常用值调大效果调小效果色块数313颜色细腻训练慢过渡生硬易收敛温度 T0.8颜色柔和偏灰颜色鲜艳易过饱和分辨率256细节好显存高快但糊学习率1e-4震荡不收敛收敛慢5. 避坑与排查着色项目里最容易翻车的 5 件事5.1 输出全是灰色饱和度几乎为零现象推理结果像加了淡黄滤镜几乎没有彩色。原因九成是没加类别重平衡权重网络退化成全预测灰色类。解决按第 2.3 节构造class_weights传给 CrossEntropyLoss确认权重均值在 1 附近训练 loss 会先升后降这是正常的。5.2 颜色溢出边界人脸和背景串色现象头发染上了背景的蓝色衣服边缘出现彩色光晕。原因U-Net 跳跃连接没接好或者解码器上采样用了最近邻导致块状伪影。解决检查 skip connection 是否逐层对应上采样改用双线性插值 卷积别直接用nn.Upsample(modenearest)。5.3 训练 loss 不降卡在 5.5 左右现象loss 曲线一条直线模型没学到东西。原因L 通道没归一化到 [-1,1]或者 ab 标签范围算错导致输入输出量级不匹配。解决打印一个 batch 的 L 和 ab 的 min/maxL 应在 [-1,1]ab 应在 [-1,1] 附近。不对就回去改load_image_as_lab。5.4 推理结果偏紫或偏绿现象整张图色调诡异像坏了的老电视。原因lab2rgb前 L 没反归一化还是 [-1,1]或者 ab 没乘回 110。解决严格按L*5050和ab*110还原再送lab2rgb。这个坑我踩过两次都是复制代码时漏了乘系数。5.5 显存爆了batch 只能设 1现象CUDA out of memory256 分辨率都跑不动。原因313 维输出在 256x256 上是 313×256×256 的张量约 20M 元素加上中间特征很吃显存。解决用混合精度torch.cuda.amp或者把输出分辨率降到 128 训练、推理时再上采样。MobileNetV2 骨干比 ResNet-18 省一半显存。6. 进阶技巧用感知损失和验证集指标判断着色是否真的可用训练 loss 降了不代表颜色好看这是着色任务最反直觉的地方。我现在的习惯是每 5 个 epoch 存一张验证图的着色结果人眼看。数值指标用 PSNR 和 SSIM 参考但它们对颜色饱和度不敏感一张灰图 PSNR 可能很高。真正有用的是色彩直方图对比——把预测图的 ab 直方图和原图比分布接近才算颜色合理。进阶可以加感知损失把着色结果和原图都送进一个预训练 VGG比较高层特征的差异。这样网络会学到「语义上颜色对不对」而不是逐像素对齐。代码上就是在总 loss 里加一项0.01 * perceptual_loss权重别超过 0.05否则颜色会为了迎合 VGG 特征而失真。# 感知损失片段 vgg torchvision.models.vgg16(pretrainedTrue).features[:16].to(device).eval() for p in vgg.parameters(): p.requires_grad False def perceptual_loss(fake_rgb, real_rgb): f1 vgg(fake_rgb) f2 vgg(real_rgb) return nn.functional.l1_loss(f1, f2) # total_loss ce_loss 0.01 * perceptual_lossfeatures[:16]取到 VGG 的第三个 block兼顾纹理和语义。权重 0.01 是起点观察验证图颜色是否自然再微调。这个技巧对老照片特别有用因为老照片的退化让逐像素损失不可靠感知损失更鲁棒。最后说个我的习惯每次改完参数先拿同一张黑白人像跑一遍看肤色和背景分不分得开。这张图能过再上批量。着色这行玄学不少但把量纲、权重、感受野这三件事盯死八成翻车都能避免。希望帮到你。本文还有配套的精品资源点击获取