基于VGG19迁移学习实现图像风格迁移

简介: 基于VGG19迁移学习实现图像风格迁移

一直想要做个图像风格迁移来玩玩的,感觉还是蛮有意思的。


所谓图像风格迁移,即给定内容图片A,风格图片B,能够生成一张具有A图片内容和B图片风格的图片C。


比如说,我们可以使用梵高先生的名画《星夜》 作为风格图片,来与其他图片生成具有《星夜》风格新图片。emmm,夭寿啦,机器帮你画世界名画啦。。。


举两个生成的例子:


均使用《星夜》作为风格图片(可以替换,我以《星夜》为例):

61aa2a7e9d8c309a5b7dd5abc3f84185.jpg


示例1:


网络上找到的一张风景图片。


内容图片:


bd5650613ce7f7bf4600b4301beae71d.jpg

生成图片:

5c1998ea2542b9cad045266e47a7807c.png


生成图片的尺寸比较小,没办法,我的显卡太差了,尺寸大一点的话显卡内存不足。


示例2:


嗷嗷嗷,狼人嚎叫~


内容图片:

ae2e611493206e46ccbfd39ac0750984.png


生成图片:

6446446227f6d4188315453547380ac1.png


效果还凑合吧,可以接受。


下面记录实现过程。


一.获取预训练的vgg19模型


VGG19是Google DeepMind发表在ICLR 2015上的论文《VERY DEEP CONVOLUTIONAL NETWORK SFOR LARGE-SCALE IMAGE RECOGNITION》中提出的一种DCNN结构。


众所周知,CNN在图片处理上表现良好,VGG19提出后,也被用在图像处理上。我这里要用到的VGG19模型就是在imagenet数据集上预训练的模型。


一般认为,深度卷积神经网络的训练是对数据集特征的一步步抽取的过程,从简单的特征,到复杂的特征。


训练好的模型学习到的是对图像特征的抽取方法,所以在imagenet数据集上训练好的模型理论上来说,也可以直接用于抽取其他图像的特征,这也是迁移学习的基础。自然,这样的效果往往没有在新数据上重新训练的效果好,但能够节省大量的训练时间,在特定情况下非常有用。


预训练好的VGG19模型可以从这里下载,模型大小500M+。


二.模型编写


这里的模型基本上就是VGG19模型,只是稍微做了一些修改。


我们要从预训练的模型中,获取卷积层部分的参数,用于构建我们自己的模型。VGG19中的全连接层舍弃掉,这一部分对提取图像特征基本无用。


要注意的是,我这里提取出来的VGG参数全部是作为constant(即常量)使用的,也就是说,这些参数是不会再被训练的,在反向传播的过程中也不会改变。


另外,输入层要设置为Variable,我们要训练的就是这个。最开始输入一张噪音图片,然后不断地根据内容loss和风格loss对其进行调整,直到一定次数后,该图片兼具了风格图片的风格以及内容图片的内容。当训练结束时,输入层的参数就是我们生成的图片。


附一张VGG结构图:

2db754673ce5fa0dbdef39763bcee780.png


这个代码里主要是定义VGG,至于LOSS在训练过程中进行说明。


models.py

