☰
灰度图像彩色化实践:基于Python与Lab颜色空间的模型选型与复现
2026/10/7 12:47:36 网站建设 项目流程

简介:面向图像处理初学者的Python灰度图像彩色化实践包,以“灰度图→彩色图”的真实感着色为实验目标,配套完整源码和一份约三页的实验报告,适用于课程设计、期末复习与相关课题入门。资源共36个文件,除.py源码与.pyc编译文件外,还包含.jpg/.png示例图像、.xml工程配置、.docx报告文档等,压缩包约14.38MB,目录沿用colorization-master结构,附带saved_eccv16.png等结果图,便于对照代码运行与输出评估。目前已有1373人学习下载,从源码到报告一应俱全:既可直接复现灰度图彩色化实验,也可通过报告中的实现思路与特征计算要点,理解图像特征表示在色彩迁移中的作用;即便没有深度学习基础,也能借助清晰注释和实验输出逐步读懂算法流程,适合小白快速上手并完成实验答辩。

1. 灰度图像彩色化:这门课设,值不值得你用 Python 复现一遍

本科《图像特征计算与表示》这门课里,灰度图像彩色化几乎是每年都会出现的课程设计题目。给定一张只有亮度信息的灰度图,让程序自动补出 a、b 色彩通道,既要还原真实感,又不要把天空涂成紫色。最近我完整拆了一个 Python 源码资源:colorization-master,里面带 demo_release.py、colorizers 工具包、imgs 输入图片和一份三页实验报告,正好把「复现代码 → 做实验 → 交报告」这条链路一次补齐。它适合两类人:一是正在做课设、需要快速跑通并写出报告的学生;二是想了解图像特征计算与表示里 Lab 空间、颜色分类这类经典思路的入门开发者。

2. 图像特征计算与表示:Lab 空间与两个预训练模型的选型逻辑

2.1 为什么必须先把 RGB 拆成 L、a、b 三通道

很多人在第一次做彩色化时,第一反应是拿网络直接回归 RGB 三个通道。实际这样做有两个问题:RGB 三个通道高度相关,模型训练时梯度互相干扰,容易产生偏色;另一个问题是灰度图里根本没有颜色基线,模型很难从零同时预测三个值。这个项目换了一个特征表示思路,把输入和输出都放到 Lab 颜色空间,这是整个资源里最核心的设计。

在 Lab 空间中,L 通道只表示亮度,范围是 0 到 255;a 通道表示绿色到红色,b 通道表示蓝色到黄色,两者的数值范围大约是 -128 到 127。彩色化任务就变成了:保持灰度图的 L 通道完全不动,让网络只预测 a、b 两个通道。这样做的好处非常明显——网络不用学亮度结构,只需要学颜色分布,训练难度小了一个量级。

读取和预处理时要注意,网络内部期望的输入范围并不是 0 到 255,而是 -1 到 1。常见做法是把图像先转成 float,归一化后再减均值除方差。我拆这个源码包时看到的预处理逻辑大致如下:

import numpy as np import cv2 from PIL import Image def load_img(img_path, target_hw=(256, 256)): # 统一用 PIL 打开,转 RGB,避免 OpenCV 的 BGR 通道顺序问题 img = Image.open(img_path).convert('RGB') img = np.array(img).astype(np.float32) img = cv2.resize(img, target_hw) # 先缩放到 0-1,再映射到 -1 到 1 img = img / 255.0 return img def get_l_ab_input(img): # OpenCV 的 cvtColor 可以直接把 RGB 转成 Lab lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) l_channel = lab[:, :, 0] # 网络输入 L 通道,归一化到 [-1, 1] l_norm = (l_channel / 255.0 - 0.5) / 0.5 ab_gt = lab[:, :, 1:] # 真实彩色图的 a、b 通道,训练时作为目标 return l_norm, ab_gt

这里有两个参数值得说明。target_hw 控制图像缩放尺寸,官方 demo 默认按原尺寸推理,但如果你只想复现课设,我建议固定成 256 乘 256,速度和显存占用都会稳定很多。另一个是 cvtColor 的输入,必须保证是 RGB 通道顺序,否则 Lab 转换后颜色完全错位。

