基于cv_unet_image-colorization的老照片修复项目:Python完整源码解析
基于cv_unet_image-colorization的老照片修复项目:Python完整源码解析
不知道你有没有翻过家里的老相册?那些泛黄的黑白照片,记录着爷爷奶奶年轻时的模样,或是父母童年的瞬间。但时间久了,照片会褪色、发黄,甚至出现划痕,那些珍贵的记忆也随之变得模糊。以前,给这些老照片上色、修复,要么需要专业的设计师花大量时间手动处理,要么效果生硬不自然。
现在,情况不一样了。我最近用cv_unet_image-colorization这个模型,完整地跑通了一个老照片修复项目。效果怎么说呢,挺让人惊喜的。它不仅能智能地把黑白照片变成彩色,还能在一定程度上修复一些轻微的划痕和噪点,让老照片重新“活”过来。
这篇文章,我就带你一起看看这个项目的里里外外。我们不只讲效果,更会深入项目内部,看看代码是怎么写的,模型是怎么工作的,以及最终呈现出来的前后对比到底有多明显。如果你对用Python玩转AI图像处理感兴趣,或者手头正好有些老照片想处理,那这篇内容应该能给你不少实用的参考。
1. 项目效果:让记忆重现色彩
在深入代码之前,我们先看看这个模型到底能做什么。毕竟,效果才是最有说服力的。
我找了几张有代表性的老照片进行测试,涵盖了人物肖像、风景和家庭合照等常见场景。修复过程完全自动化,只需要输入黑白图片,模型就会输出彩色结果,同时进行基础的画质增强。
1.1 人物肖像的色彩重生
我首先测试了一张单人男性肖像照。原图是典型的黑白照片,对比度较高,面部细节清晰但毫无色彩。经过模型处理后,效果出来了。
- 肤色还原:模型准确地为人脸和手部皮肤赋予了自然的、带有血色的肉色,而不是那种常见的、蜡像般的粉色或黄色。脸颊处还隐约透出一点红润感,这让照片看起来生动了不少。
- 衣物与背景:人物所穿的深色外套被还原为藏青色,而背景的墙壁则呈现出淡淡的米黄色。颜色的过渡比较柔和,没有出现大块的色块或不自然的边界。
- 整体观感:上色后的照片,瞬间从“历史档案”变成了“鲜活记忆”。色彩的加入极大地增强了照片的立体感和时代氛围,你会不自觉地开始想象照片拍摄时的场景和光线。
1.2 复杂场景与家庭合照
接下来是一张户外风景照和一张多人家庭合照,这对模型的场景理解能力提出了更高要求。
- 风景照处理:一张有树木、天空和草地的黑白风景照。模型成功地将天空识别并渲染为浅蓝色,草地为绿色,树干为棕色。虽然树叶的颜色层次不算极其丰富,但整体的色彩搭配非常和谐,完全符合我们对自然风景的认知。
- 家庭合照挑战:合照里有不同年龄、穿着不同颜色衣物的人。模型的表现令人印象深刻。它能区分开不同人的衣物,并赋予不同的颜色(如红色、蓝色、格纹),且没有出现颜色“串染”到旁边人物身上的情况。每个人的肤色也根据年龄略有差异,孩子的肤色更明亮一些。
- 划痕与噪点改善:对于原图上一些细小的白色划痕和因年代久远产生的颗粒噪点,模型在色彩化的过程中,似乎对其有一定的“融合”与“覆盖”作用。修复后的图片上,这些瑕疵虽然未被完全擦除,但变得不那么显眼了,整体画面干净了许多。
简单来说,这个模型不是简单粗暴地给物体涂色。它更像是一个理解了“世界应该是什么颜色”的智能助手,能根据图像的内容和结构,进行符合常理的、全局协调的色彩填充。对于保存尚可、只有褪色问题的老照片,其修复效果已经非常接近专业人工精修的水平,足以满足家庭纪念、历史资料数字化等大部分需求。
2. 项目架构与核心思路
看完了效果,我们来看看这个项目是怎么搭建起来的。一个好的项目结构能让开发和维护都轻松很多。
整个项目的文件夹结构很清晰,遵循了常见的机器学习项目规范:
old_photo_restoration/ ├── checkpoints/ # 存放预训练好的模型权重文件 ├── data/ │ ├── input/ # 存放待修复的黑白老照片 │ └── output/ # 存放模型修复后生成的彩色照片 ├── models/ # 模型定义的核心代码 │ └── unet_colorizer.py # U-Net着色模型结构 ├── utils/ # 工具函数 │ ├── image_processor.py # 图像加载、预处理、后处理 │ └── visualization.py # 效果对比图生成 ├── config.py # 配置文件,集中管理路径、参数 ├── colorize.py # 主程序,执行修复流程 └── requirements.txt # 项目依赖的Python库列表这个结构的核心思路是“管道化”处理。从把照片读进来,到预处理,扔给模型推理,再到后处理并保存结果,每一步都有明确的模块负责。config.py文件里集中了所有路径和参数,想换一批照片或者调整参数,改这一个文件就行,不用到处翻代码。
模型的核心是U-Net。这是一种在图像分割领域非常出名的网络结构,形状像一个“U”字。它先通过一系列层把图片压缩(编码),提取出高级特征,然后再一步步还原回原图大小(解码),同时在这个过程中融合之前提取的特征。这种结构特别适合像图片上色这种“像素到像素”的任务,因为它能在还原细节时,充分利用之前学到的全局信息。
对于老照片修复,这个模型在训练时,并不是用真正的黑白老照片和彩色原图来学的。那样数据太难找了。它用的是更巧妙的方法:用海量的彩色图片,人工去掉颜色变成黑白图作为输入,原始的彩色图作为目标,让模型学习这个“从黑白猜彩色”的映射关系。由于自然世界的色彩分布是有很强规律性的(天是蓝的,草是绿的,皮肤是某种肉色),模型学到的这个规律,同样可以应用到真正的老照片上。
3. 核心源码深度解读
接下来,我们深入到几个关键的Python文件里,看看具体的代码是怎么实现的。我会把核心逻辑拆开揉碎了讲,保证即使你Python刚入门也能看懂个大概。
3.1 图像预处理与后处理 (utils/image_processor.py)
模型不能直接吃原始图片,需要先“加工”一下。这个文件里的函数就是负责这个的。
import cv2 import numpy as np def load_and_preprocess(image_path, target_size=(256, 256)): """ 加载一张图片并进行标准化预处理,准备输入模型。 参数: image_path: 图片文件路径。 target_size: 模型要求的输入尺寸,默认256x256。 返回: 处理好的numpy数组,形状为(1, H, W, 3)。 """ # 1. 用OpenCV读取图片 img = cv2.imread(image_path) if img is None: raise FileNotFoundError(f"无法读取图片: {image_path}") # 2. 转换为RGB颜色空间(OpenCV默认是BGR) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 3. 调整大小为模型需要的尺寸 img_resized = cv2.resize(img, target_size, interpolation=cv2.INTER_CUBIC) # 4. 归一化:将像素值从0-255缩放到0-1的浮点数范围,有助于模型稳定训练和推理 img_normalized = img_resized.astype(np.float32) / 255.0 # 5. 添加一个批次维度,因为模型通常接收的是批次数据,即使我们只处理一张图 # 形状从 (H, W, 3) 变为 (1, H, W, 3) img_batch = np.expand_dims(img_normalized, axis=0) return img_batch def postprocess_and_save(prediction, original_path, output_path): """ 将模型的输出(归一化的数组)转换回正常的图片并保存。 参数: prediction: 模型输出的数组,形状为(1, H, W, 3),值在0-1之间。 original_path: 原始黑白图片路径,用于获取原始尺寸。 output_path: 彩色输出图片的保存路径。 """ # 1. 移除批次维度,并缩放到0-255范围 # 先截断到[0,1]区间,防止极小值溢出 img_array = np.clip(prediction[0], 0, 1) img_array = (img_array * 255).astype(np.uint8) # 2. 读取原始图片,目的是获取原始尺寸,以便将结果缩放回去。 # 因为模型处理的是256x256的小图,我们需要把颜色“贴”回原图大小。 original_img = cv2.imread(original_path) original_h, original_w = original_img.shape[:2] # 3. 将模型输出的彩色小图,缩放到原始照片的尺寸。 # 注意:这里只是把“颜色信息”缩放了,更高级的做法是只在原图分辨率上应用颜色。 colored_fullsize = cv2.resize(img_array, (original_w, original_h), interpolation=cv2.INTER_CUBIC) # 4. 将原始黑白图从BGR转为Lab颜色空间。 # Lab空间的L通道代表明度(黑白信息),a和b通道代表颜色。 original_lab = cv2.cvtColor(original_img, cv2.COLOR_BGR2Lab) # 5. 获取原始图的L通道(明度,即黑白细节),这是我们想要保留的。 L_channel = original_lab[:, :, 0] # 6. 将我们预测的彩色图也转为Lab空间,并取出其a、b颜色通道。 colored_lab = cv2.cvtColor(colored_fullsize, cv2.COLOR_RGB2Lab) ab_channels = colored_lab[:, :, 1:] # 7. 融合:用原始高清的L通道 + 模型预测的ab颜色通道,合并成新的Lab图像。 # 这样能保证最终彩图的细节清晰度和原始黑白图一致。 merged_lab = np.concatenate([L_channel[:, :, np.newaxis], ab_channels], axis=2) # 8. 将融合后的Lab图转回BGR颜色空间,并用OpenCV保存。 merged_bgr = cv2.cvtColor(merged_lab, cv2.COLOR_Lab2BGR) cv2.imwrite(output_path, merged_bgr) print(f"彩色化图片已保存至: {output_path}")代码解读:
load_and_preprocess函数是预处理流水线。它负责把一张普通的jpg或png图片,变成模型能“消化”的格式。关键步骤是尺寸调整和归一化。你可以把target_size调大,比如(512,512),可能会得到细节更丰富的颜色,但计算也会更慢。postprocess_and_save函数是后处理魔法所在,也是最体现工程技巧的部分。模型预测的是小图上的颜色,直接放大会模糊。这里的技巧是使用Lab颜色空间。- 原始高清黑白图提供了完美的细节(L通道)。
- 模型预测的彩色小图提供了颜色信息(a,b通道)。
- 我们把两者结合——用原图的细节,加上模型预测的颜色——再转回普通的彩色图。这样得到的最终图片,既保留了老照片原有的清晰纹理和轮廓,又拥有了自然生动的色彩。
3.2 模型定义与推理 (models/unet_colorizer.py)
这里是项目的大脑,定义了U-Net模型的结构。我们使用TensorFlow/Keras来搭建它。
import tensorflow as tf from tensorflow.keras import layers, Model def build_unet_model(input_shape=(256, 256, 3)): """ 构建用于图像着色的U-Net模型。 参数: input_shape: 输入图像的形状(高度,宽度,通道数)。 返回: 编译好的Keras模型。 """ inputs = layers.Input(shape=input_shape) # --- 编码器 (下采样) --- # 第一层 conv1 = layers.Conv2D(64, 3, activation='relu', padding='same')(inputs) conv1 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv1) pool1 = layers.MaxPooling2D(pool_size=(2, 2))(conv1) # 第二层 conv2 = layers.Conv2D(128, 3, activation='relu', padding='same')(pool1) conv2 = layers.Conv2D(128, 3, activation='relu', padding='same')(conv2) pool2 = layers.MaxPooling2D(pool_size=(2, 2))(conv2) # 第三层(瓶颈层) conv3 = layers.Conv2D(256, 3, activation='relu', padding='same')(pool2) conv3 = layers.Conv2D(256, 3, activation='relu', padding='same')(conv3) # --- 解码器 (上采样) --- # 第四层,上采样并拼接(跳跃连接) up4 = layers.UpSampling2D(size=(2, 2))(conv3) # 跳跃连接:将编码器第二层的输出与上采样结果在通道维度拼接 merge4 = layers.concatenate([conv2, up4], axis=-1) conv4 = layers.Conv2D(128, 3, activation='relu', padding='same')(merge4) conv4 = layers.Conv2D(128, 3, activation='relu', padding='same')(conv4) # 第五层,上采样并拼接 up5 = layers.UpSampling2D(size=(2, 2))(conv4) # 跳跃连接:将编码器第一层的输出与上采样结果拼接 merge5 = layers.concatenate([conv1, up5], axis=-1) conv5 = layers.Conv2D(64, 3, activation='relu', padding='same')(merge5) conv5 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv5) # 最终输出层,用1x1卷积将通道数映射到3(RGB) outputs = layers.Conv2D(3, 1, activation='sigmoid')(conv5) # 创建模型 model = Model(inputs=inputs, outputs=outputs) # 编译模型(用于训练,推理时不需要) # model.compile(optimizer='adam', loss='mse', metrics=['accuracy']) return model def load_trained_model(checkpoint_path): """ 加载预训练好的模型权重。 参数: checkpoint_path: 模型权重文件(.h5)的路径。 返回: 加载好权重的模型,可以直接用于预测。 """ # 1. 先构建模型结构 model = build_unet_model() # 2. 加载预训练的权重 model.load_weights(checkpoint_path) print(f"已从 {checkpoint_path} 加载模型权重。") return model代码解读:
build_unet_model函数清晰地展示了U-Net的对称结构。编码器(conv1,conv2)通过卷积和池化层层提取特征,同时压缩图像尺寸。解码器(conv4,conv5)则通过上采样逐步恢复尺寸。- 跳跃连接(
layers.concatenate)是U-Net的灵魂。它把编码器对应层的特征图直接“抄近道”送到解码器。这样做的好处是,解码器在还原图片细节时,能同时利用高层语义信息(来自瓶颈层)和低层细节信息(来自编码器早期层),从而生成更精确、边缘更清晰的结果。对于上色任务,这能帮助模型更好地判断物体边界该上什么色。 load_trained_model函数展示了如何将我们定义的结构和他人训练好的权重结合起来。我们不需要自己从头训练(那需要海量数据和GPU时间),直接使用预训练模型进行推理即可,这是应用AI模型最高效的方式。
3.3 主程序流程 (colorize.py)
最后,我们把所有模块像拼积木一样组装起来,形成一个完整的、可执行的流程。
import os import glob from config import INPUT_DIR, OUTPUT_DIR, CHECKPOINT_PATH from models.unet_colorizer import load_trained_model from utils.image_processor import load_and_preprocess, postprocess_and_save from utils.visualization import create_comparison_grid def main(): """ 老照片修复主程序。 步骤:1.加载模型 -> 2.遍历输入图片 -> 3.预处理 -> 4.预测 -> 5.后处理保存。 """ # 0. 准备工作:创建输出目录 os.makedirs(OUTPUT_DIR, exist_ok=True) # 1. 加载预训练的颜色模型 print("正在加载颜色模型...") color_model = load_trained_model(CHECKPOINT_PATH) print("模型加载完毕。") # 2. 获取所有待处理的图片路径 # 支持jpg, png, jpeg格式 image_paths = glob.glob(os.path.join(INPUT_DIR, "*.jpg")) + \ glob.glob(os.path.join(INPUT_DIR, "*.png")) + \ glob.glob(os.path.join(INPUT_DIR, "*.jpeg")) if not image_paths: print(f"在目录 {INPUT_DIR} 中未找到图片文件。") return print(f"找到 {len(image_paths)} 张待处理图片。") # 3. 逐张处理图片 for i, img_path in enumerate(image_paths): print(f"\n正在处理第 {i+1}/{len(image_paths)} 张: {os.path.basename(img_path)}") try: # 3.1 预处理:加载并准备图片数据 input_batch = load_and_preprocess(img_path) # 3.2 推理:让模型预测颜色 # 这里的预测结果是一个值在0到1之间的数组 colored_output = color_model.predict(input_batch, verbose=0) # 3.3 后处理:将预测结果与原图融合,并保存 # 生成输出文件名,例如:old_photo_colorized.jpg base_name = os.path.splitext(os.path.basename(img_path))[0] output_filename = f"{base_name}_colorized.jpg" output_path = os.path.join(OUTPUT_DIR, output_filename) postprocess_and_save(colored_output, img_path, output_path) except Exception as e: print(f"处理图片 {img_path} 时出错: {e}") continue # 4. 所有图片处理完成后,生成效果对比图 print("\n所有图片处理完成!正在生成效果对比图...") create_comparison_grid(INPUT_DIR, OUTPUT_DIR) print("程序执行完毕。请查看输出目录下的结果。") if __name__ == "__main__": main()代码解读: 这个主程序就是一个标准的AI推理流水线,逻辑非常直白:
- 初始化:准备好输出文件夹,加载模型(最耗时的步骤,但只需一次)。
- 收集任务:找到输入文件夹里所有需要处理的图片。
- 循环处理:对每一张图,执行“预处理 -> 模型预测 -> 后处理保存”的标准三步走。
- 收尾工作:调用另一个工具函数,把所有修复前后的图片拼成一张对比图,方便我们直观地查看效果。
你可以把这个脚本当成一个工具。以后只要有新的老照片,扔进input文件夹,再运行一次这个脚本,就能在output文件夹里拿到彩色化的结果了。
4. 如何运行与使用建议
如果你已经迫不及待想试试了,可以按照下面几步来操作。整个过程在配备普通显卡(甚至只用CPU)的电脑上都能完成。
4.1 环境搭建与项目运行
- 准备环境:确保你的电脑安装了Python(建议3.8以上版本)。然后,根据项目里的
requirements.txt文件安装依赖库。通常主要需要tensorflow(或tensorflow-cpu)、opencv-python、numpy等。在命令行里进入项目目录,运行:pip install -r requirements.txt - 准备模型:你需要获取预训练好的
cv_unet_image-colorization模型权重文件(通常是一个.h5或.pb文件)。可以尝试在开源模型社区(如Hugging Face, ModelScope等)搜索相关名称找到资源。下载后,将其放入项目的checkpoints文件夹。 - 放入照片:把你的黑白老照片(jpg或png格式)放入
data/input文件夹。 - 修改配置:打开
config.py文件,确认里面的路径设置是否正确,特别是CHECKPOINT_PATH是否指向你刚下载的权重文件。 - 运行程序:在命令行中执行主程序:
程序会开始处理,并在控制台打印进度。处理速度取决于你的电脑性能和图片数量、大小。python colorize.py
4.2 使用技巧与注意事项
根据我的使用经验,有几个小技巧可以让修复效果更好:
- 图片质量是基础:模型对输入图片的质量有要求。尽量选择扫描清晰、对比度适中、主体完整的老照片。如果原图本身非常模糊、有大面积破损或污渍,模型也很难“无中生有”,效果会打折扣。
- 分辨率不是越高越好:虽然理论上可以处理大图,但模型是在固定分辨率(如256x256)上训练的。过大的图片会被压缩,可能丢失细节;且后处理融合步骤对超大图可能不完美。建议先将非常古老的小尺寸照片适度放大(用Photoshop或AI放大工具),再将中等分辨率(如1024x768左右)的图片输入模型,效果往往更均衡。
- 理解模型的“脑回路”:这个模型是根据大量现代彩色照片训练的,它的“色彩常识”来源于此。所以,对于某些特定历史时期的服饰颜色、旧式汽车的颜色等,它可能会按照现代常见色来渲染,不一定100%符合历史原貌。但这对于家庭怀旧用途来说,完全不是问题,甚至赋予了老照片一种新颖的、充满生命力的美感。
- 后续微调(可选):如果对某张照片的局部颜色不满意(比如觉得天空应该更蓝一点),可以用简单的图像处理软件(如Photoshop、GIMP甚至手机APP)进行微调。模型已经完成了最困难的、全局性的色彩重建工作,剩下的局部调整就简单多了。
运行完项目,看着那些黑白记忆一点点变得鲜活,确实是一件很有成就感的事情。这个项目代码结构清晰,把复杂的AI模型封装成了几个简单的步骤,非常适合作为学习AI图像处理实战的第一个项目。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
