摘要: 照片承载了很多人在某个时刻的记忆,尤其是一些老旧的黑白照片,尘封于脑海之中,随着时间的流逝,记忆中对当时颜色的印象也会慢慢消散,这确实有些可惜。技术的发展会解决一些现有的难题,深度学习恰好能够解决这个问题。

人工智能和深度学习技术逐渐在各行各业中发挥着作用,尤其是在计算机视觉领域,深度学习就像继承了某些上帝的功能,无所不能,令人叹为观止。照片承载了很多人在某个时刻的记忆,尤其是一些老旧的黑白照片,尘封于脑海之中,随着时间的流逝,记忆中对当时颜色的印象也会慢慢消散,这确实有些可惜。但随着科技的发展,这些已不再是比较难的问题。在这篇文章中,将带领大家领略一番深度学习的强大能力——将灰度图像转换为彩色图像。文章使用PyTorch从头开始构建一个机器学习模型,自动将灰度图像转换为彩色图像,并且给出了相应代码及图像效果图。整篇文章都是通过iPython Notebook中实现,对性能的要求不高,读者们可以自行动手实践一下在各自的计算机上运行下,亲身体验下深度学习神奇的效果吧。

PS:不仅能够对旧图像进行着色,还可以对视频(每次对视频进行一帧处理)进行着色哦!闲话少叙,下面直接进入正题吧。

简介

在图像着色任务中,我们的目标是在给定灰度输入图像的情况下生成彩色图像。这个问题是具有一定的挑战性,因为它是多模式的——单个灰度图像可能对应许多合理的彩色图像。因此,传统模型通常依赖于重要的用户输入以及输入的灰度图像内容。

最近,深层神经网络在自动图像着色方面取得了显着的成功——从灰度到彩色,无需额外的人工输入。这种成功的部分原因在于深层神经网络能够捕捉和使用语义信息(即图像的实际内容),尽管目前还不能够确定这些类型的模型表现如此出色的原因,因为深度学习类似于黑匣子,暂时无法弄清算法是如何自动学习,后续会朝着可解释性研究方向发展。

在解释模型之前,首先以更精确地方式阐述我们所面临的问题。

问题

我们的目的是要从灰度图像中推断出每个像素(亮度、饱和度和色调)具有3个值的全色图像,对于灰度图而言,每个像素仅具有1个值(仅亮度)。为简单起见,我们只能处理大小为256 x 256的图像,所以我们的输入图像大小为256 x 256 x 1(亮度通道),输出的图像大小为256 x 256 x 2(另两个通道)。

正如人们通常所做的那样,我们不是用RGB格式的图像进行处理,而是使用LAB色彩空间(亮度,A和B)。该色彩空间包含与RGB完全相同的信息,但它将使我们能够更容易地将亮度通道与其他两个(我们称之为A和B)分开。在稍后会构造一个辅助函数来完成这个转换过程。

此外将尝试直接预测输入图像的颜色值(即回归)。还有其他更有趣的分类方法,但目前坚持使用回归方法,因为它很简单且效果很好。

数据

着色数据无处不在,这是由于我们可以从任何一张彩色图像中提取出灰度通道。对于本文项目,我们将使用MIT地点数据集中的一个子集,该子数据集包含地点、景观和建筑物。

# Download and unzip (2.2GB)

!wget http://data.csail.mit.edu/places/places205/testSetPlaces205_resize.tar.gz

!tar -xzf testSetPlaces205_resize.tar.gz

# Move data into training and validation directories

import os

os.makedirs('images/train/class/', exist_ok=True) # 40,000 images

os.makedirs('images/val/class/', exist_ok=True) # 1,000 images

for i, file in enumerate(os.listdir('testSet_resize')):

if i < 1000: # first 1000 will be val

os.rename('testSet_resize/' + file, 'images/val/class/' + file)

else: # others will be val

os.rename('testSet_resize/' + file, 'images/train/class/' + file)

# Make sure the images are there

from IPython.display import Image, display

display(Image(filename='images/val/class/84b3ccd8209a4db1835988d28adfed4c.jpg'))

工具

本文使用PyTorch构建和训练搭建的模型。此外,我们还了使用torchvision工具,该工具在PyTorch中处理图像和视频时很有用,以及使用了scikit-learn工具,用于在RGB和LAB颜色空间之间进行转换。

# Download and import libraries