在推理时,网络只会拿到一个单通道的 L 输入,但 PyTorch 模型通常要求四维张量,形状是 batch、channel、height、width。所以编码阶段要先把 L 通道扩展成三维,再补一个 batch 维度。项目里的 colorizers/util.py 就把这些封装好了,这也是源码包里的关键工具文件。

2.2 ECCV16 与 SIGGRAPH17:两个模型出力有什么不一样

这个资源里 demo_release.py 把彩色化模型封装成两个版本,参数用 model_id 区分:eccv16 和 siggraph17。它们不是同一个网络简单改了改参数,而是两篇经典论文的实现,选型直接影响最后结果。

ECCV16 对应的论文是 Colorful Image Colorization,核心思路是把彩色化当成一个颜色分类问题。ab 空间被量化成 313 个格子,每个格子代表一类颜色,网络输出一个概率分布,训练时用加权分类损失解决自然图像中常见颜色样本多、罕见颜色样本少的问题。它的优点是全局语义能力,看到草地就能想到绿色,看到天空就能想到蓝色,颜色通常比较饱和,适合做课设演示。

SIGGRAPH17 对应的论文是 Real-Time User-Guided Image Colorization with Learned Deep Priors。在 ECCV16 的基础上加了一个交互输入,可以给灰度图点几个彩色种子点,模型根据这些提示色做局部约束。有没有提示点,结果差异很大。如果不给任何提示,它和 ECCV16 的输出差别不是特别大,但颜色风格更平和,噪点更少。

我整理了一张对比表,可以直接写进实验报告里:

对比维度ECCV16SIGGRAPH17
训练目标313 个颜色类别的分类分类任务 + 用户提示点条件输入
交互能力不支持支持彩色种子点
色彩风格偏饱和、偏浓偏真实、偏淡
推理速度快稍慢,但 CPU 也能跑
课设推荐度适合演示适合做对比实验

实际选型时,如果你只是想拿到一张能看的彩色图,用 ECCV16 就够了;如果你的实验报告需要突出「交互式彩色化」这个点,就选 SIGGRAPH17,并自己实现往图上画几个彩色点再输入模型。还要注意一个很容易踩的坑:这两个模型都是针对自然图像训练的,拿它彩色化老照片、黑白人物肖像,效果够用,但拿它处理漫画、文档扫描件,颜色会很怪。后面避坑章节我会展开说。

3. 把 demo_release.py 跑通:环境、权重和一次完整推理

3.1 requirements.txt 背后的依赖坑

资源里带了 requirements.txt,我先说结论:这份依赖列表能满足 90% 的复现场景,但版本不能盲目升级。核心依赖大致是 numpy、scipy、Pillow、opencv-python、torch、torchvision、matplotlib、tqdm。这个组合在 Python 3.8 到 3.11 下都能跑,如果你机器上装的是 Python 3.12,会有概率遇到 torch 版本不匹配的问题。

我一般会先建一个干净的虚拟环境,避免和系统 Python 环境打架:

python -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install numpy==1.26.4 scipy==1.11.4 pillow==10.4.0 opencv-python==4.9.0.80 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install matplotlib==3.8.4 tqdm

说明几个参数。把 numpy 固定在 1.26.4,是因为更高版本偶尔会让某些 torch 版本报二进制不兼容;scipy 1.11.4 和 Pillow 10.4.0 都是为了配合老代码的 API。torch 这里装的是 CPU 版,彩色化推理不需要大型 GPU,CPU 跑一张 256 乘 256 的图差不多 3 到 5 秒,完全够课设用。如果你本机已有 GPU 版本的 PyTorch,直接跳过第二条 index-url 参数即可。

装完之后检查一下关键包能否导入:

python -c "import torch, cv2, PIL, scipy, matplotlib; print(torch.__version__, cv2.__version__)"

这里有个很容易忽视的点:opencv-python 的导入名是 cv2,不是 opencv。如果 pip 装完后导入报 ModuleNotFoundError,先敲 pip list 看包名,再检查安装过程有没有静默失败。

3.2 权重文件放哪里

很多第一次接触这类源码包的人会以为 saved_eccv16.png 和 saved_siggraph17.png 是模型权重,实际上这两张图是官方跑出来的示例输出结果,已经保存成 PNG 方便你对照自己的结果。真正的权重是 .pth 文件,并不包含在 zip 资源内,需要单独下载。

常见做法是创建 models/pytorch 目录,把两个权重文件按照官方命名放进去:

