这篇文章是我的《AI 绘画系列》的第三篇,点击此处可以查看整个系列的文章
本篇文章的源码可以在微信公众号「01 二进制」后台回复「图像风格迁移」获得
简介 所谓图像风格迁移,是指将一幅内容图 A 的内容,和一幅风格图 B 的风格融合在一起,从而生成一张具有 A 图风格和 B 图内容的图片 C 的技术。目前这个技术已经得到了比较广泛的应用,这里安利一个 app——“大画家”,这个软件可以将用户的照片自动变换为具有艺术家的风格的图片。
准备 其实刚开始写这篇文章的时候我是准备详细介绍下原理的,但是后来发现公式实在是太多了,就算写了估计也没什么人看,而且这篇文章本来定位的用户就是只需要实现功能的新人。此外,有关风格迁移的原理解析的博客实在是太多了,所以这里我就把重点放在如何使用 TensorFlow 实现一个快速风格迁移的应用上,原理的解析就一带而过了。如果只想实现这个效果的可以跳到**“运行”**一节。
第一步我们需要提前安装好 TensorFlow,如果有 GPU 的小伙伴可以参考我的这篇文章搭建一个 GPU 环境:《AI 绘画第一弹——用 GPU 为你的训练过程加速》,如果打算直接用 CPU 运行的话,执行下面一行话就可以了
pip install numpy tensorflow scipy 原理 本篇文章是基于A Neural Algorithm of Artistic Style一文提出的方法实现的,如果嫌看英文论文太麻烦的也可以查看我对这篇文章的翻译【译】一种有关艺术风格迁移的神经网络算法。
为了将风格图的风格和内容图的内容进行融合,所生成的图片,在内容上应当尽可能接近内容图,在风格上应当尽可能接近风格图,因此需要定义内容损失函数和风格损失函数,经过加权后作为总的损失函数。
预训练模型 CNN 具有抽象和理解图像的能力,因此可以考虑将各个卷积层的输出作为图像的内容,这里我们采用了利用 VGG19 训练好的模型来进行迁移学习,一般认为,卷积神经网络的训练是对数据集特征的一步步抽取的过程,从简单的特征,到复杂的特征。训练好的模型学习到的是对图像特征的抽取方法,而该模型就是在 imagenet 数据集上预训练的模型,所以理论上来说,也可以直接用于抽取其他图像的特征,虽然效果可能没有在原有数据集上训练出的模型好,但是能够节省大量的训练时间,在特定情况下非常有用。
加载预训练模型 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中进行配置。
...