TensorFlow 智能移动项目:1~5(3)https://developer.aliyun.com/article/1426904
图 3.8:使用不同的模型运行应用并显示检测结果
返回步骤 7 中的函数,FilePathForResourceName
函数是用于返回资源文件路径的助手函数:mscoco_label_map.pbtxt
文件,该文件定义了要检测的 90 个对象类的 ID,内部名称和显示名称。 ,模型图文件和测试图像。 它的实现与我们在上一章的HelloTensorFlow
应用中看到的实现相同。
LoadLablesFile
和GetDisplayName
函数使用 Google Protobuf API 加载和解析mscoco_label_map.pbtxt
文件,并返回显示名称以显示检测到的对象的 ID。
LoadGraph 尝试加载三个用户选择的模型文件之一,并返回加载状态。
这两个关键函数是RunInferenceOnImage
和DrawTopDetections
。 正如我们在“设置 TensorFlow 对象检测 API”部分中所看到的那样,summary_graph
工具显示了我们在应用中使用的三种预训练对象检测模型的以下信息(请注意uint8
类型):
Found 1 possible inputs: (name=image_tensor, type=uint8(4), shape=[?,?,?,3])
这就是为什么我们需要使用uint8
创建一个图像张量,而不是float
类型来加载到我们的模型,否则在运行模型时会出现错误。 另请注意,当我们使用 TensorFlow C++ API 的Session
的Run
方法将image_data
转换为Tensor
类型的image_data
时,我们不使用input_mean
和 input_std
就像我们在使用图像分类模型时所做的(有关详细比较,请参见第 2 章,“通过迁移学习对图像进行分类”的 HelloTensorFlow 应用的RunInferenceOnImage
实现)。 我们知道有四个名为detection_boxes
,detection_scores
,detection_classes
和num_detections
的输出,因此RunInferenceOnImage
具有以下代码来为模型输入图像输入并获得四个输出:
tensorflow::Tensor image_tensor(tensorflow::DT_UINT8, tensorflow::TensorShape({1, image_height, image_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor<uint8, 4>(); tensorflow::uint8* in = image_data.data(); uint8* c_out = image_tensor_mapped.data(); for (int y = 0; y < image_height; ++y) { tensorflow::uint8* in_row = in + (y * image_width * image_channels); uint8* out_row = c_out + (y * image_width * wanted_channels); for (int x = 0; x < image_width; ++x) { tensorflow::uint8* in_pixel = in_row + (x * image_channels); uint8* out_pixel = out_row + (x * wanted_channels); for (int c = 0; c < wanted_channels; ++c) { out_pixel[c] = in_pixel[c]; } } } std::vector<Tensor> outputs; Status run_status = session->Run({{"image_tensor", image_tensor}}, {"detection_boxes", "detection_scores", "detection_classes", "num_detections"}, {}, &outputs);
要在检测到的对象上绘制边界框,我们将outputs
张量向量传递给DrawTopDetections
,后者使用以下代码解析outputs
向量以获取四个输出的值,并循环遍历每次检测以获得边界框值(左,上,右,下)以及检测到的对象 ID 的显示名称,因此您可以编写代码以使用以下名称绘制边界框:
auto detection_boxes = outputs[0].flat<float>(); auto detection_scores = outputs[1].flat<float>(); auto detection_classes = outputs[2].flat<float>(); auto num_detections = outputs[3].flat<float>()(0); LOG(INFO) << "num_detections: " << num_detections << ", detection_scores size: " << detection_scores.size() << ", detection_classes size: " << detection_classes.size() << ", detection_boxes size: " << detection_boxes.size(); for (int i = 0; i < num_detections; i++) { float left = detection_boxes(i * 4 + 1) * image_width; float top = detection_boxes(i * 4 + 0) * image_height; float right = detection_boxes(i * 4 + 3) * image_width; float bottom = detection_boxes((i * 4 + 2)) * image_height; string displayName = GetDisplayName(&imageLabels, detection_classes(i)); LOG(INFO) << "Detected " << i << ": " << displayName << ", " << score << ", (" << left << ", " << top << ", " << right << ", " << bottom << ")"; ... }
当前面的LOG(INFO)
行与图 3.1 中的第二个测试图像一起运行时,以及 TensorFlow Object Detection API 网站上显示的演示图像时,将输出以下信息:
num_detections: 100, detection_scores size: 100, detection_classes size: 100, detection_boxes size: 400 Detected 0: person, 0.916851, (533.138, 498.37, 553.206, 533.727) Detected 1: kite, 0.828284, (467.467, 344.695, 485.3, 362.049) Detected 2: person, 0.779872, (78.2835, 516.831, 101.287, 560.955) Detected 3: kite, 0.769913, (591.238, 72.0729, 676.863, 149.322)
这就是在 iOS 应用中使用现有的经过预训练的对象检测模型所需要的。 如何在 iOS 中使用我们的经过训练的对象检测模型? 事实证明,这与使用预训练模型几乎相同,在处理再训练图像分类模型时,无需像上一章一样修改input_size, input_mean, input_std
和input_name
。 您只需要执行以下操作:
- 将您的训练后的模型(例如,在上一节中创建的
output_inference_graph_ssd_mobilenet.pb
文件,用于模型的训练的标签映射文件,例如pet_label_map.pbtxt
)添加到TFObjectDetectionAPI
项目 - 在
ViewController.mm
中,使用重新训练的模型调用RunInferenceOnImage
- 仍在
ViewController.mm
中,在DrawTopDetections
函数内调用LoadLablesFile([FilePathForResourceName(@"pet_label_map", @"pbtxt") UTF8String], &imageLabels);
而已。 运行该应用,您可以看到针对重新训练的模型对检测到的结果进行了更精细的调整。 例如,使用通过使用牛津宠物数据集进行重新训练而生成的前面的重新训练模型,我们希望看到边界框围绕头部区域而不是整个身体,而这正是我们在图 3.9 中所示的测试图像所看到的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QdwEhv3-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ced173a8-1b82-4868-a274-3cdf666556e1.png)]
图 3.9:比较预训练和再训练模型的检测结果
使用 YOLO2 – 另一种物体检测模型
正如我们在第一部分中提到的,YOLO2 是另一个很酷的对象检测模型,它使用了与 RCNN 系列不同的方法。 它使用单个神经网络将输入图像划分为固定大小的区域(但不像 RCNN 系列方法那样提供区域建议),并预测每个区域的边界框,类别和概率。
TensorFlow Android 示例应用具有使用预训练的 YOLO 模型的示例代码,但没有 iOS 示例。 由于 YOLO2 是最快的对象检测模型之一,而且非常准确(请在其网站上查看其与 SSD 模型的 mAP 比较),因此有必要了解一下如何在 iOS 应用中使用它。
YOLO 使用称为 Darknet 的独特开源神经网络框架来训练其模型。 还有另一个名为 darkflow 的库,该库可以将使用 Darknet 训练的 YOLO 模型的神经网络权重转换为 TensorFlow 图格式,并重新训练预训练的模型。
要以 TensorFlow 格式构建 YOLO2 模型,请首先从这里获取 darkflow。因为它需要 Python3 和 TensorFlow 1.0(Python 2.7 和 TensorFlow 1.4 或更高版本也可能工作),所以我们将使用 Anaconda 来设置一个新的具有 Python3 支持的 TensorFlow 1.0 环境:
conda create --name tf1.0_p35 python=3.5 source activate tf1.0_p35 conda install -c derickl tensorflow
同时运行conda install -c menpo opencv3
以安装 OpenCV 3,这是 darkflow 的另一个依赖项。 现在,将cd
移至 darkflow 目录,然后运行pip install .
安装 darkflow。
接下来,我们需要下载经过预训练的 YOLO 模型的权重-我们将尝试两个 Tiny-YOLO 模型,它们超级快,但不如完整的 YOLO 模型准确。 同时运行 Tiny-YOLO 模型和 YOLO 模型的 iOS 代码几乎相同,因此我们仅向您展示如何运行 Tiny-YOLO 模型。
您可以在 YOLO2 官方网站上下载 tiny-yolo-voc(受 20 个对象类的 PASCAL VOC 数据集训练)和 tiny-yolo(受 80 个对象类的 MS COCO 数据集训练)的权重和配置文件。 或 darkflow 仓库。 现在,运行以下命令将权重转换为 TensorFlow 图文件:
flow --model cfg/tiny-yolo-voc.cfg --load bin/tiny-yolo-voc.weights --savepb flow --model cfg/tiny-yolo.cfg --load bin/tiny-yolo.weights --savepb
生成的两个文件tiny-yolo-voc.pb
和tiny-yolo.pb
将位于built_graph
目录中。 现在,转到 TensorFlow 源根目录,并像上一章一样运行以下命令来创建量化模型:
python tensorflow/tools/quantization/quantize_graph.py --input=darkflow/built_graph/tiny-yolo.pb --output_node_names=output --output=quantized_tiny-yolo.pb --mode=weights python tensorflow/tools/quantization/quantize_graph.py --input=darkflow/built_graph/tiny-yolo-voc.pb --output_node_names=output --output=quantized_tiny-yolo-voc.pb --mode=weights
现在,请按照以下步骤查看如何在我们的 iOS 应用中使用两个 YOLO 模型:
- 将
quantized_tiny-yolo-voc.pb
和quantized_tiny-yolo.pb
都拖到TFObjectDetectionAPI
项目中 - 在
ViewController.mm
中添加两个新的警报操作,因此在运行该应用时,您将看到可用于运行的模型,如图 3.10 所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdaWkWrO-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/532a45ca-35ee-452b-b33a-79d557fee667.png)]
图 3.10:将两个 YOLO 模型添加到 iOS 应用
- 添加以下代码以将输入图像处理到张量中以馈送到输入节点,并在加载了 YOLO 模型图的情况下运行 TensorFlow 会话以生成检测输出:
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT,tensorflow::TensorShape({1, wanted_height, wanted_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor<float, 4>(); tensorflow::uint8* in = image_data.data(); float* out = image_tensor_mapped.data(); for (int y = 0; y < wanted_height; ++y) { ... out_pixel[c] = in_pixel[c] / 255.0f; } std::vector<tensorflow::Tensor> outputs; tensorflow::Status run_status = session->Run({{"input", image_tensor}}, {"output"}, {}, &outputs);
请注意,此处的for-loop
和session->Run
与上一章中用于图像分类的代码以及使用本章前面所示的其他模型进行对象检测的代码中存在细微但重要的区别(我们未显示...
中的代码段,因为与这两个示例中的相同)。 为了使图像数据转换正确,您需要了解模型的详细信息,或者从 Python,Android 或 iOS 的有效示例中学习,当然还要进行必要的调试。 为了正确设置输入和输出节点名称,可以使用summarize_graph
工具或我们多次显示的 Python 代码段。
- 将输出结果传递给名为
YoloPostProcess
的函数,该函数类似于tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java
Android 示例文件中的后处理代码:
tensorflow::Tensor* output = &outputs[0]; std::vector<std::pair<float, int> > top_results; YoloPostProcess(model, output->flat<float>(), &top_results);
我们不会在此处显示其余代码。 您可以在源代码存储库的ch3/ios
中检出完整的 iOS 应用。
- 运行该应用,然后选择 YOLO2 Tiny VOC 或 YOLO2 Tiny COCO,与使用 SSD MobileNet v1 模型相比,您会看到类似的速度,但检测结果的准确率较差。
尽管基于 MobileNet 的 TensorFlow 模型和 Tiny YOLO2 模型的准确率较低,但 TensorFlow 对象检测模型和 YOLO2 模型在移动设备上的运行速度都非常快。 较大的 Faster RNN 模型和完整的 YOLO2 模型更准确,但是它们花费的时间更长,甚至无法在移动设备上运行。 因此,向移动应用添加快速对象检测的最佳方法是使用 SSD MobileNet 或 Tiny-YOLO2 模型,或经过重新训练和微调的模型。 模型的未来版本很可能会具有更好的表现和准确率。 凭借本章介绍的知识,您应该能够在 iOS 应用中快速启用对象检测。
总结
在本章中,我们首先简要概述了各种不同的基于深度学习的对象检测方法。 然后,我们详细介绍了如何使用 TensorFlow 对象检测 API 通过预训练的模型进行现成的推理,以及如何在 Python 中重新训练预训练的 TensorFlow 对象检测模型。 我们还提供了有关如何手动构建 TensorFlow iOS 库,使用该库创建新的 iOS 应用以及如何在 iOS 中使用预先存在和经过重新训练的 SSD MobileNet 和 Faster RCNN 模型的详细教程。 最后,我们展示了在您的 iOS 应用中使用另一种强大的对象检测模型 YOLO2 所需要的内容。
在下一章中,这是我们与计算机视觉相关的第三项任务,我们将仔细研究如何在 Python 和 TensorFlow 中训练和构建有趣的深度学习模型,以及如何在 iOS 和 Android 应用中使用它来添加令人赞叹的图像艺术风格。
四、以惊人的艺术风格变换图片
自从 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 年夏季发表在题为《艺术风格的神经算法》的论文中。它是 2014 年 ImageNet 图像识别挑战赛的获胜者,该挑战赛具有 16 个卷积层或特征映射,分别代表不同级别的图像内容。 在这种原始方法中,首先将最终迁移的图像初始化为与内容图像合并的白噪声图像。 内容损失函数定义为内容图像和结果图像的卷积层conv4_2
上都被馈入 VGG-19 网络后,特定的一组特征表示形式的平方误差损失。 样式损失函数计算样式图像和所得图像在五个不同卷积层上的总误差差。 然后,将总损失定义为内容损失和样式损失的总和。 在训练期间,损失会降到最低,并生成将一个图像的内容与另一个图像的样式混合在一起的结果图像。
尽管原始神经样式迁移算法的结果令人惊叹,但其性能却很差-训练是样式迁移图像生成过程的一部分,通常在 GPU 上花费几分钟,在 CPU 上花费约一个小时才能生成良好的图像。 结果。
如果您对原始算法的细节感兴趣,可以在以下位置阅读该论文以及文档齐全的 Python 实现。我们不会讨论这种原始算法,因为在手机上运行该算法是不可行的,但是尝试该算法很有趣且有用,可以更好地了解如何针对不同的计算机视觉任务使用预训练的深度 CNN 模型。
自然地,在 2016 年,论文中发布了一种“快三个数量级”的新算法,即《实时样式传递和超分辨率的感知损失》,作者是 Justin Johnson 等。 它使用单独的训练过程,并定义了更好的损失函数,这些函数本身就是深度神经网络。 训练后(在下一节中我们将看到,在 GPU 上可能要花费几个小时),使用训练后的模型来生成样式迁移的图像在计算机上几乎是实时的,而在智能手机上只需几秒钟。
使用这种快速神经迁移算法仍然有一个缺点:只能针对特定样式训练模型,因此,要在您的应用中使用不同的样式,必须逐一训练这些样式以为每种样式生成一个模型 。 2017 年发表了一篇名为《学习风格的艺术表现形式》的新论文,它发现一个单一的深度神经网络模型可以概括许多不同的样式。 TensorFlow Magenta 项目包括具有多种样式的预训练模型,我们将在本章的最后两个部分中看到,在 iOS 和 Android 应用中使用这种模型来产生强大而神奇的艺术效果是多么容易。
训练快速的神经样式迁移模型
在本部分中,我们将向您展示如何使用带有 TensorFlow 的快速神经样式迁移算法训练模型。 执行以下步骤来训练这样的模型:
- 在 Mac 的终端上,或者最好在 GPU 驱动的 Ubuntu 上,运行
git clone https://github.com/jeffxtang/fast-style-transfer
,这是 Johnson 的快速样式迁移的 TensorFlow 实现的一个很好的分支,已修改为允许在 iOS 或 Android 应用中使用经过训练的模型。 cd
到快速样式迁移目录,然后运行setup.sh
脚本下载预训练的 VGG-19 模型文件以及 MS COCO 训练数据集,我们在上一章中提到过,注意下载大文件可能需要几个小时。- 运行以下命令,使用名为
starry_night.jpg
的样式图像和名为ww1.jpg
的内容图像进行训练,以创建检查点文件:
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-LNZjkACR-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/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 环境中正常运行。
- 在文本编辑器中打开
evaluate.py
文件,然后取消注释以下两行代码(在 158 和 159 行):
# saver = tf.train.Saver() # saver.save(sess, "checkpoints_ios/fns.ckpt")
- 运行以下命令,以输入图像
img_placeholder
和迁移的图像preds
创建新的检查点:
python evaluate.py --checkpoint checkpoints \ --in-path examples/content/dog.jpg \ --out-path examples/content/dog-output.jpg
- 运行以下命令以构建一个 TensorFlow 图文件,该文件将图定义和检查点中的权重结合在一起。 这将创建一个大约 6.7MB 的
.pb
文件:
python freeze.py --model_folder=checkpoints_ios --output_graph fst_frozen.pb
- 假设您具有
/tf_files
目录,将生成的fst_frozen.pb
文件复制到/tf_files
,cd
直接复制到 TensorFlow 源根目录(可能是~/tensorflow-1.4.0
),然后运行以下命令以生成量化模型的.pb
文件(我们在第 2 章,“通过迁移学习对图像进行分类”中介绍了量化):
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 进行查看,以及希望样式迁移效果更好。
在了解如何在 iOS 和 Android 应用中使用这些模型之前,重要的一点是,您需要记下在第 5 步中使用的,指定为--in-path
值的图像的确切图像宽度和高度参数,并在 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 月)—尝试加载多样式模型文件时将引发以下错误:
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 应用并测试运行该应用:
- 如果您已经具有添加了 TensorFlow 手动库的 iOS 应用,则可以跳过此步骤。 否则,类似于我们在上一章中所做的,创建一个新的基于 Objective-C 的 iOS 应用,例如
NeuralStyleTransfer
,或者在现有应用中,在PROJECT
下创建一个新的用户定义设置,命名为TENSORFLOW_ROOT
,值为$HOME/tensorflow-1.4.0
,假定在那儿已安装 TensorFlow 1.4.0,然后在TARGET
的构建设置中,将其他链接器标志设置为:
-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
然后将标题搜索路径设置为:
$(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
- 将
fst_frozen_quantized.pb
文件和一些测试图像拖放到项目的文件夹中。 从以前的 iOS 应用中,或从本书源代码仓库中Ch4/ios
下的NeuralStyleTransfer
应用文件夹中复制我们在前几章中使用过的相同ios_image_load.mm
和.h
文件到项目中。 - 将
ViewController.m
重命名为ViewController.mm
并将其替换为Ch4/ios/NeuralStyleTransfer
中的ViewController.h
和.mm
文件。 在测试运行该应用后,我们将详细介绍核心代码段。 - 在 iOS 模拟器或 iOS 设备上运行该应用,您将看到一张狗图片,如图 4.2 所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXLQcpNY-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/9bdc2a19-7e59-4f72-b41f-ea3eeba0c5c5.png)]
图 4.2:应用样式之前的原始狗图片
- 点击以选择快速样式迁移,几秒钟后,您将在图 4.3 中看到一张新图片,其中已迁移了繁星点点的夜色:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdEYvQG1-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/a78207bb-2407-4343-b84d-2f17aacfd4ff.png)]
图 4.3:就像让梵高画出您喜欢的狗一样
您只需选择喜欢的图片作为样式图像,然后按照上一节中的步骤操作,即可轻松构建具有不同样式的其他模型。 然后,您可以按照本节中的步骤在 iOS 应用中使用模型。 如果您想了解模型的训练方法,则应在上一节的 GitHub 存储库中查看代码。 让我们详细看一下使用该模型完成魔术的 iOS 代码。
回顾快速神经迁移模型的 iOS 代码
ViewController.mm
中有几个关键代码段,它们在输入图像的预处理和迁移图像的后处理中是唯一的:
- 在步骤 5 中,将两个常量
wanted_width
和wanted_height
定义为与存储库图像examples/content/dog.jpg
的图像宽度和高度相同的值:
const int wanted_width = 300; const int wanted_height = 400;
- iOS 的分派队列用于在非 UI 线程中加载和运行我们的快速神经迁移模型,并在生成样式迁移的图像后,将图像发送到 UI 线程进行显示:
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 维张量,该张量用于将输入图像数据转换为:
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({wanted_height, wanted_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor<float, 3>();
- 发送到 TensorFlow
Session->Run
方法的输入节点名称和输出节点名称定义为与训练模型时相同:
std::string input_layer = "img_placeholder"; std::string output_layer = "preds"; std::vector<tensorflow::Tensor> outputs; tensorflow::Status run_status = session->Run({{input_layer, image_tensor}} {output_layer}, {}, &outputs);
- 模型完成运行并发送回输出张量(其中包含 0 到 255 范围内的 RGB 值)后,我们需要调用一个名为
tensorToUIImage
的实用函数,以将张量数据首先转换为 RGB 缓冲区:
UIImage *imgScaled = tensorToUIImage(model, output->flat<float>(), image_width, image_height); static UIImage* tensorToUIImage(NSString *model, const Eigen::TensorMap<Eigen::Tensor<float, 1, Eigen::RowMajor>, 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; }
- 然后,我们将缓冲区转换为
UIImage
实例,然后再调整其大小并返回以供显示:
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 应用需要什么:
- 在 Android Studio 中,选择“文件 | 新增 | 新项目…”,然后输入
FastNeuralTransfer
作为应用名称; 在单击“完成”之前,接受所有默认设置。 - 创建一个新的
assets
文件夹,如图 2.13 所示,然后将您训练过的快速神经迁移模型从 iOS 应用中拖动(如果您在上一节中尝试过),或者从文件夹/tf_files
中拖动,如“训练快速神经样式迁移模型”部分步骤 7 所示,以及一些测试图像到assets
文件夹。 - 在应用的
build.gradle
文件中,在dependencies
的末尾添加一行compile 'org.tensorflow:tensorflow-android:+'
和。 - 打开
res/layout/activity_main.xml
文件,在其中删除默认的TextView
,然后首先添加一个ImageView
以显示样式迁移前后的图像:
<ImageView android:id="@+id/imageview" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
- 添加一个按钮以启动样式迁移操作:
<Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Style Transfer" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.502" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.965" />
- 在应用的
MainActivity.java
文件中,首先输入我们最重要的导入:
import org.tensorflow.contrib.android.TensorFlowInferenceInterface;
TensorFlowInferenceInterface
提供 JAVA 接口来访问本机 TensorFlow 推理 API。 然后确保MainActivity
类实现了Runnable
接口,因为我们需要保持我们的应用响应速度,并在工作线程上加载并运行 TensorFlow 模型。
- 在类的开头,定义六个常量,如下所示:
private static final String MODEL_FILE = "file:///android_asset/fst_frozen_quantized.pb"; private static final String INPUT_NODE = "img_placeholder"; private static final String OUTPUT_NODE = "preds"; private static final String IMAGE_NAME = "pug1.jpg"; private static final int WANTED_WIDTH = 300; private static final int WANTED_HEIGHT = 400;
您可以将任何训练有素的模型文件用于MODEL_FILE
。 INPUT_NODE
和OUTPUT_NODE
的值与我们在 Python 训练脚本中设置并在 iOS 应用中使用的值相同。 同样,WANTED_WIDTH
和WANTED_HEIGHT
与我们在“训练快速神经样式迁移模型”部分的第 5 步中使用的--in-path
图像的宽度和高度相同。
- 声明四个实例变量:
private ImageView mImageView; private Button mButton; private Bitmap mTransferredBitmap; private TensorFlowInferenceInterface mInferenceInterface;
mImageView
和mButton
将使用onCreate
方法中的简单findViewById
方法进行设置。 mTransferredBitmap
将保留已迁移图像的位图,以便mImageView
可以显示它。 mInferenceInterface
用于加载我们的 TensorFlow 模型,将输入图像输入模型,运行模型,并返回推理结果。
- 在我们的 TensorFlow 推断线程向
Handler
实例发送消息之后,创建一个Handler
实例来处理在主线程中显示最终迁移的图像的任务,我们还创建一个方便的Toast
消息:
Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { mButton.setText("Style Transfer"); String text = (String)msg.obj; Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); mImageView.setImageBitmap(mTransferredBitmap); } };
- 在
onCreate
方法内部,我们将使用mImageView
实例变量绑定布局 xml 文件中的ImageView
,将测试图像的位图加载到assets
文件夹中,并在ImageView
中显示 :
mImageView = findViewById(R.id.imageview); try { AssetManager am = getAssets(); InputStream is = am.open(IMAGE_NAME); Bitmap bitmap = BitmapFactory.decodeStream(is); mImageView.setImageBitmap(bitmap); } catch (IOException e) { e.printStackTrace(); }
- 类似地设置
mButton
并设置一个点击监听器,以便在点击按钮时,创建并启动一个新线程,并调用run
方法:
mButton = findViewById(R.id.button); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mButton.setText("Processing..."); Thread thread = new Thread(MainActivity.this); thread.start(); } });
- 在线程的
run
方法中,我们首先声明三个数组,并为其分配适当的内存:intValues
数组保存测试图像的像素值,每个像素值代表 32 位 ARGB(Alpha,红,绿,蓝色)值;floatValues
数组如模型所预期的那样分别保存每个像素的红色,绿色和蓝色值,因此其大小是intValues
的三倍,并且outputValues
的大小与floatValues
相同 ],但保留模型的输出值:
public void run() { int[] intValues = new int[WANTED_WIDTH * WANTED_HEIGHT]; float[] floatValues = new float[WANTED_WIDTH * WANTED_HEIGHT * 3]; float[] outputValues = new float[WANTED_WIDTH * WANTED_HEIGHT * 3];
然后,我们获得测试图像的位图数据,对其进行缩放以匹配训练中使用的图像的大小,然后将缩放后的位图的像素加载到intValues
数组并将其转换为floatValues
:
Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open(IMAGE_NAME)); Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, WANTED_WIDTH, WANTED_HEIGHT, true); scaledBitmap.getPixels(intValues, 0, scaledBitmap.getWidth(), 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight()); for (int i = 0; i < intValues.length; i++) { final int val = intValues[i]; floatValues[i*3] = ((val >> 16) & 0xFF); floatValues[i*3+1] = ((val >> 8) & 0xFF); floatValues[i*3+2] = (val & 0xFF); }
注意,val
或intValues
像素数组的每个元素是一个 32 位整数,在其每个 8 位区域中均保留 ARGB。 我们使用向右移位(用于红色和绿色)和按位与运算来提取每个像素的红色,绿色和蓝色值,而忽略intValues
元素中最左边的 8 位的 Alpha 值。 因此floatValues[i*3]
,floatValues[i*3+1]
和floatValues[i*3+2]
分别保持像素的红色,绿色和蓝色值。
现在,我们创建一个新的TensorFlowInferenceInterface
实例,并在其中将AssetManager
实例和模型文件名传递到assets
文件夹中,然后使用 TensorFlowInferenceInterface
实例将转换后的[ floatValues
数组。 如果模型需要多个输入节点,则可以调用多个feed
方法。 然后,我们通过传递输出节点名称的字符串数组来运行模型。 在这里,对于我们的快速样式迁移模型,我们只有一个输入节点和一个输出节点。 最后,我们通过传递输出节点名称来获取模型的输出值。 如果希望接收多个输出节点,则可以调用多个访存:
AssetManager assetManager = getAssets(); mInferenceInterface = new TensorFlowInferenceInterface(assetManager, MODEL_FILE); mInferenceInterface.feed(INPUT_NODE, floatValues, WANTED_HEIGHT, WANTED_WIDTH, 3); mInferenceInterface.run(new String[] {OUTPUT_NODE}, false); mInferenceInterface.fetch(OUTPUT_NODE, outputValues);
模型生成的outputValues
在每个元素中都保留 0 到 255 之间的 8 位红色,绿色和蓝色值之一,我们首先对红色和绿色值使用左移操作,但是具有不同的移位大小(16 和 8),然后使用按位或运算将 8 位 Alpha 值(0xFF
)与 8 位 RGB 值组合,将结果保存在intValues
数组中:
for (int i=0; i < intValues.length; ++i) { intValues[i] = 0xFF000000 | (((int) outputValues[i*3]) << 16) | (((int) outputValues[i*3+1]) << 8) | ((int) outputValues[i*3+2]);
然后,我们创建一个新的Bitmap
实例,并使用intValues
数组设置其像素值,将位图缩放到测试图像的原始大小,并将缩放后的位图保存到mTransferredBitmap
:
Bitmap outputBitmap = scaledBitmap.copy( scaledBitmap.getConfig() , true); outputBitmap.setPixels(intValues, 0, outputBitmap.getWidth(), 0, 0, outputBitmap.getWidth(), outputBitmap.getHeight()); mTransferredBitmap = Bitmap.createScaledBitmap(outputBitmap, bitmap.getWidth(), bitmap.getHeight(), true);
最后,我们向主线程的处理器发送一条消息,以使其知道显示样式迁移图像的时间:
Message msg = new Message(); msg.obj = "Tranfer Processing Done"; mHandler.sendMessage(msg);
因此,总共不到 100 行代码,您就有了一个完整的 Android 应用,可以对图像进行惊人的样式迁移。 在 Android 设备或虚拟设备上运行该应用,首先将看到一个带有按钮的测试图像,点击该按钮,几秒钟后,您将看到样式迁移的图像,如图 4.4 所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7Jq4uxa-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/07ef305b-1e23-4a4c-98f5-faf14f398d65.png)]
图 4.4:Android 上原始图像和样式迁移的图像
快速神经样式模型存在的一个问题是,即使量化后每个模型只有 1.7MB,我们仍然需要针对每种样式分别进行训练,并且每个训练的模型只能支持一种样式迁移。 幸运的是,这个问题有很好的解决方案。
在 iOS 中使用 TensorFlow Magenta 多样式模型
TensorFlow Magenta 项目允许您使用 10 多种经过预训练的模型来生成新的音乐和图像。 在本节和下一节中,我们将重点介绍使用 Magenta 的图像样式化模型。 您可以单击链接在计算机上安装 Magenta,尽管要在移动应用中使用其炫酷的图像样式迁移模型,也不必安装 Magenta。 基于论文《艺术风格的习得表示》实现的 Magenta 预训练样式迁移模型,消除了一个模型只能具有一种风格的限制,并允许多种风格包含在单个模型文件中,您可以选择使用这些样式的任意组合。 您可以在这个页面上快速浏览该演示,但可以在此处下载两个预训练的检查点模型。 由于检查点文件中保存了某些NaN
(不是数字)错误,因此无法直接在您的移动应用中使用。 我们不会详细说明如何删除这些数字并生成可在您的应用中使用的.pb
模型文件(如果感兴趣,您可以查看这里),我们仅使用 TensorFlow Android 示例tensorflow/examples/android/assets
中包含的经过预训练的stylize_quantized.pb
模型文件来查看其工作原理。
如果您确实想训练自己的模型,则可以按照前面的image_stylization
链接中的训练模型下的步骤进行。 但是请注意,您至少需要 500GB 的可用磁盘空间才能下载 ImageNet 数据集,并需要强大的 GPU 来完成训练。 在本节或下一节中看到代码和结果之后,您更有可能对预训练的stylize_quantized.pb
模型启用的炫酷样式迁移效果感到满意。
在本章前面创建的 iOS 应用中,执行以下步骤来使用和运行多样式模型:
- 将
stylize_quantized.pb
文件从tensorflow/examples/android/assets
拖放到 Xcode 中的 iOSapps
文件夹中。 - 使用用于加载和处理快速迁移样式模型的相同
dispatch_async
,向抽头处理器中添加新的UIAlertAction
:
UIAlertAction* multi_style_transfer = [UIAlertAction actionWithTitle:@"Multistyle Transfer" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { _lbl.text = @"Processing..."; _iv.image = [UIImage imageNamed:image_name]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *img = imageStyleTransfer(@"stylize_quantized"); dispatch_async(dispatch_get_main_queue(), ^{ _lbl.text = @"Tap Anywhere"; _iv.image = img; }); }); }];
- 将
input_layer
和output_layer
值替换为新模型的正确值,并添加一个名为style_num
的新输入节点名称(这些值来自StylizeActivity.java
中的示例 Android 代码,但您也可以使用summarize_graph
工具,TensorBoard 或我们在前几章中显示的代码段中找到它们):
std::string input_layer = "input"; std::string style_layer = "style_num"; std::string output_layer = "transformer/expand/conv3/conv/Sigmoid";
- 与快速样式迁移模型不同,此处的多样式模型期望使用 4 维浮点张量作为图像输入:
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, wanted_height, wanted_width, wanted_channels})); auto image_tensor_mapped = image_tensor.tensor<float, 4>();
- 我们还需要将
style_tensor
定义为形状为[NUM_STYLES * 1]
的另一个张量,其中NUM_STYLES
在ViewController.mm
的开头定义为const int NUM_STYLES = 26;
。 数字 26 是stylize_quantized.pb
模型文件中内置的样式数,您可以在其中运行 Android TF 风格化应用并查看 26 种结果,如图 4.5 所示。 请注意,第 20 张图片(左下角的图片)是梵高熟悉的繁星点点的夜晚:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4095Y9F-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ea7a04fe-8b56-47b2-ac5a-7f25ced4acbd.png)]
图 4.5:多样式模型中的 26 种样式图像
tensorflow::Tensor style_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({ NUM_STYLES, 1})); auto style_tensor_mapped = style_tensor.tensor<float, 2>(); float* out_style = style_tensor_mapped.data(); for (int i = 0; i < NUM_STYLES; i++) { out_style[i] = 0.0 / NUM_STYLES; } out_style[19] = 1.0;
out_style
数组中所有值的总和必须为 1,最终的样式迁移图像将是由out_style
数组中指定的值加权的样式的混合。 例如,前面的代码将仅使用繁星点点的夜晚样式(数组索引 19 对应于图 4.5 中的样式图像列表中的第 20 个图像)。
如果希望将繁星点点的夜景图像和右上角图像均匀混合,则需要用以下代码替换前面代码块中的最后一行:
out_style[4] = 0.5; out_style[19] = 0.5;
如果您希望所有 26 种样式均等地混合使用,请将前面的for
循环更改为以下样式,并且不要将其他值设置为任何特定的out_style
元素:
for (int i = 0; i < NUM_STYLES; i++) { out_style[i] = 1.0 / NUM_STYLES; }
稍后,您将在图 4.8 和 4.9 中看到这三种设置的样式迁移效果。
- 将
session->Run
调用更改为以下行,以将图像张量和样式张量都发送到模型:
tensorflow::Status run_status = session->Run({{input_layer, image_tensor}, {style_layer, style_tensor}}, {output_layer}, {}, &outputs);
这些就是使用多样式模型运行 iOS 应用所需的全部更改。 现在运行您的应用,您将首先看到如图 4.6 所示的内容:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKmu1OCt-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f2841f1f-b0c3-412d-822b-5e9d0f34f235.png)]
TensorFlow 智能移动项目:1~5(5)https://developer.aliyun.com/article/1426906