mkdir -p models/pytorch # 假设你已经从公开渠道下载到了 eccv16.pth 和 siggraph17.pth mv eccv16.pth models/pytorch/ mv siggraph17.pth models/pytorch/

放错位置的典型报错是找不到 key 对应的 state dict,或者模型实例化后 forward 时没有权重可用。如果你在无网络环境内做课设,这个步骤要提前准备。还有一个细节:torch 默认会去 ~/.cache/torch/hub/checkpoints 里找带 URL 的权重,如果 demo_release.py 里有 load_state_dict_from_url 的逻辑而你没有联网,程序会卡在下载阶段。稳妥做法是先手动把权重下载好放到 models/pytorch,再修改代码里权重路径指向本地文件。

3.3 一行命令出图:参数怎么给最稳

环境配置好之后,官方 demo 的调用方式很直接:

python demo_release.py -i imgs/example.jpg --model_id siggraph17 --use_gpu 0

对应参数含义如下:

参数可选值作用
-i任意图像路径指定输入灰度图或彩色图
--model_ideccv16 / siggraph17选择预训练模型版本
--use_gpu0 或 10 强制使用 CPU,1 使用 GPU

如果你跑的是一张真正的单通道灰度 PNG,也就是没有 R、G、B 三通道信息的图,demo 内部一般会把它重复成三通道来匹配模型输入。命令行的 -i 参数给相对路径还是绝对路径都可以,但注意路径不要包含中文,否则 OpenCV 在部分 Windows 环境下会读不出文件,我在这上面翻过车。

默认输出目录是 imgs_out,文件名会拼接上模型 ID。比如输入 example.jpg、模型选 siggraph17,输出就是 imgs_out/example_siggraph17.png。跑完之后先别急着交,打开原图和输出图对比一下,看两个问题:主体颜色是否符合常识,边缘有没有溢出。

3.4 用 Python 检查输出结果是否正常

代码跑通了不代表结果正确,我通常会用一个小脚本量化检查输出图像:

import numpy as np import cv2 from PIL import Image def check_color_output(img_path): img = Image.open(img_path).convert('RGB') img = np.array(img).astype(np.float32) lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) a_std = lab[:, :, 1].std() b_std = lab[:, :, 2].std() saturation = (lab[:, :, 1] ** 2 + lab[:, :, 2] ** 2) ** 0.5 print(f'a 通道标准差: {a_std:.2f}') print(f'b 通道标准差: {b_std:.2f}') print(f'平均饱和度: {saturation.mean():.2f}') check_color_output('imgs_out/example_siggraph17.png')

这里为什么要看 a、b 的标准差:如果输出图的 a 和 b 标准差都小于 3,说明颜色几乎不存在,图像看起来是发灰的;正常自然图像彩色化后,a、b 标准差一般在 5 到 20 之间。平均饱和度则更直观,低于 15 属于保守配色,高于 40 会显得过于艳丽。用这个脚本可以判断你的预处理、模型加载、输出转换三个环节有没有出问题。

4. 灰度彩色化避坑指南:5 个实战翻车点

4.1 输入灰度图被当成三通道,颜色偏得离谱

现象:输入是一张 640 乘 480 的灰度 JPEG,跑完后输出整体偏黄绿色,房顶和地面颜色混成一片。

原因:模型虽然内部接收三通道输入,但它期望的是三个通道相同的灰度表示。很多人直接用 cv2.imread 读取原始灰度图,得到 H 乘 W 的单通道矩阵,然后要么忘了扩展,要么扩成 H 乘 W 乘 1 直接送进网络,导致预处理阶段做 Lab 转换时数据形状错乱,颜色完全偏移。

解决:读取后先转成三通道:

import cv2 import numpy as np gray = cv2.imread('gray.png', cv2.IMREAD_GRAYSCALE) # 单通道 rgb_like = np.repeat(gray[:, :, None], 3, axis=2) # 变成三通道

np.repeat 的参数 Confirmed:gray[:, :, None] 会把 H 乘 W 变成 H 乘 W 乘 1,repeat 的 axis=2 表示沿通道方向复制三次。之后再走正常的 RGB 到 Lab 转换,就不会有形状和通道含义的错乱。

4.2 图像太大导致显存溢出或者卡死

