多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python图像着色实战:ECCV16与SIGGRAPH17模型推理与避坑指南

Python图像着色实战:ECCV16与SIGGRAPH17模型推理与避坑指南 简介这份资源面向希望上手图像自动着色、了解深度先验应用的Python开发者与计算机视觉学习者核心是两套预训练着色模型ECCV16与SIGGRAPH17可在无需重新训练的情况下为灰度照片实时上色。包内共23个文件以6个py脚本与5个pyc缓存为主辅以jpg、jpeg、png示例图片及license、md、txt说明文档压缩包约4.47MB体量轻便便于快速部署与二次开发。使用流程清晰将图像转为Lab空间、缩放至256×256、着色后与原始全分辨率拼接再转回RGBdemo_release.py提供了完整调用示例输出结果可与imgs_out中的参考图对照验证。目前已有680人学习下载适合想研究深度先验着色、复现经典模型或将其集成到自身图像处理流程中的读者参考。1. 黑白照片上色这件事这套 Python 代码到底能不能直接跑手里有一张几十年前的黑白老照片想给它上色第一反应可能是打开某个在线工具传上去等几秒下载回来。但如果你是个写代码的多半会想这背后的模型能不能自己跑一遍colorization-master这个包就是干这个的——它把 ECCV16 和 SIGGRAPH17 两个经典着色网络打包成了可以直接调用的 Python 模块输入一张灰度图输出一张彩色图。整个仓库不大核心就是colorizers目录下的eccv16.py、siggraph17.py、base_color.py和util.py四个文件加上一个demo_release.py做命令行入口。它解决的不是从零训练一个着色模型的问题而是我有一张图想用现成的深度先验给它上色的问题。适合谁适合想快速验证着色效果、想拿它做二次开发、或者想拿它当课程设计里基于深度学习的图像着色那一章的实操素材的人。不适合指望它把每张图都染得跟原彩照一模一样的人——它给的是合理推测不是还原真相。2. 把模型跑起来从 requirements 到第一张着色图2.1 环境准备与依赖安装拿到colorization-master.zip之后先解压到一个没有中文和空格的路径下。目录结构里能看到requirements.txt这是第一步要处理的东西。常见做法是建一个独立的虚拟环境避免和系统里已有的 PyTorch 版本打架。# 创建虚拟环境Python 版本建议 3.8 到 3.10 python -m venv color_env # 激活环境Windows color_env\Scripts\activate # 激活环境Linux / macOS source color_env/bin/activate # 安装依赖 pip install -r requirements.txtrequirements.txt里通常列的是torch、torchvision、numpy、Pillow这几样。这里有个血泪经验如果你机器上有 GPU 但装的是 CPU 版 PyTorch跑起来不会报错只是慢得让你怀疑人生。判断方法很简单在 Python 里执行import torch; print(torch.cuda.is_available())返回False就说明当前装的是 CPU 版。着色模型本身不大CPU 也能跑但如果你要批量处理几十张图还是建议按 PyTorch 官网的对应命令重装 GPU 版。另一个容易翻车的地方是 Pillow 的版本。老版本的Image.LANCZOS在新版 Pillow 里已经改名成Image.Resampling.LANCZOS如果util.py里用的是旧写法运行时会报AttributeError。遇到这个不用慌要么降 Pillow 版本要么手动把util.py里对应的常量改掉。2.2 加载预训练模型与推理流程环境好了之后核心操作就是加载模型然后喂图。demo_release.py里已经写好了完整的流程但如果你想在自己的脚本里调用关键就两行import colorizers # 加载 ECCV16 模型eval() 切换到推理模式 colorizer_eccv16 colorizers.eccv16().eval() # 加载 SIGGRAPH17 模型 colorizer_siggraph17 colorizers.siggraph17().eval()这两个模型在初始化时会自动下载预训练权重权重文件不大但下载走的是外网如果卡住了就多试几次或者手动把权重放到对应缓存目录。eval()这一步不能省它会把 BatchNorm 和 Dropout 切到推理模式否则输出的颜色会飘。推理的完整链路在demo_release.py里体现得很清楚先把输入图转成 Lab 空间只取 L 通道亮度然后 resize 到 256x256 送进网络网络输出 ab 两个通道的预测值再把预测的 ab 和原始分辨率的 L 通道拼回去最后转回 RGB 保存。这个降分辨率推理再升回来的策略是为了省显存和提速代价是边缘细节会糊一点。# 命令行直接跑单张图 python demo_release.py -i imgs/ansel_adams3.jpg跑完之后结果默认输出到imgs_out目录文件名和输入对应。仓库里已经放了saved_eccv16.png和saved_siggraph17.png两张参考输出你可以拿自己的结果跟它们对比确认流程没跑偏。2.3 两个模型的选型差异ECCV16 和 SIGGRAPH17 不是简单的版本迭代关系它们的网络结构和着色风格有实际区别。ECCV16 是较早的版本网络更轻推理更快但颜色偏保守遇到大面积天空或草地时容易给出偏灰的结果。SIGGRAPH17 在结构上做了改进加入了更强的特征融合颜色更鲜艳但偶尔会在不该上色的地方比如纯文字区域引入色偏。对比项ECCV16SIGGRAPH17推理速度较快稍慢颜色饱和度偏保守偏鲜艳边缘保持一般较好适合场景快速预览、批量粗筛单张精修、展示输出我一般会先用 ECCV16 跑一遍看整体色调方向对不对如果觉得太灰再换 SIGGRAPH17 跑同一张图对比。两个模型都跑一遍的成本很低没必要纠结选哪个。3. 预处理和后处理的参数细节Lab 空间、256 尺寸与拼接逻辑3.1 为什么是 Lab 而不是 RGB直接把 RGB 三通道丢给网络预测效果会差很多。原因是 RGB 三个通道高度相关亮度变化和颜色变化耦合在一起网络很难学。Lab 空间把亮度L和颜色a、b解耦了着色任务只需要预测 a 和 b 两个通道L 通道保持原图不变。这样网络的学习目标更明确输出的颜色也更稳定。在util.py里能看到具体的转换代码用的是skimage.color.rgb2lab或者 OpenCV 的cv2.cvtColor。两者在数值范围上有差异skimage 的 L 范围是 0 到 100a 和 b 是 -128 到 127OpenCV 的 L 是 0 到 255a 和 b 是 0 到 255。如果你自己改代码一定要注意当前用的是哪套归一化方式不对的话输出会全黑或者全灰。3.2 256x256 这个尺寸是怎么来的网络输入固定为 256x256这不是随便定的。训练时用的就是 256x256 的裁剪块所以推理时也必须对齐这个尺寸。如果你把原图直接缩到 256x256长宽比会变人脸会被拉扁。正确的做法是先按长边缩放到 256短边按比例缩放后做 padding或者直接 resize 但接受形变。demo_release.py里的做法比较直接就是 resize 到 256x256。对于大多数风景照和人物照形变在可接受范围内。但如果你处理的是建筑照或者有明显直线条的画面形变会很明显。改进思路是改成保持长宽比的 resize 加 padding推理完再把 padding 裁掉。from PIL import Image import numpy as np def preprocess(img_path, size256): img Image.open(img_path).convert(RGB) # 保持长宽比的 resize w, h img.size scale size / max(w, h) new_w, new_h int(w * scale), int(h * scale) img_resized img.resize((new_w, new_h), Image.BICUBIC) # padding 到 256x256 canvas Image.new(RGB, (size, size), (0, 0, 0)) canvas.paste(img_resized, ((size - new_w) // 2, (size - new_h) // 2)) return canvas, (w, h, new_w, new_h)这段代码的逻辑是先算缩放比例让长边对齐 256短边按比例缩放然后把缩放后的图贴到 256x256 的黑色画布中央。推理完之后根据记录的偏移量把结果裁出来再放大回原始分辨率。这样处理过的图边缘不会出现拉伸伪影。3.3 后处理拼接与全分辨率恢复网络输出的是 256x256 的 ab 通道要把它和原始分辨率的 L 通道拼在一起。这里有个细节如果你在预处理时做了 padding拼接前要先把 ab 通道裁到和缩放后图像一样的尺寸再 resize 回原始分辨率。直接 resize 会导致颜色在 padding 区域也产生预测值拼回去之后边缘会出现色块。import torch import numpy as np from skimage.color import lab2rgb def postprocess(ab_output, L_original, original_size): # ab_output 是网络输出的 256x256 的 ab 通道 # L_original 是原始分辨率的 L 通道 ab ab_output.cpu().numpy().transpose(1, 2, 0) # 把 ab resize 回原始分辨率 ab_resized np.array(Image.fromarray(ab).resize(original_size, Image.BICUBIC)) # 拼接 L 和 ab lab np.concatenate([L_original[:, :, np.newaxis], ab_resized], axis2) # 转回 RGB rgb lab2rgb(lab) return (rgb * 255).astype(np.uint8)参数说明ab_output的形状是[1, 2, 256, 256]去掉 batch 维度后转成 HWC 格式L_original是原始分辨率的亮度通道范围要和 ab 对齐lab2rgb输出的范围是 0 到 1 的浮点数乘 255 再转 uint8 才能保存成正常图片。注意Lab 转 RGB 时如果 a 或 b 的值超出训练分布范围会出现颜色溢出表现为某些区域突然出现荧光色。遇到这种情况可以在拼接前对 ab 做 clip限制在 [-128, 127] 之间。4. 避坑与排查着色结果发灰、报错和显存不够怎么办4.1 输出全灰或颜色极淡现象跑完模型输出的图几乎是灰的只有轻微色偏。原因最常见的是输入图被当成了已经是彩色的图L 通道提取时没有正确丢弃原有的 ab 信息。另一个可能是模型权重没加载成功网络输出接近零值。解决确认输入图是真正的灰度图或者在代码里强制转成灰度再转回 RGB 走流程。检查模型加载时有没有打印权重加载的日志如果没有手动指定权重路径。4.2 报错 CUDA out of memory现象用 GPU 跑的时候报显存不足尤其是批量处理多张图时。原因模型本身不大但如果你把 batch size 设得太大或者同时加载了两个模型显存占用会叠加。解决把 batch size 降到 1处理完一张图后手动torch.cuda.empty_cache()。如果还是不够切到 CPU 跑着色模型在 CPU 上单张图也就几秒的事。4.3 输出图片尺寸和原图不一致现象保存出来的图和输入图分辨率不一样或者长宽比变了。原因预处理时 resize 到 256x256 之后后处理没有正确恢复到原始尺寸或者恢复时用了错误的插值方式。解决在预处理阶段记录原始尺寸和缩放比例后处理时严格按照记录的参数还原。插值方式建议用 BICUBICNEAREST 会产生锯齿。4.4 某些区域出现异常色块现象天空、墙面等大面积纯色区域出现不自然的色斑或条纹。原因网络在 256x256 分辨率下推理再放大回高分辨率时低分辨率预测的块状效应被放大了。另外如果原图有压缩伪影也会被网络放大成色块。解决对输入图先做一次轻微的降噪比如高斯模糊 sigma0.5减少压缩伪影的干扰。如果色块问题严重可以尝试把输入裁剪成多个 256x256 的块分别推理再拼接但要注意块与块之间的颜色一致性。4.5 模型权重下载失败现象第一次运行时卡在下载权重那一步或者报连接超时。原因权重文件托管在外部服务器网络不稳定时容易失败。解决找到代码里指定的权重 URL手动下载后放到缓存目录。PyTorch 的缓存目录通常在~/.cache/torch/hub/checkpoints/把文件放进去再跑就能跳过下载。5. 进阶玩法批量处理、效果对比与二次开发切入点5.1 批量处理整个文件夹单张跑通了之后批量处理是自然需求。写一个循环遍历imgs目录对每张图调用推理流程输出到imgs_out。这里的关键是异常处理——某张图格式不对或者损坏时不能让整个脚本挂掉。import os from pathlib import Path input_dir Path(imgs) output_dir Path(imgs_out) output_dir.mkdir(exist_okTrue) for img_path in input_dir.glob(*.jpg): try: # 调用你的推理函数 result run_colorization(img_path) save_path output_dir / f{img_path.stem}_color.png result.save(save_path) print(fOK: {img_path.name}) except Exception as e: print(fFAIL: {img_path.name} - {e})这段代码用pathlib遍历目录try/except保证单张失败不影响后续。输出文件名加了_color后缀避免和原图混淆。如果你要处理 PNG 或 JPEG 混合的目录把 glob 改成*.*再在循环里判断后缀。5.2 两个模型输出对比的自动化想直观对比 ECCV16 和 SIGGRAPH17 的效果可以写一个脚本同时跑两个模型把结果横向拼接成一张对比图。from PIL import Image def compare_models(img_path): # 分别用两个模型推理 out_eccv run_with_model(img_path, eccv16) out_siggraph run_with_model(img_path, siggraph17) # 横向拼接 w, h out_eccv.size canvas Image.new(RGB, (w * 2, h)) canvas.paste(out_eccv, (0, 0)) canvas.paste(out_siggraph, (w, 0)) canvas.save(fcompare_{Path(img_path).stem}.png)拼接后的图左边是 ECCV16右边是 SIGGRAPH17一眼就能看出颜色倾向的差异。我一般会拿几张不同类型的图人像、风景、建筑各跑一遍心里就有数了。5.3 二次开发的几个切入点这个仓库的代码结构很清晰适合做二次开发。几个常见的改法一是换损失函数重新训练但训练代码在 Caffe 分支里已经不再维护想训练的话得自己用 PyTorch 重写训练循环二是改网络输入尺寸把 256 改成 512但需要重新训练或者至少做 fine-tune直接改推理尺寸效果会崩三是在后处理阶段加一个颜色校正模块比如用直方图匹配把输出色调往参考图靠。还有一个取巧的玩法把着色结果当作初始化再叠一层传统图像处理的滤镜比如锐化或者局部对比度增强能让输出看起来更通透。这个不算模型层面的改进但在实际展示场景里很管用。5.4 验证输出是否正常的快速方法跑完一张图之后怎么判断结果是不是合理的我的习惯是看三个地方一是天空区域正常应该偏蓝或偏灰如果出现绿色或紫色说明 ab 通道预测异常二是人脸区域肤色应该自然如果偏绿或偏红可能是模型对肤色先验不够强三是整体亮度分布着色不应该改变原图的明暗关系如果输出比输入明显变亮或变暗说明 L 通道在拼接时出了问题。从那以后我每次跑完着色都会先把输出图和输入图并排放在一起扫一眼确认亮度结构没变、颜色没有大面积溢出再往下做批量处理。希望帮到你。本文还有配套的精品资源点击获取
返回列表