这篇文章是个人《AI绘画系列》的第三篇,点击此处能够查看整个系列的文章
本篇文章的源码能够在微信公众号「01二进制」后台回复「图像风格迁移」得到python
所谓图像风格迁移,是指将一幅内容图A的内容,和一幅风格图B的风格融合在一块儿,从而生成一张具备A图风格和B图内容的图片C的技术。目前这个技术已经获得了比较普遍的应用,这里安利一个app——"大画家",这个软件能够将用户的照片自动变换为具备艺术家的风格的图片。算法
其实刚开始写这篇文章的时候我是准备详细介绍下原理的,可是后来发现公式实在是太多了,就算写了估计也没什么人看,并且这篇文章原本定位的用户就是只须要实现功能的新人。此外,有关风格迁移的原理解析的博客实在是太多了,因此这里我就把重点放在如何使用 TensorFlow 实现一个快速风格迁移的应用上,原理的解析就一带而过了。若是只想实现这个效果的能够跳到**"运行"**一节。微信
第一步咱们须要提早安装好 TensorFlow,若是有GPU的小伙伴能够参考个人这篇文章搭建一个GPU环境:《AI 绘画第一弹——用GPU为你的训练过程加速》,若是打算直接用 CPU 运行的话,执行下面一行话就能够了网络
pip install numpy tensorflow scipy
复制代码
本篇文章是基于A Neural Algorithm of Artistic Style一文提出的方法实现的,若是嫌看英文论文太麻烦的也能够查看我对这篇文章的翻译【译】一种有关艺术风格迁移的神经网络算法。app
为了将风格图的风格和内容图的内容进行融合,所生成的图片,在内容上应当尽量接近内容图,在风格上应当尽量接近风格图,所以须要定义内容损失函数和风格损失函数,通过加权后做为总的损失函数。函数
CNN具备抽象和理解图像的能力,所以能够考虑将各个卷积层的输出做为图像的内容,这里咱们采用了利用VGG19训练好的模型来进行迁移学习,通常认为,卷积神经网络的训练是对数据集特征的一步步抽取的过程,从简单的特征,到复杂的特征。训练好的模型学习到的是对图像特征的抽取方法,而该模型就是在imagenet数据集上预训练的模型,因此理论上来讲,也能够直接用于抽取其余图像的特征,虽然效果可能没有在原有数据集上训练出的模型好,可是可以节省大量的训练时间,在特定状况下很是有用。post
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
复制代码
咱们使用VGG中的一些层的输出来表示图片的内容特征和风格特征。好比,我使用[‘conv4_2’,’conv5_2’]
表示内容特征,使用[‘conv1_1’,’conv2_1’,’conv3_1’,’conv4_1’]
表示风格特征。在settings.py
中进行配置。学习
# 定义计算内容损失的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)]
复制代码
其中,X是噪声图片的特征矩阵,P是内容图片的特征矩阵。M是P的长*宽,N是信道数。最终的内容损失为,每一层的内容损失加权和,再对层数取平均。spa
我知道不少人一看到数学公式就会头疼,简单理解就是这个公式可让模型在训练过程当中不断的抽取图片的内容。.net
计算风格损失。咱们使用风格图像在指定层上的特征矩阵的GRAM矩阵来衡量其风格,风格损失能够定义为风格图像和噪音图像特征矩阵的格莱姆矩阵的差值的L2范数。
对于每一层的风格损失函数,咱们有:
其中M是特征矩阵的长*宽,N是特征矩阵的信道数。G为噪音图像特征的Gram矩阵,A为风格图片特征的GRAM矩阵。最终的风格损失为,每一层的风格损失加权和,再对层数取平均。
一样的,看不懂公式不要紧,你就把它理解为这个公式能够在训练过程当中获取图片的风格。
最后咱们只须要将内容损失函数和风格损失函数带入刚开始的公式中便可,要作的就是控制一些风格的权重和内容的权重:
# 内容损失权重
ALPHA = 1
# 风格损失权重
BETA = 500
复制代码
ALPHA越大,则最后生成的图片内容信息越大;同理,BETA越大,则最后生成的图片风格化更严重。
当训练开始时,咱们根据内容图片和噪声,生成一张噪声图片。并将噪声图片喂给网络,计算loss,再根据loss调整噪声图片。将调整后的图片喂给网络,从新计算loss,再调整,再计算…直到达到指定迭代次数,此时,噪声图片已兼具内容图片的内容和风格图片的风格,进行保存便可。
若是对上述原理不感兴趣的,想直接运行代码的能够去微信公众号「01二进制」后台回复「图像风格迁移」得到源码。接下来咱们来讲说如何使用这个代码跑出本身的绘画。
首先看下项目结构:
images下有两张图片,分别是内容图和风格图,output下是训练过程当中产生的文件,.mat文件就是预训练模型,models.py是咱们实现的用于读取预训练模型的文件,settings.py是配置文件,train.py是最终的训练文件。
想要运行该项目,咱们只须要执行python train.py
便可,想更改风格和内容的的话只要在images文件中更换原先的图片便可。固然你也能够在settings.py
中修改路径:
# 内容图片路径
CONTENT_IMAGE = 'images/content.jpg'
# 风格图片路径
STYLE_IMAGE = 'images/style.jpg'
# 输出图片路径
OUTPUT_IMAGE = 'output/output'
# 预训练的vgg模型路径
VGG_MODEL_PATH = 'imagenet-vgg-verydeep-19.mat'
复制代码
咱们来看看训练后的图片:
虽然经过上述代码咱们能够实现图像的风格迁移,可是他有一个最大的缺点,就是没法保存训练好的模型,每次转换风格都要从新跑一遍,若是使用CPU跑1000轮的话大约是在30分钟左右,所以推荐你们使用GPU进行训练。
可是即便使用上了GPU,训练时间也没法知足商业使用的,那有没有什么办法能够保存训练好的风格模型,而后直接快速生成目标图片呢?固然是有的,斯坦福的李飞飞发表过一篇《Perceptual Losses for Real-Time Style Transfer and Super-Resolution》,经过使用perceptual loss来替代per-pixels loss使用pre-trained的vgg model来简化原先的loss计算,增长一个transform Network,直接生成Content image的style。这里就再也不多说了,感兴趣的能够参考我下面给出的两个连接:
以上就是本篇文章的所有内容,我的作下来感受仍是挺有意思的,因我的能力有限,文中若有纰漏错误之处,还请各位大佬指正,万分感谢!
欢迎关注个人微信公众号:「01二进制」,关注后便可得到博主认真收集的计算机资料