现象:输入一张 4000 乘 3000 的照片,GPU 环境下报 CUDA out of memory,CPU 环境下直接卡到几十秒不出图。

原因:彩色化模型虽然是全卷积网络,但 demo_release.py 默认按原始分辨率推理,而预训练模型是在 256 乘 256 级别上训练的。分辨率越大,中间特征图越大,显存消耗按平方增长。

解决:读图时先统一缩放到合理尺寸,例如最大边不超过 512:

img = Image.open('big.jpg').convert('RGB') img.thumbnail((512, 512), Image.Resampling.LANCZOS)

如果你在实验报告里写「算法不限制输入尺寸」,代码里一定要保留缩放这一步,否则会给人留下没有做过大图测试的印象。另外,--use_gpu 0 在 CPU 模式下虽然慢,但基本不会因为内存崩掉。

4.3 输出整体发灰,a/b 通道方差几乎为零

现象:模型跑通了,也没有报错,但输出图和灰度图几乎一样,偶尔有淡到看不出来的颜色。

原因:输入张量的归一化范围不对。如果直接把 0 到 255 的像素喂给模型,或者把 0 到 1 的像素误当成 -1 到 1,网络输出的 ab 置信度会被过度抑制,最终颜色被拉平。

解决:统一走 (x / 255.0 - 0.5) / 0.5 的方式,把输入映射到 -1 到 1。同时检查输出端有没有做反向反归一化,常见操作是 (out + 1) * 127.5,再夹取到 0 到 255。

l_norm = (l_channel / 255.0 - 0.5) / 0.5 # 正确 # 错误示范:直接 l_channel / 128.0 - 1,会破坏 L 通道原本的感知分布

L 通道不是线性亮度范围,它本身带 gamma 校正,直接除以 128 而不是先缩放到 0 到 1,会导致后续 ab 输出摆幅异常。这是我实际见过最多的翻车原因,没有之一。

4.4 加载权重报警告,结果全是彩色噪点

现象:load_state_dict 时提示 missing key 或者 unexpected key,运行后的输出图像像打翻了的调色盘,到处是彩色斑点。

原因:预训练权重往往是用 torch.nn.DataParallel 训练出来的,state dict 里的键名带了 module. 前缀,而你的模型实例是单卡加载,键名对不上。另一类原因是本地权重文件不完整,下载中断后 partial 文件也能加载但权重值错误。

解决:加载时剥掉前缀或者用 strict=False:

import torch state_dict = torch.load('models/pytorch/eccv16.pth', map_location='cpu') new_state = {k.replace('module.', ''): v for k, v in state_dict.items()} model.load_state_dict(new_state, strict=False)

strict=False 会忽略多余的键,但不会忽略缺失的键。如果你发现缺失的键集中在网络后半部分,说明权重文件下载不完整,重新下载而不是改代码硬绕。

4.5 OpenCV 和 Pillow 的通道顺序不一致,输出偏蓝

现象:同一张图,用 demo 跑出来颜色偏蓝偏暗,直接用 PIL 打开原图却一切正常。

原因:cv2.imread 读出来的图像通道顺序是 BGR,而模型训练数据用的 RGB。如果你在自定义预处理里用了 cv2 读图,却用 PIL 的方式保存,或者相反,颜色通道就整体交换了。

解决:在项目里统一用 PIL 打开、输出时再转 numpy,或者在 cv2 读完后显式转换:

img_bgr = cv2.imread('input.jpg') img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

在实验报告的代码附录里,我会建议把「读取 → 转换 → 输出」统一封装,只暴露 RGB 接口,这样后续谁改参数都不会因为通道顺序翻车。

5. 把三页实验报告写成答辩能过:从跑通到讲清楚

5.1 三页报告怎么排:我按这个骨架写

很多课设资源里会附带报告,但那份三页实验报告更像是实验记录,不是答辩话术。我建议你自己重写成下面的结构,正好三页 A4:

页码内容要点
第 1 页引言 + 任务描述 + 技术路线说清输入、输出、为什么选 Lab 空间
第 2 页方法原理 + 核心代码流程ECCV16 颜色分类思想、模型结构图、关键参数
第 3 页实验对比 + 结果分析 + 结论至少两张输出图、量化统计、总结

