# 以惊人的艺术风格变换图片 自从 2012 年深层神经网络在 AlexNet 赢得 ImageNet 挑战后开始起飞以来,人工智能研究人员一直在将深度学习技术(包括经过预训练的深度 CNN 模型)应用于越来越多的问题领域。 有什么能比创造艺术更有创造力? 已经提出并实现了 的一种想法,称为神经样式传递,它使您可以利用预先训练的深度神经网络模型并传递图像或任何梵高的样式 或莫奈的杰作),例如另一张图片(例如个人资料图片或您喜欢的狗的图片),从而创建将图片内容与杰作风格融合在一起的图片。 实际上,有一个名为 Prisma 的 iOS 应用程序在 2016 年获得了年度最佳应用程序奖。 在短短几秒钟内,它将以您选择的任何样式转换您的图片。 在本章中,我们将首先概述三种神经样式转换方法,其中一种是原始方法,一种是经过改进的方法,另一种是进一步改进的方法。 然后,我们将详细研究如何使用第二种方法来训练快速神经样式转换模型,该模型可在您的 iOS 和 Android 智能手机中使用,以实现 Prisma 的功能。 接下来,我们将实际在 iOS 应用程序和 Android 应用程序中使用该模型,引导您完成从头开始创建此类应用程序的整个过程。 最后,我们将向您简要介绍 TensorFlow Magenta 开源项目,您可以将其用于基于深度学习构建更多的音乐和艺术生成应用程序,并向您展示如何使用单个预先训练的样式转换模型, 是基于神经样式转换的最新研究进展而创建的,其中包括 26 种很酷的艺术样式,可在您的 iOS 和 Android 应用中获得更快的性能和结果。 总之,本章将涵盖以下主题: * 神经风格转换-快速概述 * 训练快速的神经风格转换模型 * 在 iOS 中使用快速的神经样式转换模型 * 在 Android 中使用快速的神经样式转换模型 * 在 iOS 中使用 TensorFlow Magenta 多样式模型 * 在 Android 中使用 TensorFlow Magenta 多样式模型 # 神经风格转换-快速概述 使用深度神经网络将图像内容与另一种图像的样式合并的原始思想和算法于 2015 年夏季发表在题为[《艺术风格的神经算法》](https://arxiv.org/abs/1508.06576)的论文中。它是 2014 年 ImageNet 图像识别挑战赛的获胜者,该挑战赛具有 16 个卷积层或特征图,分别代表不同级别的图像内容。 在这种原始方法中,首先将最终传输的图像初始化为与内容图像合并的白噪声图像。 内容损失函数定义为内容图像和结果图像的卷积层`conv4_2`上都被馈入 VGG-19 网络后,特定的一组特征表示形式的平方误差损失。 样式损失函数计算样式图像和所得图像在五个不同卷积层上的总误差差。 然后,将总损失定义为内容损失和样式损失的总和。 在训练期间,损失会降到最低,并生成将一个图像的内容与另一个图像的样式混合在一起的结果图像。 尽管原始神经样式转换算法的结果令人惊叹,但其性能却很差-训练是样式转换图像生成过程的一部分,通常在 GPU 上花费几分钟,在 CPU 上花费约一个小时才能生成良好的图像。 结果。 如果您对原始算法的细节感兴趣,可以在[以下位置](https://github.com/log0/neural-style-painting/blob/master/art.py)阅读该论文以及文档齐全的 Python 实现。我们不会讨论这种原始算法,因为在手机上运行该算法是不可行的,但是尝试该算法很有趣且有用,可以更好地了解如何针对不同的计算机视觉任务使用预先训练的深度 CNN 模型。 自然地,在 2016 年,论文中发布了一种“快三个数量级”的新算法,即[《实时样式传递和超分辨率的感知损失》](https://cs.stanford.edu/people/jcjohns/eccv16/),作者是 Justin Johnson 等。 它使用单独的训练过程,并定义了更好的损失函数,这些函数本身就是深度神经网络。 训练后(在下一节中我们将看到,在 GPU 上可能要花费几个小时),使用训练后的模型来生成样式转换的图像在计算机上几乎是实时的,而在智能手机上只需几秒钟。 使用这种快速神经传递算法仍然有一个缺点:只能针对特定样式训练模型,因此,要在您的应用中使用不同的样式,必须逐一训练这些样式以为每种样式生成一个模型 。 2017 年发表了一篇名为[《学习风格的艺术表现形式》](https://arxiv.org/abs/1610.07629)的新论文,它发现一个单一的深度神经网络模型可以 概括许多不同的样式。 [TensorFlow Magenta 项目](https://github.com/tensorflow/magenta/tree/master/magenta/models/image_stylization)包括具有多种样式的预训练模型,我们将在最后一部分中看到 本章的两个部分在 iOS 和 Android 应用程序中使用这种模型来产生强大而神奇的艺术效果是多么容易。 # 训练快速的神经风格转换模型 在本部分中,我们将向您展示如何使用带有 TensorFlow 的快速神经样式传输算法训练模型。 执行以下步骤来训练这样的模型: 1. 在 Mac 的终端上,或者最好在 GPU 驱动的 Ubuntu 上,运行`git clone https://github.com/jeffxtang/fast-style-transfer`,这是 Johnson 的快速样式传输的 TensorFlow 实现的一个很好的分支,已修改为允许在 iOS 或 Android 应用中使用经过训练的模型。 2. `cd`到快速样式迁移目录,然后运行`setup.sh`脚本下载预先训练的 VGG-19 模型文件以及 MS COCO 训练数据集,我们在上一章中提到过-注意 下载大文件可能需要几个小时。 3. 运行以下命令,使用名为`starry_night.jpg`的样式图像和名为`ww1.jpg`的内容图像进行训练,以创建检查点文件: ```py mkdir checkpoints mkdir test_dir python style.py --style images/starry_night.jpg --test images/ww1.jpg --test-dir test_dir --content-weight 1.5e1 --checkpoint-dir checkpoints --checkpoint-iterations 1000 --batch-size 10 ``` `images`目录中还有一些其他样式的图像,可用于创建不同的检查点文件。 此处使用的`starry_night.jpg`样式图片是梵高的一幅著名画作,如图 4.1 所示: ![](img/ea1f4494-b7f4-4be2-a897-adfcbc4f0ef4.jpg) 图 4.1:用梵高的绘画作为风格图像 在第 1 章, “移动 TensorFlow 入门”中设置的 NVIDIA GTX 1070 GPU 驱动的 Ubuntu 上,整个训练大约需要 5 个小时,并且在 CPU 上肯定要花更长的时间 。 该脚本最初是为 TensorFlow 0.12 编写的,但后来为 TensorFlow 1.1 进行了修改,并且已被验证为可以在 TensorFlow 1.4 的 Python 2.7 环境中正常运行。 4. 在文本编辑器中打开`evaluate.py`文件,然后取消注释以下两行代码(在 158 和 159 行): ```py # saver = tf.train.Saver() # saver.save(sess, "checkpoints_ios/fns.ckpt") ``` 5. 运行以下命令,以输入图像`img_placeholder`和传输的图像`preds`创建新的检查点: ```py python evaluate.py --checkpoint checkpoints \ --in-path examples/content/dog.jpg \ --out-path examples/content/dog-output.jpg ``` 6. 运行以下命令以构建一个 TensorFlow 图形文件,该文件将图形定义和检查点中的权重结合在一起。 这将创建一个大约 6.7MB 的`.pb`文件: ```py python freeze.py --model_folder=checkpoints_ios --output_graph fst_frozen.pb ``` 7. 假设您具有`/tf_files`目录,将生成的`fst_frozen.pb`文件复制到`/tf_files`,`cd`直接复制到 TensorFlow 源根目录(可能是`~/tensorflow-1.4.0`),然后运行以下命令以生成量化模型的`.pb`文件(我们在第 2 章,“通过迁移学习对图像进行分类”中介绍了量化): ```py bazel-bin/tensorflow/tools/quantization/quantize_graph \ --input=/tf_files/fst_frozen.pb \ --output_node_names=preds \ --output=/tf_files/fst_frozen_quantized.pb \ --mode=weights ``` 这会将冻结的图形文件大小从 6.7MB 减小到 1.7MB,这意味着,如果在您的应用中放置 50 种不同风格的 50 个模型,则增加的大小将约为 85MB。 苹果于 2017 年 9 月宣布,蜂窝无线应用程序下载限制已增加至 150MB,因此用户仍应能够通过蜂窝网络下载具有 50 多种不同样式的应用程序。 这就是使用样式图像和输入图像来训练和量化快速神经传递模型的全部步骤。 您可以在步骤 3 中生成的 `test_dir` 目录中签出生成的图像,以查看样式转换的效果。 如果需要,您可以使用[中记录的超参数 https://github.com/jeffxtang/fast-style-transfer/blob/master/docs.md#style](https://github.com/jeffxtang/fast-style-transfer/blob/master/docs.md#style) 进行查看,以及 希望更好,样式迁移效果。 在了解如何在 iOS 和 Android 应用程序中使用这些模型之前,重要的一点是,您需要记下指定为`--in-path` 值的图像的确切图像宽度和高度 ]在第 5 步中使用 参数,并在 iOS 或 Android 代码中使用图像的宽度和高度值(您会看到多久了),否则会出现 `Conv2DCustomBackpropInput: Size of out_backprop doesn't match computed` 错误 在应用中运行模型时。 # 在 iOS 中使用快速的神经样式传输模型 事实证明,在由 TensorFlow 实验性容器构建的 iOS 应用中,使用在步骤 7 中生成的`fst_frozen_quantized.pb`模型文件没有问题,如第 2 章,“通过迁移学习对图像分类”,但 TensorFlow Magenta 项目中的预训练多样式模型文件(我们将在本章的后续部分中使用)将不会随 TensorFlow Pod 一起加载(截至 2018 年 1 月)—尝试加载多样式模型文件时将引发以下错误: ```py Could not create TensorFlow Graph: Invalid argument: No OpKernel was registered to support Op 'Mul' with these attrs. Registered devices: [CPU], Registered kernels: device='CPU'; T in [DT_FLOAT] [[Node: transformer/expand/conv1/mul_1 = Mul[T=DT_INT32](transformer/expand/conv1/mul_1/x, transformer/expand/conv1/strided_slice_1)]] ``` 在第 3 章,“检测对象及其位置”中,我们讨论了原因以及如何使用手动构建的 TensorFlow 库修复此错误。 由于我们将在同一 iOS 应用程序中使用这两种模型,因此我们将使用功能更强大的手动 TensorFlow 库创建一个新的 iOS 应用程序。 # 使用快速神经传递模型进行添加和测试 如果您尚未手动构建 TensorFlow 库,则需要先回到上一章。 然后执行以下步骤以将 TensorFlow 支持和快速的神经样式传输模型文件添加到您的 iOS 应用并测试运行该应用: 1. 如果您已经具有添加了 TensorFlow 手动库的 iOS 应用,则可以跳过此步骤。 否则,类似于我们在上一章中所做的,创建一个新的基于 Objective-C 的 iOS 应用程序,例如`NeuralStyleTransfer`,或者在现有应用程序中,在`PROJECT`下创建一个新的用户定义设置 使用`$HOME/tensorflow-1.4.0`值命名为`TENSORFLOW_ROOT`的构建设置,假定在那儿已安装 TensorFlow 1.4.0,然后在`TARGET`的构建设置中,将其他链接器标志设置为: ```py -force_load $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf-lite.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c++11/nsync.a ``` 然后将标题搜索路径设置为: ```py $(TENSORFLOW_ROOT) $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/protobuf/src $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/eigen $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/proto ``` 2. 将 `fst_frozen_quantized.pb` 文件和一些测试图像拖放到项目的文件夹中。 从以前的 iOS 应用程序中,或从本书源代码仓库中`Ch4/ios`下的`NeuralStyleTransfer`应用程序文件夹中复制我们在前几章中使用过的相同`ios_image_load.mm`和`.h`文件到项目中。 3. 将`ViewController.m`重命名为`ViewController.mm`并将其替换为`Ch4/ios/NeuralStyleTransfer`中的`ViewController.h`和`.mm`文件。 在测试运行该应用程序后,我们将详细介绍核心代码段。 4. 在 iOS 模拟器或 iOS 设备上运行该应用程序,您将看到一张狗图片,如图 4.2 所示: ![](img/9bdc2a19-7e59-4f72-b41f-ea3eeba0c5c5.png) 图 4.2:应用样式之前的原始狗图片 5. 点击以选择快速样式迁移,几秒钟后,您将在图 4.3 中看到一张新图片,其中已转移了繁星点点的夜色: ![](img/a78207bb-2407-4343-b84d-2f17aacfd4ff.png) 图 4.3:就像让梵高画出您喜欢的狗一样 您只需选择喜欢的图片作为样式图像,然后按照上一节中的步骤操作,即可轻松构建具有不同样式的其他模型。 然后,您可以按照本节中的步骤在 iOS 应用中使用模型。 如果您想了解模型的训练方法,则应在上一节的 GitHub 存储库 中查看代码。 让我们详细看一下使用该模型完成魔术的 iOS 代码。 # 使用快速神经传递模型回顾 iOS 代码 `ViewController.mm`中有几个关键代码段,它们在输入图像的预处理和传输图像的后处理中是唯一的: 1. 在步骤 5 中,将两个常量`wanted_width`和`wanted_height`定义为与存储库图像`examples/content/dog.jpg`的图像宽度和高度相同的值: ```py const int wanted_width = 300; const int wanted_height = 400; ``` 2. iOS 的分派队列用于在非 UI 线程中加载和运行我们的快速神经传递模型,并在生成样式转换的图像后,将图像发送到 UI 线程进行显示: ```py dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *img = imageStyleTransfer(@"fst_frozen_quantized"); dispatch_async(dispatch_get_main_queue(), ^{ _lbl.text = @"Tap Anywhere"; _iv.image = img; }); }); ``` 3. 定义了一个浮点数的 3 维张量,该张量用于将输入图像数据转换为: ```py tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({wanted_height, wanted_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor(); ``` 4. 发送到 TensorFlow `Session->Run`方法的输入节点名称和输出节点名称定义为与训练模型时相同: ```py std::string input_layer = "img_placeholder"; std::string output_layer = "preds"; std::vector outputs; tensorflow::Status run_status = session->Run({{input_layer, image_tensor}} {output_layer}, {}, &outputs); ``` 5. 模型完成运行并发送回输出张量(其中包含 0 到 255 范围内的 RGB 值)后,我们需要调用一个名为`tensorToUIImage`的实用函数,以将张量数据首先转换为 RGB 缓冲区: ```py UIImage *imgScaled = tensorToUIImage(model, output->flat(), image_width, image_height); static UIImage* tensorToUIImage(NSString *model, const Eigen::TensorMap, Eigen::Aligned>& outputTensor, int image_width, int image_height) { const int count = outputTensor.size(); unsigned char* buffer = (unsigned char*)malloc(count); for (int i = 0; i < count; ++i) { const float value = outputTensor(i); int n; if (value < 0) n = 0; else if (value > 255) n = 255; else n = (int)value; buffer[i] = n; } ``` 6. 然后,我们将缓冲区转换为`UIImage`实例,然后再调整其大小并返回以供显示: ```py UIImage *img = [ViewController convertRGBBufferToUIImage:buffer withWidth:wanted_width withHeight:wanted_height]; UIImage *imgScaled = [img scaleToSize:CGSizeMake(image_width, image_height)]; return imgScaled; ``` 完整的代码和应用程​​序位于`Ch4/ios/NeuralStyleTransfer`文件夹中。 # 在 Android 中使用快速的神经样式传输模型 在第 2 章,“通过迁移学习对图像进行分类”中,我们描述了如何将 TensorFlow 添加到您自己的 Android 应用中,但没有任何 UI。 让我们创建一个新的 Android 应用程序,以使用我们之前训练并在 iOS 中使用的快速样式传输模型。 由于此 Android 应用程序提供了一个很好的机会来使用最少的 TensorFlow 相关代码,Android UI 和线程化代码来运行完整的 TensorFlow 模型驱动的应用程序,因此,我们将从头开始添加每行代码,以帮助您进一步了解 从头开始开发 Android TensorFlow 应用需要什么: 1. 在 Android Studio 中,选择“文件 | 新增 | 新项目...”,然后输入`FastNeuralTransfer`作为应用程序名称; 在单击“完成”之前,接受所有默认设置。 2. 创建一个新的`assets`文件夹,如图 2.13 所示,然后将您训练过的快速神经传递模型从 iOS 应用中拖动(如果您在上一节中尝试过),或者从文件夹`/tf_files`中拖动,如“训练快速神经样式转换模型”部分步骤 7 所示,以及一些测试图像到`assets`文件夹。 3. 在应用程序的`build.gradle` 文件中,在 `dependencies` 的末尾添加一行 `compile 'org.tensorflow:tensorflow-android:+'`和。 4. 打开`res/layout/activity_main.xml`文件,在其中删除默认的`TextView`,然后首先添加一个`ImageView`以显示样式转换前后的图像: ```py ``` 5. 添加一个按钮以启动样式转换操作: ```py