!pip install torch torchvision matplotlib numpy scikit-image pillow==4.1.1

# For plotting

import numpy as np

import matplotlib.pyplot as plt

%matplotlib inline

# For conversion

from skimage.color import lab2rgb, rgb2lab, rgb2gray

from skimage import io

# For everything

import torch

import torch.nn as nn

import torch.nn.functional as F

# For our model

import torchvision.models as models

from torchvision import datasets, transforms

# For utilities

import os, shutil, time

# Check if GPU is available

use_gpu = torch.cuda.is_available()

模型

模型采用卷积神经网络构建而成,与传统的卷积神经网络模型类似,首先应用一些卷积层从图像中提取特征,然后将反卷积层应用于高级(增加空间分辨率)特征。

具体来说,模型采用的是迁移学习的方法,基础是ResNet-18模型,ResNet-18网络具有18层结构以及剩余连接的图像分类网络层。我们修改了该网络的第一层,以便它接受灰度输入而不是彩色输入,并且切断了第六层后面的网络结构:

现在,在代码中定义后续的网络模型,将从网络的后半部分开始,即上采样层:

class ColorizationNet(nn.Module):

def __init__(self, input_size=128):

super(ColorizationNet, self).__init__()

MIDLEVEL_FEATURE_SIZE = 128

## First half: ResNet

resnet = models.resnet18(num_classes=365)

# Change first conv layer to accept single-channel (grayscale) input

resnet.conv1.weight = nn.Parameter(resnet.conv1.weight.sum(dim=1).unsqueeze(1))

# Extract midlevel features from ResNet-gray

self.midlevel_resnet = nn.Sequential(*list(resnet.children())[0:6])

## Second half: Upsampling

self.upsample = nn.Sequential(

nn.Conv2d(MIDLEVEL_FEATURE_SIZE, 128, kernel_size=3, stride=1, padding=1),

nn.BatchNorm2d(128),

nn.ReLU(),

nn.Upsample(scale_factor=2),

nn.Conv2d(128, 64, kernel_size=3, stride=1, padding=1),

nn.BatchNorm2d(64),

nn.ReLU(),

nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),

nn.BatchNorm2d(64),

nn.ReLU(),

nn.Upsample(scale_factor=2),

nn.Conv2d(64, 32, kernel_size=3, stride=1, padding=1),

nn.BatchNorm2d(32),

nn.ReLU(),

nn.Conv2d(32, 2, kernel_size=3, stride=1, padding=1),

nn.Upsample(scale_factor=2)

)

def forward(self, input):

# Pass input through ResNet-gray to extract features

midlevel_features = self.midlevel_resnet(input)

# Upsample to get colors

output = self.upsample(midlevel_features)

return output

现在通过下面的代码创建整个模型:

model = ColorizationNet()

训练

损失函数

由于使用的是回归方法,所以使用的仍然是均方误差损失函数:尝试最小化预测的颜色值与真实(实际值)颜色值之间的平方距离。

criterion = nn.MSELoss()

由于问题的多形式性,上述损失函数对于着色有一点小的问题。例如,如果一件灰色的衣服可能是红色或蓝色,而模型若选择错误的颜色时,则会受到严厉的惩罚。因此,构建的模型通常会选择与饱和度鲜艳的颜色相比不太可能“非常错误”的不饱和颜色。关于这个问题已经有了重要的研究(参见Zhang等人),但是本文将坚持这种损失函数,就是这么任性。

优化

使用Adam优化器优化选定的损失函数(标准)。

optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=0.0)

加载数据

使用torchtext来加载数据,由于我们需要LAB空间中的图像,所以首先必须定义一个自定义数据加载器(dataloader)来转换图像。

class GrayscaleImageFolder(datasets.ImageFolder):

'''Custom images folder, which converts images to grayscale before loading'''

def __getitem__(self, index):

path, target = self.imgs[index]

img = self.loader(path)

if self.transform is not None:

img_original = self.transform(img)

img_original = np.asarray(img_original)

img_lab = rgb2lab(img_original)

img_lab = (img_lab + 128) / 255

img_ab = img_lab[:, :, 1:3]

img_ab = torch.from_numpy(img_ab.transpose((2, 0, 1))).float()

img_original = rgb2gray(img_original)

img_original = torch.from_numpy(img_original).unsqueeze(0).float()

if self.target_transform is not None:

target = self.target_transform(target)