写报告时注意一个常见误区:不要大段贴源码,老师想看的是「为什么这样设计」,不是代码本身。比如 L 通道固定、ab 通道预测这个思路,要结合公式说明;313 个颜色类别的量化为什么比直接回归 RGB 更稳,要写训练时类别不均衡的处理。

这份源码包里自带实验报告,可以作为复习资料用,但最好把它当作提纲,因为课程要求每年可能变化。我的习惯是先把代码跑一遍,保存两组模型的输出结果,再对着输出结果写报告,这样截图不会造假,数据也能对上。

5.2 没有真值图像也能量化:色彩统计与主观评价表

灰度彩色化没有标准答案,因为原始图像没有彩色真值,所以实验报告里写不了 PSNR 和 SSIM。这时候需要用间接指标证明你确实把颜色加上去了,而不是只改了亮度对比度。

我用一个脚本计算不同模型输出的颜色分布:

import numpy as np import cv2 from PIL import Image def color_stats(path): img = Image.open(path).convert('RGB') img = np.array(img).astype(np.float32) lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) a = lab[:, :, 1] b = lab[:, :, 2] colorful_ratio = float((np.abs(a) + np.abs(b) > 15).mean()) return { 'a_mean': float(a.mean()), 'b_mean': float(b.mean()), 'a_std': float(a.std()), 'b_std': float(b.std()), 'colorful_pixel_ratio': colorful_ratio, } for p in ['imgs_out/example_eccv16.png', 'imgs_out/example_siggraph17.png']: print(p, color_stats(p))

colorful_pixel_ratio 表示颜色明显的像素占比,阈值 15 是我根据多张自然图调出来的经验值。比 0.3 低就说明颜色太收敛,比 0.9 高则可能过度饱和。报告中把这个指标做成表格,再配上原图和两幅输出图的并排对比,就能支撑「ECCV16 色彩更饱和、SIGGRAPH17 更柔和」这个结论。

主观评价表也建议做一下:准备 10 张不同场景的灰度图,让三个同学分别评估天空、草地、肤色三个区域的颜色自然程度,打 1 到 5 分。这样报告里既有客观统计,也有主观评分,答辩时老师很难再追问。

5.3 报告中如何描述两个模型差异

这里补充一个写报告时容易困住的问题:为什么同一个输入,两个模型结果不同?你可以从训练数据分布的角度描述:ECCV16 在分类损失下倾向于选择概率最大的颜色类,所以大面积区域容易填上同一类颜色,看起来「块感」强;SIGGRAPH17 在颜色分类之外还加了提示点条件输入,让局部颜色可以突破全局先验,看起来更自然。这句话写进去,比单纯贴两张图更有说服力。

6. 再进一步:换数据集微调与后处理的习惯

灰度彩色化代码跑通只是第一步,如果你想在课程设计里拿更高分,或者想解决自己手头的真实需求,可以做两件事:换自己的数据集微调,以及加上输出后处理。

微调前要明白,预训练模型已经见过大量自然图像,直接从头训练非常烧时间。我一般会用较小的学习率微调最后两层:

import torch import torch.nn.functional as F # 假设 model 是 eccv16,冻结前面所有层 for name, param in model.named_parameters(): if 'classif' not in name and 'conv8' not in name: param.requires_grad = False optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4) for epoch in range(20): for gray_l, ab_target in dataloader: pred_ab = model(gray_l) loss = F.l1_loss(pred_ab, ab_target) loss.backward() optimizer.step()

batch size 一般取 16,lr 用 1e-4 而不是默认的 1e-3,因为预训练权重已经接近收敛点,学习率太大容易冲坏。数据集不强求 ImageNet,收集几百张三通道彩色图,转成 Lab 后切出 L 和 ab 就能训。

后处理则是我每次测试新图都会走过的流程:模型输出的颜色有时偏淡,可以转到 HSV 空间,把饱和度通道乘一个系数再转回来:

import cv2 import numpy as np hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV) hsv[:, :, 1] = np.clip(hsv[:, :, 1] * 1.2, 0, 255) img_boosted = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)

系数 1.2 是个安全值,过了会显得像滤镜过头。从那以后,我每次拿到这类带模型的源码包,第一件事永远是先把 demo 跑通,确认权重、输入输出格式都在预期内,再动自己的图和参数。这个习惯让我少踩了至少一半的坑。希望帮到你。

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

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

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

立即咨询