# -*- coding: utf-8 -*-# @Time    : 18-3-23 下午12:20# @Author  : AaronJny# @Email   : Aaron__7@163.comimporttensorflowastfimportnumpyasnpimportsettingsimportscipy.ioimportscipy.miscclassModel(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网络defvggnet(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'])returnnetdefconv_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])returnreludefpool(self,input):"""
        进行max_pool操作
        :param input: 输入层
        :return: 池化后的结果
        """returntf.nn.max_pool(input,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')defget_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)))returnw,bdefget_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)returnrandom_imgdefloadimg(self,path):"""
        加载一张图片,将其转化为符合要求的格式
        :param path:
        :return:
        """# 读取图片image=scipy.misc.imread(path)# 重新设定图片大小image=scipy.misc.imresize(image,[settings.IMAGE_HEIGHT,settings.IMAGE_WIDTH])# 改变数组形状,其实就是把它变成一个batch_size=1的batchimage=np.reshape(image,(1,settings.IMAGE_HEIGHT,settings.IMAGE_WIDTH,3))# 减去均值,使其数据分布接近0image=image-settings.IMAGE_MEAN_VALUEreturnimageif__name__=='__main__':Model(settings.CONTENT_IMAGE,settings.STYLE_IMAGE)


三.模型训练


这里简述一下训练的思路:


1.首先,我们使用VGG中的一些层的输出来表示图片的内容特征和风格特征。比如,我使用[‘conv4_2’,‘conv5_2’]表示内容特征,使用[‘conv1_1’,‘conv2_1’,‘conv3_1’,‘conv4_1’]表示风格特征。

2935b9daf166ef41acd2e6f0391cfebc.png


# 定义计算内容损失的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)]


2.将内容图片输入网络,计算内容图片在网络指定层(比如[‘conv4_2’,‘conv5_2’])上的输出值。


3.计算内容损失。我们可以这样定义内容损失:内容图片在指定层上提取出的特征矩阵,与噪声图片在对应层上的特征矩阵的差值的L2范数。即求两两之间的像素差值的平方。


对应每一层的内容损失函数:


(话说为了写这个公式,我还跑去现学了latex语法= =)

61fa37a16f261feae700e5d7739f7bf7.png

其中,X是噪声图片的特征矩阵,P是内容图片的特征矩阵。M是P的长*宽,N是信道数。


最终的内容损失为,每一层的内容损失加权和,再对层数取平均。


4.将风格图片输入网络,计算风格图片在网络指定层(比如[‘conv1_1’,‘conv2_1’,‘conv3_1’,‘conv4_1’])上的输出值。


5.计算风格损失。我们使用风格图像在指定层上的特征矩阵的GRAM矩阵来衡量其风格,风格损失可以定义为风格图像和噪音图像特征矩阵的格莱姆矩阵的差值的L2范数。


对于每一层的风格损失函数:

3ed1b9ae3153e3ed8111263dd17bd49d.png


其中M是特征矩阵的长*宽,N是特征矩阵的信道数。G为噪音图像特征的Gram矩阵,A为风格图片特征的GRAM矩阵。


最终的风格损失为,每一层的风格损失加权和,再对层数取平均。


6.最终用于训练的损失函数为内容损失和风格损失的加权和。

8257782b95572e89fbb3acad96f71bee.png


7.当训练开始时,我们根据内容图片和噪声,生成一张噪声图片。并将噪声图片喂给网络,计算loss,再根据loss调整噪声图片。将调整后的图片喂给网络,重新计算loss,再调整,再计算…直到达到指定迭代次数,此时,噪声图片已兼具内容图片的内容和风格图片的风格,进行保存即可。


具体代码如下:


train.py

# -*- coding: utf-8 -*-# @Time    : 18-3-23 下午12:22# @Author  : AaronJny# @Email   : Aaron__7@163.comimporttensorflowastfimportsettingsimportmodelsimportnumpyasnpimportscipy.miscdefloss(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层名称及对应权重forlayer_name,weightincontent_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层名称及对应权重forlayer_name,weightinstyle_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_lossreturnlossdefgram(x,size,deep):"""
    创建给定矩阵的格莱姆矩阵,用来衡量风格
    :param x:给定矩阵
    :param size:矩阵的行数与列数的乘积
    :param deep:矩阵信道数
    :return:格莱姆矩阵
    """# 改变shape为(size,deep)x=tf.reshape(x,(size,deep))# 求xTxg=tf.matmul(tf.transpose(x),x)returngdeftrain():# 创建一个模型model=models.Model(settings.CONTENT_IMAGE,settings.STYLE_IMAGE)# 创建sessionwithtf.Session()assess:# 全局初始化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))# 迭代指定次数forstepinrange(settings.TRAIN_STEPS):# 进行一次反向传播sess.run(optimizer)# 每隔一定次数,输出一下进度,并保存当前训练结果ifstep%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_VALUEimg=img[0]img=np.clip(img,0,255).astype(np.uint8)scipy.misc.imsave('{}.jpg'.format(settings.OUTPUT_IMAGE),img)if__name__=='__main__':train()

四、一些配置信息


settings.py


# -*- coding: utf-8 -*-# @Time    : 18-3-23 下午12:22# @Author  : AaronJny# @Email   : Aaron__7@163.com# 内容图片路径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月前
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习中的图像风格迁移
【9月更文挑战第26天】本文将探讨如何利用深度学习技术,实现图像风格的转换。我们将从基础的理论出发,然后逐步深入到具体的实现过程,最后通过代码实例来展示这一技术的实际应用。无论你是初学者还是有经验的开发者,都能在这篇文章中找到有价值的信息。让我们一起探索深度学习的奥秘吧!
|
6月前
|
数据可视化 PyTorch 算法框架/工具
使用PyTorch搭建VGG模型进行图像风格迁移实战(附源码和数据集)
使用PyTorch搭建VGG模型进行图像风格迁移实战(附源码和数据集)
590 1
|
1月前
|
机器学习/深度学习 人工智能 TensorFlow
利用深度学习实现图像风格迁移
【8月更文挑战第73天】本文通过深入浅出的方式,介绍了一种使用深度学习技术进行图像风格迁移的方法。我们将探讨如何将一张普通照片转化为具有著名画作风格的艺术作品。文章不仅解释了背后的技术原理,还提供了一个实际的代码示例,帮助读者理解如何实现这一过程。
|
3月前
|
人工智能 自动驾驶 测试技术
ECCV 2024:是真看到了,还是以为自己看到了?多模态大模型对文本预训练知识的过度依赖该解决了
【8月更文挑战第19天】多模态大模型(MLLMs)能依据视觉输入生成回应,但常过度依赖文本预训练知识,忽略视觉信息,导致回应与图像不符的问题。新论文提出“Bootstrapped Preference Optimization (BPO)”方法,通过引入含偏差的样本进行偏好学习,以减少文本偏倚的影响并提高模型可靠性。实验表明该方法有效改善了模型性能,但在构建偏好数据集方面仍面临挑战。论文链接: https://arxiv.org/pdf/2403.08730
46 2
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
基于Mamba架构的,状态空间音频分类模型AUM
【8月更文挑战第7天】随着AI技术的发展,音频分类在诸多领域变得至关重要。传统方法如CNN面临计算成本高的问题。新兴的Mamba架构,基于状态空间模型(SSM),展示出优秀性能。受此启发,研究者开发了Audio Mamba (AUM)模型,首个完全基于SSM且不依赖自注意力机制的音频分类模型。AUM利用SSM的高效性捕捉音频时频特征,大幅降低计算复杂度,尤其适合大规模数据。实验显示,AUM在多个任务上的表现与先进自注意力模型相当甚至更好。尽管如此,AUM在复杂任务及泛化能力方面仍存在讨论空间。[论文](https://arxiv.org/abs/2406.03344)
77 1
|
4月前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:图像风格迁移与生成
【7月更文挑战第13天】 使用Python实现深度学习模型:图像风格迁移与生成
54 2
|
6月前
|
机器学习/深度学习 编解码 自然语言处理
CVPR 2022 | Restormer:高分辨率图像恢复的高效Transformer
CVPR 2022 | Restormer:高分辨率图像恢复的高效Transformer
439 1
|
6月前
|
机器学习/深度学习 并行计算 算法
模型压缩部署神技 | CNN与Transformer通用,让ConvNeXt精度几乎无损,速度提升40%
模型压缩部署神技 | CNN与Transformer通用,让ConvNeXt精度几乎无损,速度提升40%
146 0
|
存储 机器学习/深度学习 编解码
使用训练分类网络预处理多分辨率图像
说明如何准备用于读取和预处理可能不适合内存的多分辨率全玻片图像 (WSI) 的数据存储。肿瘤分类的深度学习方法依赖于数字病理学,其中整个组织切片被成像和数字化。生成的 WSI 具有高分辨率,大约为 200,000 x 100,000 像素。WSI 通常以多分辨率格式存储,以促进图像的高效显示、导航和处理。 读取和处理WSI数据。这些对象有助于使用多个分辨率级别,并且不需要将图像加载到核心内存中。此示例演示如何使用较低分辨率的图像数据从较精细的级别有效地准备数据。可以使用处理后的数据来训练分类深度学习网络。
323 0
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习与生成对抗网络:图像合成和风格迁移
深度学习和生成对抗网络(GAN)在计算机视觉领域中取得了重大突破。本文将介绍如何使用GAN进行图像合成和风格迁移,通过训练生成器和判别器网络,实现从随机噪声生成逼真图像和将图像转换为不同风格的图像。我们将探讨GAN的工作原理、网络架构和训练过程,并提供实例代码,帮助读者快速上手实现图像合成和风格迁移。
512 0