
简介资源为基于深度学习的黑白图片上色项目源码面向Python开发者、计算机视觉学习者和图像处理从业者。项目围绕卷积神经网络CNN构建涵盖数据预处理、模型训练、评估和推理预测等完整环节可帮助用户将历史黑白照片或灰度图像自动转换为彩色图像适用于老照片修复、影视素材翻新等场景。压缩包共9个文件总大小约8KB均为轻量级代码文件其中6个Python脚本分别承担网络结构定义、训练流程、数据转换与图像裁剪等功能另含1个Shell脚本用于视频抽帧1个Markdown说明文档用于快速上手以及1个Git忽略配置文件。目前已有2726人学习下载适合希望快速理解图像色彩化经典实现思路的用户。资源提供了可直接运行的训练与预测代码并配套数据处理工具读者可据此掌握CNN在颜色回归任务中的具体应用也能基于现有模型结构进行二次开发与实验调优。1. 给黑白图片上色为什么说是深度学习的活把「Python-ImageColor 利用深度学习方法给黑白图片上色」这句话拆开看核心其实不是“上色”而是“猜色”。一张黑白照片丢失的是每个像素的颜色信息同一个灰度值可能是白墙、灰裤子也可能是天空、水泥地。传统图像处理算法能做的是查表映射、直方图匹配、色板替换但这些方法不认得物体只能做“伪彩”碰到人脸和草地就穿帮。ImageColor 这类基于深度学习的工具做的则是让网络从大量彩色图片里学到“草地是绿的、天空是蓝的、嘴唇是红的”这种语义关联再对输入的黑白图做条件生成。这个方案能解决三类具体问题老照片和胶片档案的彩色还原、黑白监控素材的辅助可视化、历史影像资料的批量风格统一。适合的读者也分两类一类是有 Python 基础、想直接拿模型出结果的工程师另一类是正在选型的研究者想搞清楚这种生成式上色方案和传统算法在工程上的真实差距。下面的内容按“原理 — 落地 — 调参 — 踩坑 — 进阶”这条线展开你可以一路照着做也可以在某一章停下来单独验证。2. 彩色化任务建模与模型选型为什么 GAN 能上色而传统算法不能2.1 Lab 色彩空间与“ab 通道预测”为什么是标准做法彩色化任务的第一步不是选模型而是选色彩空间。最常见的做法是把 RGB 图像转换到 Lab 色彩空间其中 L 通道代表亮度a 和 b 两个通道代表颜色信息。一张黑白图本质上只有 L 通道所谓上色就是给定 L预测出对应的 a、b。ImageColor 这类工具之所以普遍采用 Lab 而不是直接在 RGB 里做回归有两个工程层面的原因。第一个原因是亮度信息被完整保留。如果把任务建模成“从灰度图预测三通道 RGB”模型不仅要猜颜色还得同时重建亮度细节等于把简单问题做复杂了输出容易整体变糊。第二个原因是损失函数更容易设计。颜色误差集中在 a、b 两个通道上L 通道不参与回归就可以用单独的亮度损失或者干脆不约束 L避免颜色和亮度相互干扰。顺带一提Lab 空间里 a、b 通道的取值范围是固定的方便做归一化也方便在推理时对颜色饱和度做后处理。传统算法在这一步就已经暴露短板。灰度映射只能把灰度值线性地对应到某个色带查表法必须提前定义“肤色对应哪个灰度值”这种先验邻域扩散法一旦物体边缘不清晰就会把颜色涂到隔壁物体上。它们共同的问题是没有语义理解能力深度学习模型则是在训练中隐式学习了“这个灰度分布属于叶子还是属于墙面”。所以选型层面不用纠结做上色就奔着深度模型去传统方法只适合做快速试看。2.2 三种主流网络结构回归、对抗、分割引导在具体实现上目前主流的深度上色网络基本可以归为三类它们的适用场景差别明显这里做一次对比选型。第一类是自编码器回归结构典型形态是 U-Net 式网络。输入是单通道 L输出是双通道 ab中间通过下采样提取语义、上采样恢复空间分辨率。这一类结构的优点是训练稳定、收敛快、不容易出伪影适合大批量跑图。缺点是颜色容易趋于保守直观表现就是画面偏灰、饱和度不够——模型拿不准的地方倾向于输出接近 0 的 ab 值。第二类是条件生成对抗网络在回归模型后面加一个判别器判别器负责判断输入图是真实彩色图还是模型上色图。对抗训练让模型不敢输出“安全但灰”的颜色而是给出更饱和、更接近真实分布的色彩观感好很多代价是训练不稳定调参周期长偶尔会生成奇怪的色块。第三类是分割引导方案先让语义分割模型给灰度图每个像素打上“天空、地面、衣服、皮肤”这类标签再按标签到颜色分布做映射或作为条件输入给生成网络。这种做法可控性最强甚至可以人为指定某个区域的色板但工程链路上多了一个分割模型维护成本和出错点都增加了。我给 ImageColor 做技术选型时的习惯是把回归模型作为默认底座保证批量处理不出幺蛾子对需要展示、修复的项目再加对抗模块做精修只有遇到特定色调要求比如把天空统一调成某种蓝才上分割引导。2.3 预训练模型与权重选择的边界实际工程中很少有人从零训练彩色化模型大家都是加载预训练权重。但这里要意识到一个边界预训练权重的训练数据决定了它的“审美”。在自然风光彩色照片上训练的权重拿去处理室内老照片墙面可能偏黄在人像数据上训练的权重处理风景会明显发闷。这不是模型坏了而是数据域不匹配属于典型的“黑匣子”问题。选择权重时我一般看三点。第一训练数据的域是否和你的输入接近比如老照片扫描件有大量划痕和噪点最好选训练数据里包含旧影像增强样本的权重。第二模型的输入分辨率权重训练时通常固定在某些分辨率比如 256 或 384推理时直接喂大图会因尺寸不匹配产生畸变。第三开源许可商用项目要特别注意权重和代码的授权边界。不要盲目追求“效果最好”的权重稳比猛重要。3. 用 Python 与 ImageColor 跑通第一条上色流程3.1 环境准备与模型文件放置实战第一步是搭环境。常见做法是用 PyTorch 作为推理后端配合 OpenCV 做图像读写、Pillow 做格式兜底。下面是最小安装命令pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow numpy这套命令的逻辑是先把深度学习运行时装上。如果你机器上已经有可用的 CUDA 环境直接用 cu118 或 cu121 的轮子如果只是 CPU 推理把--index-url去掉装默认版本也行速度会慢不少但流程不变。OpenCV 负责读图和颜色空间转换Pillow 用来处理 WebP、TIFF 这类 OpenCV 默认读不好的格式。装完后把预训练权重文件放到项目目录下的weights/文件夹代码里用相对路径引用方便后面换权重。需要注意 PyTorch 1.x 和 2.x 在部分 API 上有差异如果你用的是 1.x遇到torch.load报错时记得加map_locationcpu参数。另外模型文件不要直接放在项目根目录因为后面批量处理时可能混入非模型文件导致遍历出错。3.2 单张黑白照片的上色核心代码环境准备好之后核心推理代码大概长这样。这里假设你的模型输入是 256x256 的灰度图 L 通道输出是 256x256 的双通道 ab 值import cv2 import numpy as np import torch # 加载模型权重文件放在 weights/ 下 model torch.load(weights/colorizer.pt, map_locationcpu) model.eval() def inference_gray(img_path, out_path, size256): # 读取灰度图保留原始尺寸信息用于后处理 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) h, w img.shape # 缩放到模型输入尺寸保持比例并填充到正方形 scale min(size / h, size / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(img, (new_w, new_h), interpolationcv2.INTER_LINEAR) canvas np.full((size, size), 128, dtypenp.uint8) canvas[:new_h, :new_w] resized # 转 Lab取 L 通道并归一化到 [-1, 1] lab cv2.cvtColor(canvas, cv2.COLOR_GRAY2LAB) L lab[:, :, 0].astype(np.float32) / 128.0 - 1.0 L_tensor torch.from_numpy(L).unsqueeze(0).unsqueeze(0) # 前向推理得到 ab 通道 with torch.no_grad(): ab_tensor model(L_tensor) ab ab_tensor.squeeze(0).permute(1, 2, 0).numpy() # ab 反归一化之后和 L 拼接转回 RGB ab (ab * 128.0).astype(np.float32) L_orig lab[:, :, 0].astype(np.float32) lab_color np.concatenate([L_orig[:, :, np.newaxis], ab], axis2) rgb cv2.cvtColor(lab_color, cv2.COLOR_LAB2RGB) # 裁掉填充区域还原原始尺寸 rgb rgb[:new_h, :new_w] rgb cv2.resize(rgb, (w, h), interpolationcv2.INTER_LINEAR) cv2.imwrite(out_path, rgb)这段代码里有三个关键参数要解释。size256是模型固定的输入边长如果你的权重是 384 训练的就要改成 384这是最容易踩的尺寸坑。归一化时除以 128 再减 1是因为 Lab 的 L 通道理论范围是 0 到 255映射到 [-1, 1] 是常见做法ab 通道在推理后要乘回 128 才能拼接这里乘法和加法的位置出错会直接导致颜色偏蓝或偏黄。最后裁掉填充区域再还原尺寸是为了拿到原始分辨率的输出否则整张图都会停在 256 边长上。3.3 批量处理并保留分辨率单张跑通后批量只是加一个遍历逻辑。下面这段代码会扫描指定文件夹下的所有黑白图逐张推理并保存到输出目录import glob import os input_dir input_imgs output_dir output_imgs os.makedirs(output_dir, exist_okTrue) for img_path in glob.glob(os.path.join(input_dir, *.jpg)): out_path os.path.join(output_dir, os.path.basename(img_path)) try: inference_gray(img_path, out_path, size256) print(fprocessed: {img_path}) except Exception as e: print(ffailed: {img_path}, error: {e})这段代码逻辑很简单但有一个细节值得注意每张图独立调inference_gray意味着模型每次都要重新加载权重。如果只是处理几十张图没问题处理上千张图就会明显变慢。我一般会改成先加载一次模型然后把model作为参数传进函数或者用torch.no_grad包住整个循环而不是每张图都重复进入推理态。批处理场景下还有一个体验问题黑白图里经常混着已经带一点颜色的旧照片跑出来的结果会怪怪的我建议投入运行前先写个脚本检查输入图的通道饱和度把已经带色的图挑出来单独处理。4. 关键参数与训练配置让 ImageColor 按你的数据收敛4.1 损失函数与权重表训练和微调阶段是参数最多的环节也是决定上色风格的核心。直接把自然图像上训练的模型拿来做迁移时通常要重新设计损失函数。下表是常用损失项和我在实践中采用的权重范围损失项作用常见权重说明L1 损失约束 ab 通道的像素级误差1.0基础损失保证颜色大致准确感知损失约束高层语义特征一致性0.1 到 0.5提升结构清晰度但过度会丢失颜色细节对抗损失让输出颜色更饱和、更真实0.01 到 0.1权重太大会产生伪影色度加权损失对饱和度低的像素加重惩罚手动设置解决模型“偷懒”输出灰色的问题这里要重点解释一个现象自然图像里灰色、棕色、肤色这类低饱和颜色占比极高模型如果只把 L1 损失降到最低最优策略就是把所有像素都输出接近 0 的 ab 值也就是整张图发灰。用对抗损失或色度加权损失本质上是逼着模型在“安全但灰”和“冒险但真”之间选择后者。我见过的翻车案例里七成以上是 L1 权重太高造成的“灰蒙蒙”效果调权重比换模型更见效。另一个容易被忽略的参数是损失函数作用的通道。L1 损失只应该计算 ab 通道不要把 L 通道拉进来。有些训练代码直接把输出图和真图做torch.nn.L1Loss()此时 L 通道的误差也计入梯度模型就会花精力去拟合亮度ab 通道的学习被稀释整体颜色饱和度明显下降。4.2 数据预处理与增强训练彩色化模型时数据增强策略和分类任务不一样。分类任务可以随便做颜色抖动、亮度调整但彩色化任务的输入是灰度图输出是彩色图数据增强必须保证“颜色语义不改变”。下面是训练阶段常用的预处理代码import random import torchvision.transforms as T # 只做几何增强和裁剪不做颜色抖动 train_transform T.Compose([ T.RandomResizedCrop(256, scale(0.8, 1.0)), T.RandomHorizontalFlip(p0.5), T.ColorJitter(brightness0.0, contrast0.0, saturation0.0, hue0.0), ]) # 将 RGB 转 Lab分离 L 和 ab def lab_split(rgb_tensor): lab rgb_to_lab(rgb_tensor) # 假设已有转换函数 L lab[:, :, :, 0:1] ab lab[:, :, :, 1:3] return L, abColorJitter在这里直接留空是故意的因为颜色抖动会改变物体的真实颜色关系比如把草地的绿色抖成黄色模型学到的“草地对应绿色”就会被污染训练出来的模型容易颜色混乱。亮度抖动也要谨慎因为训练时输入是 L 通道你加强亮度抖动等于改变了输入分布推理时遇到正常亮度的图片反而表现不佳。如果确实要做增强建议在 Lab 空间对 ab 通道做小幅度的饱和度扰动而不是对 RGB 做整体颜色抖动。数据配比也很重要。如果你手头的数据集里风景占 70%、人像占 30%训练出来的模型把人脸处理好的概率就低很多。我一般会按场景分类采样确保每个语义类别人像、建筑、自然、室内在训练集里都有至少 10% 的比例这样微调后的模型才不会有明显的场景偏科。4.3 训练停止时机与监控指标训练这类生成模型最忌讳的就是死盯训练集 loss。彩色化任务的 L1 损失不会像分类精度那样单调下降经常出现“loss 看起来在降但输出图颜色越来越脏”的情况。我一般每训练 5 个 epoch 就固定输入一张验证图肉眼观察三个症状整体是否发灰、边缘是否模糊、是否出现不自然的色块。如果看到发灰优先加对抗损失权重或色度惩罚看到边缘模糊检查感知损失的权重是否过高同时确认输入图像尺寸没被压得太小看到色块大概率是训练数据里有重复度高且色彩单一的小图模型把某些 pattern 强行对应到了某个颜色上。把这些症状当成监控指标比看 loss 曲线有用得多。还有一个实操习惯先把模型固定住只用回归损失训练 20 个 epoch再加入对抗损失微调 10 个 epoch这样分阶段训练能避免两个损失在早期互相拉扯。5. 彩色化必踩的四个坑颜色溢出、皮肤发灰与老照片翻车5.1 颜色溢出到物体边缘外现象天空的蓝色顺着屋檐涂到了白墙上草地的绿色漫到裙摆边缘物体边界像被水彩笔洇开一样。原因卷积网络为了扩大感受野会不断下采样到了高层特征图上一个像素可能对应原图 16x16 甚至 32x32 的区域语义边界天然就是模糊的。模型在判断“墙和天空的分界线”时会犹豫颜色就从分界区域溢出来。解决推理环节先用 OpenCV 的形态学闭运算或中值滤波对 ab 通道做后处理把相邻像素的颜色差异抹平更激进的做法是引入一个轻量级边缘检测对边缘区域的 ab 通道做抑制。如果溢出只出现在局部可以用分割掩码把特定区域的颜色锁定这个技巧在后面一章展开。5.2 人脸皮肤发灰、无血色现象人物脸部、手部的皮肤区域颜色暗淡饱和度极低看起来像贫血或者黑白照片没上完色。原因训练数据中肤色占比不高而 L1 损失对低饱和像素的惩罚天然小于高饱和像素模型的“最优策略”是给不确定的区域输出灰色。肤色是典型的低饱和区域就成了重灾区。解决第一是在损失函数里对 ab 通道靠近 0 的区域加大权重比如对饱和度低于某个阈值的像素损失乘 1.5 到 2 倍第二是训练时多放人像数据第三是推理后做一次 HSV 空间饱和度拉伸只增强低饱和区域。我个人推荐前两种从根源解决问题第三种容易连同背景一起染色。5.3 老照片的划痕、噪点被当成纹理强化现象扫描的旧照片上有大量划痕、噪点甚至胶卷颗粒上色后这些瑕疵被赋予了鲜艳颜色比原来更显眼。原因生成式模型本质上是“纹理放大器”它会把输入中突兀的灰度变化解读为语义边界然后按语义区域的色板给划痕上色。划痕横跨树干和天空就会被涂成一半绿一半蓝视觉上非常突兀。解决上色前先对输入图做降噪和划痕修复把老照片的瑕疵处理干净上色后把输出图的 L 通道替换回原始去噪后的 L 通道让颜色细节和亮度细节分离防止模型在亮度层面把划痕强化。这两个步骤的组合能显著改善老旧影像的上色效果。5.4 相邻两帧颜色跳变现象处理黑白视频或连续扫描帧时前一帧天空是蓝色后一帧变成了紫色再后面又跳回蓝绿之间整段素材看起来在频闪。原因模型对每一帧独立推理帧与帧之间没有任何语义关联。灰度值分布相近但略有差异的两帧模型可能给出完全不同的颜色判断尤其在场景切换和云雾遮挡时最明显。解决常规做法是对 ab 通道按时间窗口做滤波比如前后各取 3 帧对颜色值做加权平均更稳的方案是引入光流约束让相邻帧的颜色沿着运动方向传播。做批量素材时我常用第一种计算开销小效果立竿见影。6. 进阶用掩码与色板干预把“猜色”变成“指定色”6.1 灰度图上的语义掩码与色板重映射模型输出的颜色再好看终究是它“认为”的颜色不是你要的颜色。真实项目里常常出现“这张老照片里墙应该是红砖色模型却给了紫红色”的尴尬局面。进阶做法是给模型加一个人工干预接口先用粗分割或手动画框得到区域掩码再按用户指定的色板对掩码区域做颜色重映射。下面是一个简单的实现思路def apply_palette(lab_img, mask, target_ab): # lab_img: Lab 颜色空间图像, mask: 0/1 掩码, target_ab: 目标色板的 ab 值 ab lab_img[:, :, 1:3].astype(np.float32) alpha cv2.GaussianBlur(mask.astype(np.float32), (15, 15), 5) alpha alpha[:, :, np.newaxis] new_ab ab * (1 - alpha * 0.7) target_ab * alpha * 0.7 lab_img[:, :, 1:3] new_ab return lab_img这段代码的逻辑是做一个软融合alpha经过高斯模糊后在掩码边缘产生过渡区域避免颜色替换出现生硬的边界融合系数 0.7 表示保留 30% 的原模型颜色这样替换后颜色不会显得太假。实际使用中可以先跑一遍模型得到初始结果再用简单工具圈出天空、墙面等区域指定对应的 RGB 色值并转成 Lab 里的 ab 分量逐区域代入。这套逻辑不依赖重新训练模型每个区域 3 到 5 分钟就能调完。6.2 二次迭代与批量风格统一做过一批老照片修复后我的体会是与其每张图分别调色不如先在 3 到 5 张代表性图片上调好各区域的色板保存成一个配置再用同一套配置跑完整个批次。这样既保证了整组照片色调统一又省掉大量重复劳动。具体做法是把每个区域的掩码文件命名规范成region_brick.png、region_sky.png这类格式在配置里记录掩码路径和对应色板值批量脚本逐张读取并套用。遇到掩码不够精准的图单独微调那一张的掩码即可。有一次我处理一组老建筑照片第一次跑完所有图青砖墙全变成了紫红色当时就觉得这类生成模型确实是“黑匣子”它不会告诉你它在哪个细节上偷换了概念。后来我养成了一个习惯任何批处理项目开工前先跑三张有代表性的图逐张检查颜色合理性色板问题先处理掉再铺开跑。这个方法帮我省掉了大量返工时间。做上色这件事模型负责把你带到 80 分的位置剩下 20 分靠的是你对颜色语义的理解和手上的掩码。如果你也准备用深度学习给黑白素材上色希望帮到你。本文还有配套的精品资源点击获取