手把手教你使用VGG19做图像风格迁移

手把手教你使用VGG19做图像风格迁移_第1张图片

前面文章中介绍了使用VGG19做图像风格迁移的基本原理,这篇文章中将为大家介绍使用tensorflow的VGG19版本做图像风格迁移的实验,教大家手把手进行图像的风格迁移。

项目代码使用的是下面链接中的代码,大家可以自行前往下载:

https://github.com/AaronJny/nerual_style_change

手把手教你使用VGG19做图像风格迁移_第2张图片

文件目录介绍

下载完成后解压得到的文件目录如下:

手把手教你使用VGG19做图像风格迁移_第3张图片

其中

images——风格图像和内容图像储存目录

sample——风格图像、内容图像和迁移图像的实例

models.py——VGG19模型文件

settings.py——参数设置文件

train.py——模型训练文件

images文件夹下共包含两幅图像content.jpg和style.jpg分别为内容图像和风格图像:

代码介绍

2.1 models.py介绍

# -*- coding: utf-8 -*-

import tensorflow as tf

import numpy as np

import settings

import scipy.io

import scipy.misc

 

class Model(object):

    def __init__(self, content_path, style_path):

        self.content = self.loadimg(content_path)  # 加载内容图片

        self.style = self.loadimg(style_path)  # 加载风格图片

        self.random_img = self.get_random_img()  # 生成噪音内容图片

        self.net = self.vggnet()  # 建立vgg网络

 

    def vggnet(self):

        # 读取预训练的vgg模型

        vgg = scipy.io.loadmat(settings.VGG_MODEL_PATH)

        vgg_layers = vgg['layers'][0]

        net = {}

        # 使用预训练的模型参数构建vgg网络的卷积层和池化层

        # 全连接层不需要

        # 注意,除了input之外,这里参数都为constant,即常量

        # 和平时不同,我们并不训练vgg的参数,它们保持不变

        # 需要进行训练的是input,它即是我们最终生成的图像

        net['input'] = tf.Variable(np.zeros([1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3]), dtype=tf.float32)

        # 参数对应的层数可以参考vgg模型图

        net['conv1_1'] = self.conv_relu(net['input'], self.get_wb(vgg_layers, 0))

        net['conv1_2'] = self.conv_relu(net['conv1_1'], self.get_wb(vgg_layers, 2))

        net['pool1'] = self.pool(net['conv1_2'])

        net['conv2_1'] = self.conv_relu(net['pool1'], self.get_wb(vgg_layers, 5))

        net['conv2_2'] = self.conv_relu(net['conv2_1'], self.get_wb(vgg_layers, 7))

        net['pool2'] = self.pool(net['conv2_2'])

        net['conv3_1'] = self.conv_relu(net['pool2'], self.get_wb(vgg_layers, 10))

        net['conv3_2'] = self.conv_relu(net['conv3_1'], self.get_wb(vgg_layers, 12))

        net['conv3_3'] = self.conv_relu(net['conv3_2'], self.get_wb(vgg_layers, 14))

        net['conv3_4'] = self.conv_relu(net['conv3_3'], self.get_wb(vgg_layers, 16))

        net['pool3'] = self.pool(net['conv3_4'])

        net['conv4_1'] = self.conv_relu(net['pool3'], self.get_wb(vgg_layers, 19))

        net['conv4_2'] = self.conv_relu(net['conv4_1'], self.get_wb(vgg_layers, 21))

        net['conv4_3'] = self.conv_relu(net['conv4_2'], self.get_wb(vgg_layers, 23))

        net['conv4_4'] = self.conv_relu(net['conv4_3'], self.get_wb(vgg_layers, 25))

        net['pool4'] = self.pool(net['conv4_4'])

        net['conv5_1'] = self.conv_relu(net['pool4'], self.get_wb(vgg_layers, 28))

        net['conv5_2'] = self.conv_relu(net['conv5_1'], self.get_wb(vgg_layers, 30))

        net['conv5_3'] = self.conv_relu(net['conv5_2'], self.get_wb(vgg_layers, 32))

        net['conv5_4'] = self.conv_relu(net['conv5_3'], self.get_wb(vgg_layers, 34))

        net['pool5'] = self.pool(net['conv5_4'])

        return net

 

    def conv_relu(self, input, wb):

        """

        进行先卷积、后relu的运算

        :param input: 输入层

        :param wb: wb[0],wb[1] == w,b

        :return: relu后的结果

        """

        conv = tf.nn.conv2d(input, wb[0], strides=[1, 1, 1, 1], padding='SAME')

        relu = tf.nn.relu(conv + wb[1])

        return relu

 

    def pool(self, input):

        """

        进行max_pool操作

        :param input: 输入层

        :return: 池化后的结果

        """

        return tf.nn.max_pool(input, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

 

    def get_wb(self, layers, i):

        """

        从预训练好的vgg模型中读取参数

        :param layers: 训练好的vgg模型

        :param i: vgg指定层数

        :return: 该层的w,b

        """

        w = tf.constant(layers[i][0][0][0][0][0])

        bias = layers[i][0][0][0][0][1]

        b = tf.constant(np.reshape(bias, (bias.size)))

        return w, b

 

    def get_random_img(self):

        """

        根据噪音和内容图片,生成一张随机图片

        :return:

        """

        noise_image = np.random.uniform(-20, 20, [1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3])

        random_img = noise_image * settings.NOISE + self.content * (1 - settings.NOISE)

        return random_img

 

    def loadimg(self, path):

        """

        加载一张图片,将其转化为符合要求的格式

        :param path:

        :return:

        """

        # 读取图片

        image = scipy.misc.imread(path)

        # 重新设定图片大小

        image = scipy.misc.imresize(image, [settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH])

        # 改变数组形状,其实就是把它变成一个batch_size=1的batch

        image = np.reshape(image, (1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3))

        # 减去均值,使其数据分布接近0

        image = image - settings.IMAGE_MEAN_VALUE

        return image

 

if __name__ == '__main__':

    Model(settings.CONTENT_IMAGE, settings.STYLE_IMAGE)

2.2 settings.py介绍

# -*- coding: utf-8 -*-

 

# 内容图片路径

CONTENT_IMAGE = 'images/content.jpg'

# 风格图片路径

STYLE_IMAGE = 'images/style.jpg'

# 输出图片路径

OUTPUT_IMAGE = 'output/output'

# 预训练的vgg模型路径

VGG_MODEL_PATH = 'imagenet-vgg-verydeep-19.mat'

# 图片宽度

IMAGE_WIDTH = 450

# 图片高度

IMAGE_HEIGHT = 300

# 定义计算内容损失的vgg层名称及对应权重的列表

CONTENT_LOSS_LAYERS = [('conv4_2', 0.5),('conv5_2',0.5)]

# 定义计算风格损失的vgg层名称及对应权重的列表

STYLE_LOSS_LAYERS = [('conv1_1', 0.2), ('conv2_1', 0.2), ('conv3_1', 0.2), ('conv4_1', 0.2), ('conv5_1', 0.2)]

# 噪音比率

NOISE = 0.5

# 图片RGB均值

IMAGE_MEAN_VALUE = [128.0, 128.0, 128.0]

# 内容损失权重

ALPHA = 1

# 风格损失权重

BETA = 500

# 训练次数

TRAIN_STEPS = 3000

2.3 train.py介绍

# -*- coding: utf-8 -*-

import tensorflow as tf

import settings

import models

import numpy as np

import scipy.misc

def loss(sess, model):

    """

    定义模型的损失函数

    :param sess: tf session

    :param model: 神经网络模型

    :return: 内容损失和风格损失的加权和损失

    """

    # 先计算内容损失函数

    # 获取定义内容损失的vgg层名称列表及权重

    content_layers = settings.CONTENT_LOSS_LAYERS

    # 将内容图片作为输入,方便后面提取内容图片在各层中的特征矩阵

    sess.run(tf.assign(model.net['input'], model.content))

    # 内容损失累加量

    content_loss = 0.0

    # 逐个取出衡量内容损失的vgg层名称及对应权重

    for layer_name, weight in content_layers:

        # 提取内容图片在layer_name层中的特征矩阵

        p = sess.run(model.net[layer_name])

        # 提取噪音图片在layer_name层中的特征矩阵

        x = model.net[layer_name]

        # 长x宽

        M = p.shape[1] * p.shape[2]

        # 信道数

        N = p.shape[3]

        # 根据公式计算损失,并进行累加

        content_loss += (1.0 / (2 * M * N)) * tf.reduce_sum(tf.pow(p - x, 2)) * weight

    # 将损失对层数取平均

    content_loss /= len(content_layers)

 

    # 再计算风格损失函数

    style_layers = settings.STYLE_LOSS_LAYERS

    # 将风格图片作为输入,方便后面提取风格图片在各层中的特征矩阵

    sess.run(tf.assign(model.net['input'], model.style))

    # 风格损失累加量

    style_loss = 0.0

    # 逐个取出衡量风格损失的vgg层名称及对应权重

    for layer_name, weight in style_layers:

        # 提取风格图片在layer_name层中的特征矩阵

        a = sess.run(model.net[layer_name])

        # 提取噪音图片在layer_name层中的特征矩阵

        x = model.net[layer_name]

        # 长x宽

        M = a.shape[1] * a.shape[2]

        # 信道数

        N = a.shape[3]

        # 求风格图片特征的gram矩阵

        A = gram(a, M, N)

        # 求噪音图片特征的gram矩阵

        G = gram(x, M, N)

        # 根据公式计算损失,并进行累加

        style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight

    # 将损失对层数取平均

    style_loss /= len(style_layers)

    # 将内容损失和风格损失加权求和,构成总损失函数

    loss = settings.ALPHA * content_loss + settings.BETA * style_loss

 

    return loss

 

def gram(x, size, deep):

    """

    创建给定矩阵的格莱姆矩阵,用来衡量风格

    :param x:给定矩阵

    :param size:矩阵的行数与列数的乘积

    :param deep:矩阵信道数

    :return:格莱姆矩阵

    """

    # 改变shape为(size,deep)

    x = tf.reshape(x, (size, deep))

    # 求xTx

    g = tf.matmul(tf.transpose(x), x)

    return g

 

 

def train():

    # 创建一个模型

    model = models.Model(settings.CONTENT_IMAGE, settings.STYLE_IMAGE)

    # 创建session

    with tf.Session() as sess:

        # 全局初始化

        sess.run(tf.global_variables_initializer())

        # 定义损失函数

        cost = loss(sess, model)

        # 创建优化器

        optimizer = tf.train.AdamOptimizer(1.0).minimize(cost)

        # 再初始化一次(主要针对于第一次初始化后又定义的运算,不然可能会报错)

        sess.run(tf.global_variables_initializer())

        # 使用噪声图片进行训练

        sess.run(tf.assign(model.net['input'], model.random_img))

        # 迭代指定次数

        for step in range(settings.TRAIN_STEPS):

            # 进行一次反向传播

            sess.run(optimizer)

            # 每隔一定次数,输出一下进度,并保存当前训练结果

            if step % 50 == 0:

                print('step {} is down.'.format(step))

                # 取出input的内容,这是生成的图片

                img = sess.run(model.net['input'])

                # 训练过程是减去均值的,这里要加上

                img += settings.IMAGE_MEAN_VALUE

                # 这里是一个batch_size=1的batch,所以img[0]才是图片内容

                img = img[0]

                # 将像素值限定在0-255,并转为整型

                img = np.clip(img, 0, 255).astype(np.uint8)

                # 保存图片

                scipy.misc.imsave('{}-{}.jpg'.format(settings.OUTPUT_IMAGE,step), img)

        # 保存最终训练结果

        img = sess.run(model.net['input'])

        img += settings.IMAGE_MEAN_VALUE

        img = img[0]

        img = np.clip(img, 0, 255).astype(np.uint8)

        scipy.misc.imsave('{}.jpg'.format(settings.OUTPUT_IMAGE), img)

 

if __name__ == '__main__':

    train()

风格图像生成

1. 在主文件目录下新建output文件夹

2. 下载VGG19预训练模型imagenet-vgg-verydeep-19文件,链接:https://pan.baidu.com/s/1a8ryYskbo_2dn9on74NaTA 提取码:dt8y

2. 在主文件目录下打开powershell窗口

3. 运行python train.py,即可生成风格图像输出到output文件夹下,如下:

手把手教你使用VGG19做图像风格迁移_第4张图片

如果要自定义图像进行风格迁移,可以有两种方案:

1) 将images文件夹下的风格图片和内容图片替换掉;

2) 更改settings.py文件内容图片读取路径和名称;

一路同频

ID : AI计算机视觉

一起探索科技的奥秘.....

手把手教你使用VGG19做图像风格迁移_第5张图片

你可能感兴趣的:(卷积,tensorflow,深度学习,神经网络,计算机视觉)