return img_original, img_ab, target

接下来,对训练数据和验证数据定义变换。

# Training

train_transforms = transforms.Compose([transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip()])

train_imagefolder = GrayscaleImageFolder('images/train', train_transforms)

train_loader = torch.utils.data.DataLoader(train_imagefolder, batch_size=64, shuffle=True)

# Validation

val_transforms = transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224)])

val_imagefolder = GrayscaleImageFolder('images/val' , val_transforms)

val_loader = torch.utils.data.DataLoader(val_imagefolder, batch_size=64, shuffle=False)

辅助函数

在进行训练之前,定义了辅助函数来跟踪训练损失并将图像转换回RGB图像。

class AverageMeter(object):

'''A handy class from the PyTorch ImageNet tutorial'''

def __init__(self):

self.reset()

def reset(self):

self.val, self.avg, self.sum, self.count = 0, 0, 0, 0

def update(self, val, n=1):

self.val = val

self.sum += val * n

self.count += n

self.avg = self.sum / self.count

def to_rgb(grayscale_input, ab_input, save_path=None, save_name=None):

'''Show/save rgb image from grayscale and ab channels

Input save_path in the form {'grayscale': '/path/', 'colorized': '/path/'}'''

plt.clf() # clear matplotlib

color_image = torch.cat((grayscale_input, ab_input), 0).numpy() # combine channels

color_image = color_image.transpose((1, 2, 0)) # rescale for matplotlib

color_image[:, :, 0:1] = color_image[:, :, 0:1] * 100

color_image[:, :, 1:3] = color_image[:, :, 1:3] * 255 - 128

color_image = lab2rgb(color_image.astype(np.float64))

grayscale_input = grayscale_input.squeeze().numpy()

if save_path is not None and save_name is not None:

plt.imsave(arr=grayscale_input, fname='{}{}'.format(save_path['grayscale'], save_name), cmap='gray')

plt.imsave(arr=color_image, fname='{}{}'.format(save_path['colorized'], save_name))

验证

在验证过程中,使用torch.no_grad()函数简单地运行下没有反向传播的模型。

def validate(val_loader, model, criterion, save_images, epoch):

model.eval()

# Prepare value counters and timers

batch_time, data_time, losses = AverageMeter(), AverageMeter(), AverageMeter()

end = time.time()

already_saved_images = False

for i, (input_gray, input_ab, target) in enumerate(val_loader):

data_time.update(time.time() - end)

# Use GPU

if use_gpu: input_gray, input_ab, target = input_gray.cuda(), input_ab.cuda(), target.cuda()

# Run model and record loss

output_ab = model(input_gray) # throw away class predictions

loss = criterion(output_ab, input_ab)

losses.update(loss.item(), input_gray.size(0))

# Save images to file

if save_images and not already_saved_images:

already_saved_images = True

for j in range(min(len(output_ab), 10)): # save at most 5 images

save_path = {'grayscale': 'outputs/gray/', 'colorized': 'outputs/color/'}

save_name = 'img-{}-epoch-{}.jpg'.format(i * val_loader.batch_size + j, epoch)

to_rgb(input_gray[j].cpu(), ab_input=output_ab[j].detach().cpu(), save_path=save_path, save_name=save_name)

# Record time to do forward passes and save images

batch_time.update(time.time() - end)

end = time.time()

# Print model accuracy -- in the code below, val refers to both value and validation

if i % 25 == 0:

print('Validate: [{0}/{1}]\t'

'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t'

'Loss {loss.val:.4f} ({loss.avg:.4f})\t'.format(

i, len(val_loader), batch_time=batch_time, loss=losses))

print('Finished validation.')

return losses.avg

训练

在训练过程中,使用loss.backward()运行模型并进行反向传播过程。我们首先定义了一个训练一个epoch的函数:

def train(train_loader, model, criterion, optimizer, epoch):

print('Starting training epoch {}'.format(epoch))

model.train()

# Prepare value counters and timers

batch_time, data_time, losses = AverageMeter(), AverageMeter(), AverageMeter()

end = time.time()

for i, (input_gray, input_ab, target) in enumerate(train_loader):

# Use GPU if available

if use_gpu: input_gray, input_ab, target = input_gray.cuda(), input_ab.cuda(), target.cuda()

# Record time to load data (above)

data_time.update(time.time() - end)

# Run forward pass

output_ab = model(input_gray)

