# 认识植物种类 该项目将深入讨论如何构建自定义的 TensorFlow Lite 模型,该模型能够从图像中识别植物物种。 该模型将在移动设备上运行,并将主要用于识别不同的植物物种。 该模型使用在 TensorFlow 的 Keras API 上开发的深层**卷积神经网络**(**CNN**)进行图像处理。 本章还向您介绍了如何使用基于云的 API 来执行图像处理。 以 **Google Cloud Platform**(**GCP**)提供的 Cloud Vision API 为例。 在本章结束时,您将了解基于云的服务对于**深度学习**(**DL**)应用程序的重要性,以及设备模型对脱机执行的好处 以及移动设备上的即时深度学习任务。 在本章中,我们将介绍以下主题: * 图像分类简介 * 了解项目架构 * 介绍 Cloud Vision API * 配置 Cloud Vision API 进行图像识别 * 使用**软件开发套件**(**SDK**)/工具来建立模型 * 创建用于图像识别的自定义 TensorFlow Lite 模型 * 创建 Flutter 应用程序 * 运行图像识别 # 技术要求 本章的技术先决条件如下: 1. 具有 Python 3.6 及更高版本的 Anaconda 2. TensorFlow 2.0 3. 启用了结算功能的 GCP 帐户 4. 扑 [您可以在 GitHub 存储库中找到本章介绍的代码](https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects/tree/master/Chapter4)。 # 图像分类简介 图像分类是当今**人工智能**(**AI**)的主要应用领域。 我们可以在我们周围的许多地方找到图像分类的实例,例如手机的面部解锁,对象识别,光学字符识别,照片中人物的标记等等。 从人的角度来看,这些任务看起来很简单,但对于计算机而言却并不那么简单。 首先,系统必须从图像中识别出物体或人,并在其周围绘制一个边界框,然后进行分类。 这两个步骤都是计算密集型的,很难在计算机上执行。 研究人员每天都在努力克服图像处理中的若干挑战,例如戴眼镜或新留胡子的人的脸部识别,在拥挤的地方通过脸部识别和跟踪多个人,以及新样式的字符识别。 手写或全新的语言。 深度学习一直以来都是克服这些挑战的好工具,它能够学习图像中的几种不可见图案。 深度学习中用于图像处理的一种非常常见的方法是部署 CNN,我们已经在前面的章节中进行了介绍。 要查看其概念和基本工作,请参阅“第 2 章”,“移动视觉–使用设备上模型的人脸检测”。 在这个项目中,我们将介绍如何将这些模型转换为可以在移动设备上高效运行的压缩模型。 您可能想知道我们将如何构建这些模型。 为了简化语法,对 TensorFlow API 的强大支持以及广泛的技术支持社区,我们将使用 Python 构建这些模型。 很明显,您的开发计算机上将需要 Python 运行时,但对于该项目,我们将选择一种更快,更强大的选项-Google 的 Colaboratory 环境。 Colaboratory(或简称为 Colab)为即时可用的运行时提供了几个重要的**机器学习**(**ML**)以及与运行时预装的数据科学相关的模块。 另外,Colaboratory 还为启用**图形处理单元**(**GPU**)和 **Tensor Processing Unit**(**TPU**)的运行时提供支持。 训练深度学习模型可谓小菜一碟。 然后,我们将直接在设备上部署 TensorFlow Lite 模型,这是一种适合快速运行且不需要定期更新的模型的良好做法。 让我们开始了解项目架构。 # 了解项目架构 我们将在本章中构建的项目将包括以下技术: * [**TensorFlow**](https://www.tensorflow.org):使用 CNN 构建分类模型 * [**TensorFlow Lite**](https://www.tensorflow.org/lite):一种浓缩 TensorFlow 模型的格式,可以在移动设备上高效运行 * [**Flutter**](https://flutter.dev):跨平台应用程序的开发库 您可以通过访问前面的链接来了解这些技术。 以下屏幕快照给出了这些技术在该项目中发挥作用的框图: ![](img/3605fe2e-c86c-438b-aa36-2aef8854ff9b.png) 首先,我们将在包含数百张图像的数据集上训练分类模型。 为此,我们将使用 Python 构建 TensorFlow 模型。 然后,必须以的格式保存模型。 **tflite** ,是 TensorFlow Lite 模型的扩展。 后端到此结束,我们切换到前端。 在前端,我们首先使用 Flutter 构建一个应用程序,该应用程序可以从设备上存在的图库中加载图像。 Firebase 上的预测模型已下载并缓存到设备上。 从图库中选择的图像将传递到模型,该模型将预测包含图像中显示的植物物种名称的标签。 模型存储在移动设备上,即使离线也可以使用模型。 设备上模型是在移动应用程序上使用深度学习的强大且首选的方式。 如今,普通人的手机上有几种应用程序使用设备上的模型来为其应用程序带来智能。 设备上模型通常是在桌面上开发的模型的压缩形式,并且可能会或可能不会编译为字节码。 诸如 **TensorFlow Lite** 之类的框架在上执行特殊的优化。 **tflite** 模型,使其比非移动形式的体积更小,运行更快。 但是,在我们开始为任务构建定制模型之前,让我们全面了解一下我们可以使用哪些预先存在的工具或服务来执行此类任务。 # 介绍 Cloud Vision API Cloud Vision API 是 GCP 套件中流行的 API。 它已成为使用计算机视觉构建应用程序的基准服务。 简而言之,计算机视觉是计算机识别图像中实体的能力,范围从人脸到道路和自动驾驶任务的车辆。 此外,计算机视觉可用于使人类视觉系统执行的任务自动化,例如计算道路上行驶中的车辆的数量,以及观察物理环境的变化。 计算机视觉已在以下领域得到广泛应用: * 在社交媒体平台上标记公认的面孔 * 从图像中提取文本 * 从图像中识别物体 * 自动驾驶汽车 * 基于医学影像的预测 * 反向图像搜索 * 地标检测 * 名人认可 通过 Cloud Vision API,可以轻松访问前面的某些任务,并为每个识别的实体返回标签。 例如,我们可以看到在下面的屏幕截图中,正确识别了具有 200 年历史的著名工程学杰作 Howrah Bridge。 根据有关地标的信息,可以预测该图像属于加尔各答市: ![](img/84968b01-ffbe-4752-8c16-f337ca721d96.png) 至于前面截图的标签,最主要的标签是**桥**和**悬索桥**,它们都与桥有关。 如前面的屏幕截图所示,还可以通过单击“响应”部分中的“文本”选项卡来检查图像中是否有任何可识别的文本。 要检查图像是否适合安全搜索或单击其中是否有干扰内容的内容,请单击“安全搜索”选项卡。 例如,从著名名人那里接到电话的图像很可能是欺骗,如以下屏幕快照所示: ![](img/a1fab1c9-f3db-4632-80e5-dfa94eb21e91.png) 接下来,我们将从设置 GCP 帐户开始,然后继续创建用于使用 API​​的示例 Flutter 应用程序。 # 配置 Cloud Vision API 进行图像识别 在本节中,我们将准备通过 Flutter 应用程序使用 Cloud Vision API。 必须为此任务拥有一个 Google 帐户,我们假设您已经拥有该帐户。 否则,[您可以通过以下链接注册免费创建 Google 帐户](https://accounts.google.com/signup)。 如果您目前拥有 Google 帐户,请继续进行下一部分。 # 启用 Cloud Vision API 要创建 GCP 帐户,[请转到以下链接](https://cloud.google.com)。 初始注册后,您将能够看到类似于以下屏幕截图的仪表板: ![](img/d7313084-9351-4564-aa89-f323433b11c0.png) 在左上角,您将能够看到三栏菜单,该菜单会列出 GCP 上所有可用的服务和产品的列表。 项目名称显示在搜索栏的左侧。 确保您为该项目创建并启用计费功能,以便本章进一步介绍。 在右侧,您可以看到用户个人资料信息,通知和 Google Cloud Shell 调用图标。 仪表板中心显示当前用户正在运行的服务的各种日志和统计信息。 为了访问 Cloud Vision API 并使用它,我们首先需要为项目启用它并为服务创建 API 密钥。 为此,请执行以下步骤: 1. 点击左上方的汉堡菜单图标。 这将弹出一个菜单,类似于以下屏幕快照中所示的菜单: ![](img/d8ff1b34-0395-4639-b48d-b558bdff7d8c.png) 2. 单击“ API 和服务”选项。 这将打开 API 仪表板,其中显示了与项目中启用的 API 相关的统计信息。 3. 单击启用 API 和服务按钮。 4. 在出现的搜索框中,键入 Cloud Vision API。 5. 单击相关的搜索结果。 该 API 提供程序将列为 Google。 6. API 页面打开后,单击“启用”。 之后,应该显示一个图标,表明您已启用此 API,并且“启用”按钮变为“管理”。 为了能够使用 Cloud Vision API,您必须为此服务创建一个 API 密钥。 我们将在下一部分中进行此操作。 # 创建 Cloud Vision API 密钥 现在,您必须创建一个 API 密钥来访问 API 并从中获取响应。 为此,请执行以下步骤: 1. 再次打开左侧的导航菜单,并将鼠标悬停在“ API 和服务”菜单项上。 出现一个子菜单-单击“凭据”。 2. 单击创建凭据按钮。 在显示的下拉菜单中,选择 API 密钥,如以下屏幕截图所示: ![](img/c1939d94-1eff-481d-9f17-4360050a2f4a.png) 3. API 密钥已创建。 在调用 Cloud Vision API 时,您将需要此 API 密钥。 API 密钥方法仅适用于 GCP 的部分选定 API 和服务,并非十分安全。 如果要完全访问所有 API 和服务以及细粒度的安全性,则需要对服务帐户使用该方法。 为此,[您可以阅读 GCP 官方文档中的以下文章](https://cloud.google.com/docs/authentication/)。 有了 API 密钥,您现在就可以通过 Flutter 应用程序进行 API 调用了。 在下一部分中,我们将在**合作**上开发预测模型,并将其保存为`.tflite`模型。 # 使用 SDK /工具构建模型 我们介绍了针对现有任务使用预先存在的基于服务的深度学习模型的准备工作,以预测图片中存在的植物种类。 我们将在来自五种不同花的样本上训练图像分类器模型。 然后,模型将尝试确定花朵的任何图像可能所属的物种。 但是,此类模型通常在通常可用的数据集上进行训练,并且有时不具备特定的要求(例如,在科学实验室中)。 因此,您必须学习如何建立自己的模型来预测植物种类。 这可以通过完全从头训练模型或通过扩展先前存在的模型来实现。 从头开始完全训练模型的好处是,您可以完全控制输入到模型中的数据,以及训练过程中对模型所做的任何学习。 但是,如果以这种方式设计模型,则可能会出现缓慢或偏差。 TensorFlow 团队扩展了诸如 MobileNet 模型之类的预训练模型,其优点是速度超快。 该方法的缺点是它可能不如从头开始构建的模型那样准确,但是时间准确率的折衷使 MobileNet 模型更适合在移动设备上运行。 偏差是机器学习模型中非常关键的问题。 在统计术语中,这种偏差(或抽样偏差)是指数据集中的偏斜,即对于数据集中的每个分类类别,其样本数均相等。 这样的类别将获得较少的训练样本,因此很有可能被模型的输出预测所忽略。 偏见模型的一个很好的例子可能是仅在小孩脸上训练的面部识别模型。 该模型可能完全无法识别成年人或老年人的面孔。 [您可以在汗学院(Khan Academy)的以下课程中了解有关识别样本偏差的更多信息](https://www.khanacademy.org/math/ap-statistics/gathering-data-ap/sampling-observational-studies/a/identifying-bias-in-samples-and-surveys)。 因此,在接下来的部分中,我们将使用 MobileNet 模型来实现在移动设备上快速执行的功能。 为此,我们将使用 TensorFlow 的 Keras API。 用于该任务的语言是 Python,如前所述,它最能涵盖 TensorFlow 框架的功能。 我们假定您在接下来的部分中具有 Python 的基本工作知识。 但是,重要的是要了解 TensorFlow 和 Keras 在此项目中如何协同工作。 我们将在协作环境中工作。 让我们从了解该工具开始。 # Google 合作介绍 Google 提供的协作工具允许用户在公司提供的计算资源上运行类似**笔记本**的运行时,并可以选择免费使用 GPU 和 TPU,只要用户需要即可。 运行时预装了几个与 ML 和数据科学相关的 Python 模块。 Colaboratory 中的笔记本电脑都可以直接从代码内访问 GCP API(具有适当的配置)。 每个笔记本电脑都有自己的临时存储空间,当断开运行时时,该存储空间将被销毁。 同样,可以将 Colaboratory 笔记本与 GitHub 同步,从而实现最新的版本控制。 通常,协作笔记本位于用户的 Google 云端硬盘存储中。 它们可以与多个用户实时共享和一起工作。 要打开合作实验室,[请转到以下链接](https://colab.research.google.com)。 您将获得一个样本,欢迎笔记本。 随意浏览欢迎笔记本,以基本了解 Colaboratory 的工作方式。 在笔记本电脑的左侧,您将能够看到导航选项卡药丸,如以下屏幕截图所示: ![](img/2d1ddb05-b1e3-4af7-ad2e-4930fababe26.png) “目录”选项卡显示笔记本中创建的标题和子标题,并使用 Markdown 格式进行声明。 “代码片段”选项卡提供了快速单击并插入代码片段的功能,以用于 Colaboratory 上的某些常用功能。 如果您对协作实验室不是很熟悉,但希望执行特定任务,则可能需要在此处搜索任务。 第三个选项卡“文件”是分配给此笔记本的存储空间。 此处存储的文件是此笔记本的专用文件,不会在其他任何地方显示。 使用脚本下载或脚本创建的所有文件都存储在此处。 您可以使用此屏幕上的文件管理器来浏览笔记本的整个目录结构。 在右侧,主要内容部分是笔记本本身。 为了熟悉 Colaboratory 和 Notebooks 的使用,[我们强烈建议您阅读以下文章](https://www.geeksforgeeks.org/how-to-use-google-colab/)。 # 创建用于图像识别的自定义 TensorFlow Lite 模型 一旦您在 Colaboratory 取得了不错的成绩,我们所有人都将建立自定义的 TensorFlow Lite 模型,用于识别植物物种的任务。 为此,我们将从新的协作笔记本开始并执行以下步骤: 1. 导入项目所需的模块。 首先,我们导入 TensorFlow 和 NumPy。 NumPy 对于处理图像阵列很有用,而 TensorFlow 将用于构建 CNN。 可以在以下片段中看到导入模块的代码: ```py !pip install tf-nightly-gpu-2.0-preview import tensorflow as tf import numpy as np import os ``` 注意第一行中使用的`!pip install `命令。 这用于在正在运行的 Colaboratory 笔记本中安装软件包,在这种情况下,该笔记本将安装最新的 TensorFlow 版本,该版本内部实现了 Keras 库,该库将用于构建 CNN。 您可以在以下位置阅读有关使用`!pip install`命令以及其他将新库导入并安装到您的 Colaboratory 运行时的方法的[更多信息](https://colab.research.google.com/notebooks/snippets/importing_libraries.ipynb)。 2. 要运行代码单元,请按住 *Shift* 键并按`Enter`。 TensorFlow 版本的下载和安装进度显示在您执行代码的单元下方。这将需要几秒钟,之后您会收到类似于`Successfully installed , , ...`的消息。 3. 最后,我们需要`os`模块来处理文件系统上的文件。 4. 下载数据集并提取图像。 现在,我们将从可用的**统一资源定位器**(**URL**)下载数据集,并将其提取到名为`/content/flower_photos`的文件夹中,如以下代码块所示: ```py _URL = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz" zip_file = tf.keras.utils.get_file(origin=_URL, fname="flower_photos.tgz", extract=True, cache_subdir='/content',) base_dir = os.path.join(os.path.dirname(zip_file), 'flower_photos') ``` 您可以使用左侧面板上的“文件”选项卡浏览提取的文件夹的内容。 您会发现该文件夹还包含五个其他文件夹,其名称分别为:雏菊,蒲公英,玫瑰,向日葵和郁金香。 这些将是花朵的种类,我们将在其上训练我们的模型,此后称为**标签**。 下一步,我们将再次讨论这些文件夹名称。 5. 下一步是设置生成器,以将数据传递到基于 TensorFlow 的 Keras 模型。 6. 现在,我们将创建两个生成器函数,用于将数据输入 Keras 神经网络。 Keras 的`ImageDataGenerator`类提供了两个实用程序函数,可通过使用`flow_from_directory`方法读取磁盘或通过使用`flow_from_dataframe`方法将图像转换为 NumPy 数组来将数据馈送到 Python 程序。 在这里,我们将使用`flow_from_directory`方法,因为我们已经有一个包含图像的文件夹。 但是,在此必须注意,包含图像的文件夹名称与图像所属的标签相同是故意的。 这是`flow_from_directory`方法要求其才能正常运行的文件夹结构的设计。 [您可以在此处阅读有关此方法的更多信息](https://theailearner.com/2019/07/06/imagedatagenerator-flow_from_directory-method/)。 可以使用以下屏幕快照中显示的目录树来对此进行总结: ![](img/62b369e0-1d10-4ef5-8947-cc9d7bd94fc4.png) 7. 然后,我们创建`ImageDataGenerator`类的对象,并使用它为训练数据集创建生成器,如以下代码块所示: ```py IMAGE_SIZE = 224 BATCH_SIZE = 64 datagen = tf.keras.preprocessing.image.ImageDataGenerator( rescale=1./255, validation_split=0.2) train_generator = datagen.flow_from_directory( base_dir, target_size=(IMAGE_SIZE, IMAGE_SIZE), batch_size=BATCH_SIZE, subset='training') ``` `datagen`对象采用两个参数-`rescale`和`validation_split`。 `rescale`参数告诉对象将所有黑白图像转换为`0`到`255`的范围,就像**红色,绿色和蓝色**(**RGB** 的规模,因为 MobileNet 模型已经在 RGB 图像上进行了训练。 `validation_split`参数从数据集中分配 20%(0.2 x 100)的图像作为验证集。 但是,我们也需要为验证集创建一个生成器,就像我们为训练集所做的那样。 训练集生成器`train_generator`接受`target_size`和`batch_size`参数以及其他参数。 `target_size`参数设置要生成的图像的尺寸。 这样做是为了与 MobileNet 模型中的图像尺寸匹配。 `batch_size`参数指示单个批次应生成多少个图像。 8. 对于验证集,我们具有生成器,如以下代码块所示: ```py val_generator = datagen.flow_from_directory( base_dir, target_size=(IMAGE_SIZE, IMAGE_SIZE), batch_size=BATCH_SIZE, subset='validation') ``` 9. 让我们快速看一下这些生成器生成的数据的形状,如下所示: ```py for image_batch, label_batch in train_generator: break image_batch.shape, label_batch.shape ``` 这将产生以下输出:`((64, 224, 224, 3), (64, 5))`,这意味着在第一批`train_generator`中,创建了尺寸为 224 x 224 x 3 的 64 个图像,以及 5 个单编码格式的 64 个标签。 10. 可以通过运行以下代码来获取分配给每个标签的编码索引: ```py print(train_generator.class_indices) ``` 这将产生以下输出:`{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}`。 请注意标签名称的字母顺序。 11. 现在,我们将保存这些标签,以备将来在 Flutter 应用程序中部署模型时使用,如下所示: ```py labels = '\n'.join(sorted(train_generator.class_indices.keys())) with open('labels.txt', 'w') as f: f.write(labels) ``` 12. 接下来,我们将创建一个基本模型并冻结层。 在这一步中,我们将首先创建一个基础模型,然后冻结除最后一层之外的所有模型层,如下所示: ```py IMG_SHAPE = (IMAGE_SIZE, IMAGE_SIZE, 3) base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet') ``` 通过导入 TensorFlow 团队提供的`MobileNetV2`模型来创建基本模型。 输入形状设置为(64、64、3),然后导入 ImageNet 数据集中的权重。 该模型可能在您的系统上不存在,在这种情况下,将从外部资源下载该模型。 13. 然后,我们冻结基本模型,以使`MobileNetV2`模型中的权重不受未来训练的影响,如下所示: ```py base_model.trainable = False ``` 14. 现在,我们将创建一个扩展的 CNN,并扩展基础模型以在基础模型层之后添加另一个层,如下所示: ```py model = tf.keras.Sequential([ base_model, tf.keras.layers.Conv2D(32, 3, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dense(5, activation='softmax') ]) ``` 我们创建了一个扩展基础模型的顺序模型,这实质上意味着数据是在单层连续地在连续层之间传递的,一次是一层。 我们还添加了具有`relu`激活功能的 2D 卷积层,然后是`Dropout`层,然后是`Pooling`层。 最后,添加带有`softmax`激活的输出层。 15. 然后,必须对模型进行编译以对其进行训练,如下所示: ```py model.compile(optimizer=tf.keras.optimizers.Adam(), loss='categorical_crossentropy', metrics=['accuracy']) ``` 我们将损失设置为分类交叉熵,将模型评估指标设置为预测的准确率。 已经发现`Softmax`在分类交叉熵作为损失函数时表现最佳,因此是首选。 16. 训练并保存模型。 最终,我们处于 ML 最激动人心的步骤之一-培训。 运行以下代码: ```py epochs = 10 history = model.fit(train_generator, epochs=epochs, validation_data=val_generator) ``` 该模型经过 10 个时期的训练,这意味着每个样本至少要在神经网络上抛出 10 次。 注意在此功能中使用了`train_generator`和`val_generator`。 即使有 12GB +的 RAM 和 TPU 加速可用,培训也需要花费相当长的时间(这在任何个人中端设备上都是过大的)。 您将能够观察到运行上述代码的单元下方的培训日志。 17. 然后,我们可以保存模型,之后可以继续转换保存的模型文件,如下所示: ```py saved_model_dir = '' tf.saved_model.save(model, saved_model_dir) ``` 18. 将模型文件转换并下载到 TensorFlow Lite。 现在,我们可以使用以下代码转换保存的模型文件。 这会将模型另存为`model.tflite`文件,如下所示: ```py converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir) tflite_model = converter.convert() with open('model.tflite', 'wb') as f: f.write(tflite_model) ``` 19. 现在,我们需要下载此文件,以将其嵌入到我们构建的 Flutter 应用程序中。 我们可以使用以下代码进行操作: ```py from google.colab import files files.download('model.tflite') files.download('labels.txt') ``` 注意,我们使用了`google.colab`库中的`files`模块。 我们还下载了在“步骤 11”中创建的`labels.txt`文件。 现在,我们准备开始创建 Flutter 应用程序,以演示 Cloud Vision API 的用法以及嵌入式 TensorFlow Lite 模型的用法。 # 创建 Flutter 应用程序 成功创建可识别多种植物物种的 TensorFlow Lite 模型后,现在让我们创建一个 Flutter 应用程序,以在移动设备上运行 TensorFlow Lite 模型。 该应用程序将有两个屏幕。 第一个屏幕将包含两个按钮,供用户在两个不同的模型(Cloud Vision API 和 TensorFlow Lite 模型)之间进行选择,这些模型可用于对任何选定的图像进行预测。 第二个屏幕将包含一个**浮动操作按钮**(**FAB**),使用户可以从设备的库中选择图像,一个图像视图以显示用户选择的图像,以及一个 文本以使用所选模型显示预测。 以下屏幕截图说明了应用程序的流程: ![](img/ea4bf7ee-b665-4603-86a8-9f1f46d5e33b.png) 现在,让我们看一下构建应用程序的步骤。 # 在两个不同的模型之间进行选择 让我们从创建应用程序的第一个屏幕开始。 第一个屏幕将包含两个不同的按钮,使用户可以在 Cloud Vision API 和 TensorFlow Lite 模型之间进行选择。 首先,我们创建一个新的`choose_a_model.dart`文件,其中将包含`ChooseModel`有状态的小部件。 该文件将包含用于创建应用程序第一个屏幕的代码,其中包含带有一些文本和两个凸起按钮的列,如以下屏幕截图所示: ![](img/9411ac71-b97c-45a8-aacc-deb49df6f16b.png) 创建应用程序的第一个屏幕的步骤如下: 1. 首先,我们将定义一些全局字符串变量,这些变量稍后将在创建用于选择模型的按钮以及保存用户选择的模型时使用,如下所示: ```py var str_cloud = 'Cloud Vision API'; var str_tensor = 'TensorFlow Lite'; ``` 2. 现在,让我们定义一个方法来创建一个简单的`Text`小部件,如下所示: ```py Widget buildRowTitle(BuildContext context, String title) { return Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), child: Text( title, style: Theme.of(context).textTheme.headline, ), ), ); } ``` 该方法返回一个小部件,该小部件与中心对齐,并包含一些带有`title`值的文本作为参数传递,并带有标题为主题的“选择模型”字符串。 使用`EdgeInsets.symmetric()`属性和`EdgeInsets.symmetric()`属性,还为文本提供了水平和垂直填充。 3. 接下来,我们将定义用于创建按钮的`createButton()`方法,如下所示: ```py Widget createButton(String chosenModel) { return (RaisedButton( color: Colors.blue, textColor: Colors.white, splashColor: Colors.blueGrey, child: new Text(chosenModel), onPressed: () { var a = (chosenModel == str_cloud ? 0 : 1); Navigator.push( context, new MaterialPageRoute( builder: (context) => PlantSpeciesRecognition(a) ), ); } ) ); } ``` 该方法返回`RaisedButton`方法,其颜色为`blue`,`textColor`值为`white`,`splashColor`值为`blueGrey`。 该按钮具有一个`Text`子元素,该子元素是使用`chosenModel`中传递的值构建的。 如果用户单击了运行 Cloud Vision API 的按钮,则`chosenModel`的值将为 Cloud Vision API,并且如果单击 TensorFlow Lite 的按钮,则其值为 TensorFlow Lite。 当按下按钮时,我们首先检查`chosenModel`中的值。 如果与`str_cloud`相同(即 Cloud Vision API),则分配给变量`a`的值为`0`; 否则,分配给变量`a`的值为`1`。 该值与使用`Navigator.push()`迁移到`PlantSpeciesRecognition`一起传递,这将在后面的部分中进行介绍。 4. 最后,我们创建第一个屏幕的`appBar`和主体,并从`build()`方法返回`Scaffold`,如下所示: ```py @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text('Plant Species Recognition'), ), body: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ buildRowTitle(context, 'Choose Model'), createButton(str_cloud), createButton(str_tensor), ], ) ) ); } ``` `appBar`包含位于中间的`Plant Species Recognition`标题。 `Scaffold`的主体是一列,其中包含一些文本和两个按钮,其值分别为`str_cloud`和`str_tensor`,并与中心对齐。 # 创建第二个屏幕 当用户选择了模型时,应用程序将迁移到第二个屏幕,该屏幕将允许用户从设备的本地存储中选择一个图像,然后在该图像上运行所选模型以进行预测。 我们从创建一个新文件`plant_species_recognition.dart`开始,该文件包含`PlantSpeciesRecognition`有状态的小部件。 # 创建用户界面 我们将首先创建一个新文件`PlantSpeciesRecognition.dart`,其中包含一个名为`PlantSpeciesRecognition`的有状态小部件,然后将覆盖其`build()`方法以放置**用户界面**(**UI**)的应用程序组件: 1. 让我们创建一个带有 FAB 的`Scaffold`和一个带有`build()`方法返回的应用程序标题的`AppBar`。 FAB 允许用户从设备的图库中选择图像,以预测图像中包含的植物种类,如下所示: ```py return Scaffold( appBar: AppBar( title: const Text('Plant Species Recognition'), ), floatingActionButton: FloatingActionButton( onPressed: chooseImageGallery, tooltip: 'Pick Image', child: Icon(Icons.image), ), ); ``` 在前面的代码片段中,`AppBar`将包含`Plant Species Recognition`文本。 这将作为应用程序的标题显示在屏幕顶部的应用程序栏上。 在 Flutter 中,`const`关键字有助于冻结对象的状态。 描述为`const`的对象的完整状态是在应用程序本身的编译期间确定的,并且保持不变。 同样,当与`Text()`之类的构造函数一起使用时,该关键字对于小型内存优化也很有用。 在代码中添加第二个`Text()`构造函数会重用为第一个`Text()`构造函数分配的内存,从而重用内存空间并使应用程序更快。 接下来,我们通过指定`FloatingActionButton`类并传递所需的参数来添加`floatingActionButton`属性。 `FloatingActionButtons`是圆形按钮,它们悬停在屏幕内容的顶部。 通常,一个屏幕应该包含一个位于右下角的 FAB,并且不受内容滚动的影响。 `onPressed`被添加到`chooseImageGallery`,按下该按钮将被调用。 接下来,我们添加`tooltip`属性,其`String`值为`'Pick Image'`,描述按钮将执行的操作。 最后,我们将`Icon(Icons.image)`添加为`child`,将材质图标图像放置在 FAB 的顶部。 # 添加功能 现在,让我们添加功能,以允许用户从设备的图库中选择图像。 我们将使用`image_picker`插件来执行此操作,并且整个代码将放置在`chooseImageGallery`方法内,如下所示: 1. 首先,将依赖项添加到`pubspec.yaml`文件,指定名称和版本号,如下所示: ```py dev_dependencies: flutter_test: sdk: flutter image_picker: ^0.6.0 ``` 有关发布依赖关系的详细讨论,请参阅“第 2 章”,“移动视觉–使用设备上模型的面部检测”。 确保运行`Flutter`软件包以在项目中包含依赖项。 要了解有关`image_picker`插件的更多信息,请访问[这里](https://github.com/flutter/plugins/tree/master/packages/image_picker)。 2. 将库导入到`PlantSpeciesRecognition.dart`中,如下所示: ```py import 'package:image_picker/image_picker.dart'; ``` 3. 此时,我们在`plant_species_recognition.dart`内声明以下两个全局变量: * `File_image`:存储从图库中选择的图像文件 * `bool _busy`(初始值为`false`):一个用于平滑处理 UI 操作的标志变量 4. 现在,让我们定义按下`FloatingActionButton`按钮时将调用的`chooseImageGallery()`方法,如下所示: ```py Future chooseImageGallery() async { var image = await ImagePicker.pickImage(source: ImageSource.gallery); if (image == null) return; setState(() { _busy = true; }); } ``` 在这里,我们使用`ImagePicker.pickImage()`方法通过将其作为来源来从图库中获取图像。 我们将返回的值存储在变量图像中。 如果从调用返回的值为`null`,则由于无法对`null`值执行进一步的操作,因此我们返回了该调用。 否则,请将`_busy`的值更改为`true`,以指示正在对该图像进行进一步的操作。 `setState()`是一个同步回调,用于通知框架对象的内部状态已更改。 此更改可能实际上会影响应用程序的 UI,因此,框架将需要安排`State`对象的构建。 [请参阅以下链接以进行进一步讨论](https://api.flutter.dev/flutter/widgets/State/setState.html)。 此时,该应用程序已成功编译,然后按 FAB 启动画廊,可以从中选择图像。 但是,所选的图像不会显示在屏幕上,因此,现在让我们开始吧。 # 在屏幕上显示所选图像 现在,让我们添加一个小部件以显示在上一节中选择的图像,如下所示: 1. 我们将使用小部件列表,从图库中选择的图像以及彼此堆叠或重叠的预测结果显示在屏幕上。 因此,我们首先声明一个空的小部件列表,其中将包含堆栈的所有子级。 另外,我们声明一个`size`实例,以使用`MediaQuery`类查询包含应用程序的窗口的大小,如下所示: ```py List stackChildren = []; Size size = MediaQuery.of(context).size; ``` 2. 现在,将图像添加为堆栈的第一个孩子,如下所示: ```py stackChildren.add(Positioned( top: 0.0, left: 0.0, width: size.width, child: _image == null ?Text('No Image Selected') : Image.file(_image), )); ``` `Positioned`类用于控制堆栈的子代的位置。 在这里,通过指定`top`,`left`和`width`属性的值。 `top`和`left`值分别指定子项的顶部和左侧边缘与堆栈顶部和左侧边缘的距离,此处为 0,即设备屏幕的左上角 。 `width`值指定子项的宽度-此处是包含应用程序的窗口的宽度,这意味着图像将占据整个宽度。 3. 接下来,我们将添加子项,该子项将是一个文本,如果`_image`的值为`null`,则表示未选择任何图像; 否则,它包含用户选择的图像。 为了在屏幕上显示堆栈,我们将`stackChildren`列表添加为`build()`方法返回的`Scaffold`的主体,如下所示: ```py return Scaffold( appBar: AppBar( title: const Text('Plant Species Recognition'), ), //Add stackChildren in body body: Stack( children: stackChildren, ), floatingActionButton: FloatingActionButton( onPressed: chooseImageGallery, tooltip: 'Pick Image', child: Icon(Icons.image), ), ); ``` 在前面的代码中,我们在`Stack()`内部传递`stackChildren`,以创建包含在列表内的所有小部件的覆盖结构。 4. 此时编译代码将产生以下结果: ![](img/eeaa7923-cfe1-48c9-980b-32bea0bf994d.png) 此时,单击 FAB 将启动图库,并且所选图像将显示在屏幕上。 接下来,我们将在设备上加载 TensorFlow Lite 模型,并向 Cloud Vision API 发出 HTTP 请求,以在所选图像上获得识别结果。 # 运行图像识别 现在,从图库中选择的图像可用作 Cloud Vision API 和 TensorFlow Lite 模型的两种预测方法的输入。 接下来,让我们定义两种方法。 # 使用 Cloud Vision API 在本节中,我们简单地定义一个`visionAPICall`方法,该方法用于向 CloudVision API 发出`http Post`请求,传入编码为`json`的请求字符串,该字符串返回一个`json`响应,该响应被解析以获取 所需标签中的值: 1. 首先,我们在`pubspec.yaml`文件中定义一个`http`插件依赖项,如下所示: ```py http: ^0.12.0+2 ``` 2. 将插件导入`PlantSpeciesRecognition.dart`,以帮助发出`http`请求,如下所示: ```py import 'package:http/http.dart' as http; ``` 3. 现在,我们定义创建请求 URL 并发出`http` `POST`请求的方法,如下所示: ```py List imageBytes = _image.readAsBytesSync(); String base64Image = base64Encode(imageBytes); ``` 为了能够将图像文件与 HTTP 发布请求一起发送进行分析,我们需要将`png`文件转换为 Base64 格式,即,转换为仅包含**美国信息交换标准码的字符串**(**ASCII**)值。 首先,我们使用`readAsByteSync()`读取`_image`的内容作为字节列表并将其存储在`imageBytes`中。 然后,通过将`imageBytes`列表作为`base64Encode`的参数传递给我们,以 Base64 格式对该列表进行编码。 4. 接下来,我们创建请求字符串,其格式如下: ```py var request_str = { "requests":[ { "image":{ "content": "$base64Image" }, "features":[ { "type":"LABEL_DETECTION", "maxResults":1 } ] } ] }; ``` 虽然整个字符串将被硬编码,但是内容密钥的值将根据用户选择的图像及其 base64 编码格式而有所不同。 5. 我们将需要调用的 URL 存储在`url`变量中,如下所示: ```py var url = 'https://vision.googleapis.com/v1/images:annotate?key=API_KEY; ``` 确保用您生成的密钥替换`API_KEY`。 6. 使用`http.post()`方法发出 HTTP 发布请求,并传入`url`和响应字符串,如下所示: ```py var response = await http.post(url, body: json.encode(request_str)); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); ``` 我们还使用`response.statusCode`检查状态码,如果请求成功,则状态码应为`200`。 7. 由于来自服务器的响应是 JSON 格式,因此我们使用`json.decode()`对其进行解码,然后进一步解析它,以将所需的值存储在`str`变量中,如下所示: ```py var responseJson = json.decode(response.body); str = '${responseJson["responses"][0]["labelAnnotations"][0]["description"]}: ${responseJson["responses"][0]["labelAnnotations"][0]["score"].toStringAsFixed(3)}'; ``` 8. 将所有内容放在一起后,整个`visionAPICall()`方法将如下所示: ```py Future visionAPICall() async { List imageBytes = _image.readAsBytesSync(); print(imageBytes); String base64Image = base64Encode(imageBytes); var request_str = { "requests":[ { "image":{ "content": "$base64Image" }, "features":[ { "type":"LABEL_DETECTION", "maxResults":1 } ] } ] }; var url = 'https://vision.googleapis.com/v1/images:annotate?key=AIzaSyDJFPQO3N3h78CLOFTBdkPIN3aE9_ZYHy0'; var response = await http.post(url, body: json.encode(request_str)); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); var responseJson = json.decode(response.body); str = '${responseJson["responses"][0]["labelAnnotations"][0]["description"]}: ${responseJson["responses"][0]["labelAnnotations"][0]["score"].toStringAsFixed(3)}'; } ``` 在下一节中,我们将介绍使用设备上 TensorFlow Lite 模型的步骤。 # 使用设备上的 TensorFlow Lite 模型 现在,让我们为用户的第二选择添加功能,即使用 TensorFlow Lite 模型分析所选图像。 在这里,我们将使用我们先前创建的 TensorFlow Lite 模型。 以下步骤详细讨论了如何使用设备上的 TensorFlow Lite 模型: 1. 我们将从在`pubspec.yaml`文件中添加`tflite`依赖关系开始,如下所示: ```py dev_dependencies: flutter_test: sdk: flutter image_picker: ^0.6.0 //Adding tflite dependency tflite: ^0.0.5 ``` 2. 接下来,我们在 Android 中配置`aaptOptions`。 将以下代码行添加到`android`块内的`android/app/build.gradle`文件中: ```py aaptOptions { noCompress 'tflite' noCompress 'lite' } ``` 前面的代码段确保`tflite`文件未以压缩形式存储在 **Android 软件包工具包**(**APK**)中。 3. 接下来,我们需要将已经保存的`model.tflite`和`labels.txt`文件包括在 assests 文件夹中,如以下屏幕截图所示: ![](img/bda97e78-3717-4a7b-9ab6-980f3be77066.png) 4. 在`pubspec.yaml`文件中指定文件的路径,如下所示: ```py flutter: uses-material-design: true //Specify the paths to the respective files assets: - assets/model.tflite - assets/labels.txt ``` 5. 现在,我们都准备从在设备上加载并运行我们的第一个 TensorFlow Lite 模型开始。 首先,将`tflite.dart`文件导入到`PlantSpeciesRecognition.dart`中,如下所示: ```py import 'package:tflite/tflite.dart'; ``` 6. 为了执行所有相关任务,我们定义了`analyzeTFLite()`方法。 在这里,我们从加载模型开始,将`model.tflite`文件和`labels.txt`文件作为输入传递给`Tflite.loadModel()`中的`model`和`labels`参数。 如果成功加载模型,我们将结果输出存储在`res`字符串变量中,该变量将包含`success`值,如下所示: ```py String res = await Tflite.loadModel( model: "assets/model.tflite", labels: "assets/labels.txt", numThreads: 1 // defaults to 1 ); print('Model Loaded: $res'); ``` 7. 现在,我们使用`Tflite.runModelOnImage()`方法在图像上运行模型,并传递存储在设备内部的所选图像的路径。 我们将结果存储在`recognitions`变量中,如下所示: ```py var recognitions = await Tflite.runModelOnImage( path: _image.path ); setState(() { _recognitions = recognitions; }); ``` 8. 一旦模型在图像上成功运行并将结果存储在`recognitions`局部变量中,我们将创建`_recognitions`全局列表并将其状态设置为`recognitions`中存储的值,以便可以更新 UI 结果正确。 将所有内容放在一起后,整个`analyzeTfLite()`方法将如下所示: ```py Future analyzeTFLite() async { String res = await Tflite.loadModel( model: "assets/model.tflite", labels: "assets/labels.txt", numThreads: 1 // defaults to 1 ); print('Model Loaded: $res'); var recognitions = await Tflite.runModelOnImage( path: _image.path ); setState(() { _recognitions = recognitions; }); print('Recognition Result: $_recognitions'); } ``` 在成功选择并存储图像后,取决于用户单击的按钮,这是由`visionAPICall()`和`analyzeTFLite()`这两个定义的方法从`chooseImageGallery()`调用的,这取决于传递给窗口的值。 `PlantSpeciesRecognition`构造函数:Cloud Vision API 为 0,TensorFlow Lite 为 1。 修改后的`chooseImagGallery()`方法如下所示: ```py Future chooseImageGallery() async { var image = await ImagePicker.pickImage(source: ImageSource.gallery); if (image == null) return; setState(() { _busy = true; _image = image; }); //Deciding on which method should be chosen for image analysis if(widget.modelType == 0) await visionAPICall(); else if(widget.modelType == 1) await analyzeTFLite(); setState(() { _image = image; _busy = false; }); } ``` 在方法调用之前提到`await`关键字,以确保所有操作都是异步进行的。 在这里,我们还将`_image`的值设置为`image`,将`_busy`的值设置为`false`,以指示所有处理已完成,并且现在可以更新 UI。 # 用结果更新用户界面 在上一节*创建用户界面*中,我们通过向`stackChildren`添加一个额外的子代来更新 UI,以显示用户选择的图像。 现在,我们将另一个孩子添加到堆栈中以显示图像分析的结果,如下所示: 1. 首先,我们将添加 Cloud Vision API 的结果,如下所示: ```py stackChildren.add( Center ( child: Column( children: [ str != null? new Text(str, style: TextStyle( color: Colors.black, fontSize: 20.0, background: Paint() ..color = Colors.white, ) ): new Text('No Results') ], ) ) ); ``` 回想一下,请求的 JSON 响应已被解析,格式化并存储在`str`变量中。 在这里,我们使用`str`的值创建具有指定颜色和背景的`Text`。 然后,我们将此`Text`作为子级添加到列中,并对齐`Text`以显示在屏幕中央。 最后,我们将整个格式包装在`stackChildren.add()`周围,以将其添加到 UI 元素堆栈中。 2. 接下来,我们将添加 TensorFlow Lite 的结果,如下所示: ```py stackChildren.add(Center( child: Column( children: _recognitions != null ? _recognitions.map((res) { return Text( "${res["label"]}: ${res["confidence"].toStringAsFixed(3)}", style: TextStyle( color: Colors.black, fontSize: 20.0, background: Paint() ..color = Colors.white, ), ); }).toList() : [], ), )); ``` `_recognitions`列表中存储的 TensorFlow Lite 模型的结果逐元素进行迭代,并映射到使用`.map()`指定的列表。 列表中的每个元素都将进一步转换为`Text`,并作为与屏幕中心对齐的列子元素添加。 此外,请注意,需要将 Cloud Vision API 的输出或 TensorFlow Lite 模型的输出添加到堆栈中。 为了确保这一点,我们将前面的代码包装在`if-else`块中,这样,如果在构造函数中传递的值(即`modelChosen`)为 0,则添加前者的输出;如果该值是,则添加后者的输出。 是 1。 3. 最后,在各种图像集上运行 Cloud Vision API 将提供不同的输出。 以下屏幕快照显示了一些示例: ![](img/1727eda3-5ec9-4621-8c1b-2d6d8c63b1f6.png) 当 TensorFlow Lite 模型使用相同的图像集时,识别方式会有所不同。 以下屏幕快照显示了一些示例: ![](img/23f29672-39ba-4383-b4a1-b5fa7c90fd5b.png) 在上一个屏幕截图中,我们可以看到已正确识别将图像加载到图库中的花朵的种类。 # 概要 在本章中,我们介绍了如何使用流行的基于深度学习的 API 服务来使用图像处理。 我们还讨论了如何通过扩展先前创建的基础模型,将其与定制训练模型一起应用。 虽然我们没有明确提及,但是基础模型的扩展是称为**迁移学习**(**TL**)的过程的一部分,其中将在特定数据集上训练的模型导入到 用在完全不同的场景中,几乎没有或只有很少的微调。 此外,本章还介绍了 TensorFlow Lite 为什么以及何时适合用于构建模型,以及如何将 Flutter 应用于在离线运行且非常快速的设备模型上进行应用。 本章设定了里程碑,在项目中引入了 Python 和 TensorFlow,在接下来的章节中将广泛使用这两种方法。 在下一章中,我们将介绍计算机科学的一个非常激动人心的领域,即增强现实,并介绍深度学习在现实世界中的应用。