loss = criterion(output_ab, input_ab)

losses.update(loss.item(), input_gray.size(0))

# Compute gradient and optimize

optimizer.zero_grad()

loss.backward()

optimizer.step()

# Record time to do forward and backward passes

batch_time.update(time.time() - end)

end = time.time()

# Print model accuracy -- in the code below, val refers to value, not validation

if i % 25 == 0:

print('Epoch: [{0}][{1}/{2}]\t'

'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t'

'Data {data_time.val:.3f} ({data_time.avg:.3f})\t'

'Loss {loss.val:.4f} ({loss.avg:.4f})\t'.format(

epoch, i, len(train_loader), batch_time=batch_time,

data_time=data_time, loss=losses))

print('Finished training epoch {}'.format(epoch))

接下来,我们定义一个循环训练函数,即训练100个epoch:

# Move model and loss function to GPU

if use_gpu:

criterion = criterion.cuda()

model = model.cuda()

# Make folders and set parameters

os.makedirs('outputs/color', exist_ok=True)

os.makedirs('outputs/gray', exist_ok=True)

os.makedirs('checkpoints', exist_ok=True)

save_images = True

best_losses = 1e10

epochs = 100

# Train model

for epoch in range(epochs):

# Train for one epoch, then validate

train(train_loader, model, criterion, optimizer, epoch)

with torch.no_grad():

losses = validate(val_loader, model, criterion, save_images, epoch)

# Save checkpoint and replace old best model if current model is better

if losses < best_losses:

best_losses = losses

torch.save(model.state_dict(), 'checkpoints/model-epoch-{}-losses-{:.3f}.pth'.format(epoch+1,losses))

Starting training epoch 0 ...

预训练模型

如果你想运用预训练模型而不想从头开始训练的话,我已经为你训练了好一个模型。该模型在少量时间内接受相对少量的数据训练,并且能够工作正常。可以从下面的链接下载并使用它:

# Download pretrained model

!wget https://www.dropbox.com/s/kz76e7gv2ivmu8p/model-epoch-93.pth

#https://www.dropbox.com/s/9j9rvaw2fo1osyj/model-epoch-67.pth

# Load model

pretrained = torch.load('model-epoch-93.pth', map_location=lambda storage, loc: storage)

model.load_state_dict(pretrained)

# Validate

save_images = True

with torch.no_grad():

validate(val_loader, model, criterion, save_images, 0)

Validate: [0/16] Time 10.628 (10.628) Loss 0.0030 (0.0030)

Validate: [16/16] Time 0.328 ( 0.523) Loss 0.0029 (0.0029)

结果

有趣的内容到了,让我们看看深度学习技术实现的效果吧!

# Show images

import matplotlib.image as mpimg

image_pairs = [('outputs/color/img-2-epoch-0.jpg', 'outputs/gray/img-2-epoch-0.jpg'),

('outputs/color/img-7-epoch-0.jpg', 'outputs/gray/img-7-epoch-0.jpg')]

for c, g in image_pairs:

color = mpimg.imread(c)

gray = mpimg.imread(g)

f, axarr = plt.subplots(1, 2)

f.set_size_inches(15, 15)

axarr[0].imshow(gray, cmap='gray')

axarr[1].imshow(color)

axarr[0].axis('off'), axarr[1].axis('off')

plt.show()

结论

在这篇文章中,使用PyTorch工具从头创建了一个简单的自动图像着色器,没有太复杂的代码,只需要简单的准备好数据并设计好合理的模型即可得到令人令人兴奋的结果,此外,这仅仅只是起步,后续还有很多地方可以进行改进优化并进行推广。

本文作者:【方向】

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

python图片自动上色_老旧黑白片修复机——使用卷积神经网络图像自动着色实战(附PyTorch代码)...相关推荐

  1. 双服务器架构实战飞桨部署-自动上色和老相片修复

    双服务器架构实战飞桨部署-自动上色和老相片修复 宋 朱熹 <即事有怀寄彦辅仲宗二兄>诗之二:"闻说双飞桨,翩然下广津. 飞桨功能强大,部署非常简单方便(对我这个不熟练的人,并不是 ...

  2. 使用卷积神经网络的自动心电图诊断

    使用卷积神经网络的自动心电图诊断 Automatic ECG Diagnosis Using Convolutional Neural Network https://www.mdpi.com/207 ...

  3. python图片自动上色_通过深度学习方法为黑白老照片自动上色,带我们重新忆起那段老时光!...

    我们知道,深度学习几乎已经应用在每一个领域,但如果我们能够构建一个基于深度学习的模型,让它能够给老照片着色,重现我们童年的旧回忆,这该多么令人激动啊!那么我们要怎么做呢?本文的作者将为大家介绍一个教程 ...

  4. python图片自动上色_从零开始搭建某自动上色AI的运行环境

    style2paints(或PaintsTransfer,GitHub 链接:https://github.com/lllyasviel/style2paints)是一个可以给线稿自动上色的AI,这里 ...

  5. python图片转文字_【收藏】图片转成文字的方法总结,python批量图片转文字信息参考源码...

    在日常办公或者学习中,往往存在这样一个工作场景,比如,"老王,我这里有一张图片,你把里面的文字信息给我整理出来",都2021年了,你真的还在手敲图片文字信息么?那么还不赶紧收藏这篇 ...

  6. python 图片处理模块_(python)图片处理Pillow模块的使用

    Pillow中最重要的类就是Image,该类存在于同名的模块中.可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片. 还有一个类为ImageDraw,用来画图. 1. ...

  7. python图片字符分割_图像分割python

    常用的十大 python 图像处理工具 本文为 AI 研习社编译的技术博客,原标题 : 10 Python image manipulation tools. 作者 | Parul Pandey 翻译 ...

  8. python 图片比较 猫_我用Python实现了12500张猫狗图像的精准分类

    原标题:我用Python实现了12500张猫狗图像的精准分类 在这篇文章中,我们将展示如何建立一个深度神经网络,能做到以 90% 的精度来对图像进行分类,而在深度神经网络,特别是卷积神经网络兴起之前, ...

  9. 超出网络bios会话限制_?老旧BIOS说再见,拯救者系列设置超简单

    作为公司国宝级的男同事,日常工作自然少不了要帮女同事修修电脑,本想一顿操作猛如虎,不料打开BIOS就傻眼,上古时代的界面,全英文操作,实在无从下手.实际上,目前已经有很多电脑使用简单的BIOS图形界面 ...

最新文章

  1. CVPR 2021 | 港科大:如何利用闪光图像(flash image)来去除反光
  2. linux命令gcc常用选项
  3. python接口自动化-参数化
  4. python 中给文件加锁——fcntl模块
  5. 3.爱跑步的蜗壳人动态(下同)
  6. Keil5 编译生成bin二进制文件的设置方法
  7. taskkill无法终止进程 拒绝访问_解决删除文件时,提示无法删除的问题
  8. Spring Data JPA 从入门到精通~查询结果的处理
  9. raid5坏了一块盘怎么办_机械硬盘的坏道处理——屏蔽之
  10. 安卓手机能用吗_手机才用两年卡的不行,是手机问题吗,想问手机最长能用几年?...
  11. Servlet 自动刷新页面
  12. JAVA451铝刀轮组怎么样,关于碳刀和铝刀轮组的区别
  13. Exchange企业实战技巧(1)验证安装及配置产品密钥
  14. 网络安全05_VMware 虚拟机软件安装_准备Kali- Linux虚拟机_Windows Server 2003 Enterprise 虚拟机下载和安装
  15. 树莓派-内核启动报错mmc0: error -5 whilst initialising MMC card
  16. easyExcel导出数据并设置单元格下拉数据
  17. oracle查询谁修改了数据ip,查询oracle特定表修改的用户及IP信息
  18. 鸟哥的服务器《十三》Web服务器
  19. docker容器运行不退出的方法
  20. 我的世界无限法则服务器怎么用,我的世界无限法则版

热门文章

  1. win10 悬浮日历_Win10日历隐藏功能:可直接打印空白月历
  2. 解释源代码文件、目标代码文件和可执行文件之间的区别
  3. 欧盟CE公告号-外贸人不得不了解的通关证书
  4. 复变函数 —— 0. 连接复数与三角函数的欧拉公式
  5. uinavgationcontroller
  6. windows PHP 5 版本的下载
  7. 2020移动apn接入点哪个快_为什么别人的4g网总比你快?手机这个设置没开启,难怪网络...
  8. 航空Ethernet嵌入式测试平台ETest
  9. 化工厂人员定位如何实现,可以解决哪些问题?
  10. NOIP CSP-J/S初赛知识