1. 准备工作
在此 Codelab 中,您将学习如何构建处理计算机视觉核心用例的应用,以检测图片的主要内容。这通常称为图片分类或图片标签。
前提条件
此 Codelab 是图片分类入门在线课程的一部分。这篇文章面向经验丰富的机器学习开发者。
构建内容
- 能够对花卉图片进行分类的 Android 应用
- (可选)能对花卉图片进行分类的 iOS 应用
所需物品
- Android Studio,可通过 https://developer.android.com/studio 获取 Codelab 的 Android 部分
- Xcode,可在 Apple App Store 中找到,适用于 Codelab 的 iOS 部分
2. 开始
计算机视觉是机器学习这一更广泛的学科领域,旨在为机器寻找处理图片内容并从中提取信息的新方法。在计算机仅存储图像的实际数据(例如构成图像的像素的值)之前,计算机视觉可让计算机解析图像的内容并获取其中的信息。
例如,在计算机视觉领域,除了构成猫的像素之外,系统还可以将其标记为包含猫。计算机视觉技术还有一些其他的领域会比这更具体,例如对象检测功能,在这种技术中,计算机可以在图片中找到多个项并推导边界框。
在此 Codelab 中,您将了解如何构建处理核心用例的应用,以检测图像的主要内容。这通常称为图片分类或图片标签。
为尽可能简化应用,它会使用与它捆绑在一起的图片作为资源,并向您显示这些图片的分类。日后还可以使用扩展功能,例如使用图片选择器或直接从相机中提取图片。
首先,您将使用 Android Studio 完成在 Android 上构建应用的流程。(请跳至第 7 步,在 iOS 设备上执行相同的操作。)
- 打开 Android Studio,转到“File”菜单,然后选择“Create a New Project”。
- 系统会要求您选择项目模板。选择“Empty Activity”。
- 点击 Next。系统会要求您配置您的项目。为其提供您想要的任何名称和软件包名称,但此 Codelab 中的示例代码使用项目名称 ImageClassifierStep1 和软件包名称 com.google.imageclassifierstep1。
- 选择您的首选语言:Kotlin 或 Java。本实验使用的是 Kotlin,因此,如果您希望完全按照 Kotlin 语言编写代码,您可能需要选择 Kotlin。
- 准备就绪后,点击“完成”。Android Studio 将为您创建应用。完成设置可能需要一些时间。
3.导入机器学习套件的图片标签库
机器学习套件 (https://developers.google.com/ml-kit) 为开发者提供了许多解决方案,满足机器学习领域的常见场景,并使他们能够在跨平台中轻松实现和工作。机器学习套件提供了一个名为“图片标签”的万能库。此库包含一个预训练模型,可以识别超过 600 类图片。因此,最好立即开始使用。
请注意,借助机器学习套件,您也可以使用相同的 API 来使用自定义模型,因此,准备就绪后,您便可以不局限于“开始使用”,并着手构建使用针对您的场景训练的模型的个性化图片标记应用。
在此场景中,您将构建一个花卉识别器。当您创建首个应用并显示鲜花图片时,系统会将其识别为鲜花。(稍后,在构建自己的花卉检测器模型时,借助机器学习套件,您只需做出极少更改即可将其拖放到应用中,并让新模型告诉您其类型是郁金香还是“玫瑰”。)
- 在 Android Studio 中,使用项目浏览器,确保在顶部选择了 Android。
- 打开 Gradle Scripts 文件夹,并为应用选择
build.gradle
文件。应用可能会有 2 个或更多,因此请务必使用应用 1 级,如下所示:
- 在该文件底部,您会看到一个名为 dependencies 部分,其中存储了
implementation
、testImplementation
和androidImplementation
设置的列表。使用以下代码向该文件添加一个新进程:
implementation 'com.google.mlkit:image-labeling:17.0.3'
(请确保这是在依赖项 { } 内)
- 窗口顶部会显示一个条,指明
build.gradle
已更改,您需要重新同步。开始吧。如果您没有看到此图标,请在右上角的工具栏中查找小象图标,然后点击该图标。
现在,您已导入机器学习套件,可以开始为图片加标签了。
接下来,您将创建一个简单的界面来渲染图像,并提供一个按钮,当用户按下它时,机器学习套件会调用图像标记器模型来解析图像的内容。
4.构建界面
在 Android Studio 中,您可以使用基于 xml 的布局文件来修改每个屏幕(或 activity)的界面。您创建的基本应用只有一个 activity(其代码位于 MainActivity
中,您很快就会看到该代码),界面声明位于 activity_main.xml
中。
您可以在 Android 项目资源管理器的 res > layout 文件夹中找到此文件,如下所示:
这将打开一个完整的编辑器,让您可以设计 activity 界面。其中提供了许多功能,而本实验并非要教您如何使用它。如需详细了解布局编辑器,请访问:https://developer.android.com/studio/write/layout-editor
在本实验中,请选择编辑器右上角的代码工具。
现在,您会在窗口的主要部分看到 XML 代码。将代码更改为以下代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imageToLabel"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btnTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Label Image"
android:layout_gravity="center"/>
<TextView
android:id="@+id/txtOutput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:gravity="start|top" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
您将获得一个超简单的布局,其中包含一个 ImageView
(用于渲染图像)、一个 Button
(供用户按下)和一个 TextView
(用于显示标签)。
您现在已经定义了界面。在编码之前,添加一些图片作为素材资源,应用将对这些图片进行推断。
5. 将图片与应用捆绑在一起
将额外文件与 Android 应用捆绑的一种方式是将它们添加为编译到应用中的资源。为简化此应用,我们将添加一些花卉图片。稍后,您可以将此应用扩展为使用 CameraX 或其他库来拍摄照片并使用。但为简单起见,我们暂时只捆绑图片。
- 在 Project Explorer 中,右键点击顶部的 app,然后选择“New Directory”。
- 在随即显示的对话框(列出了不同的目录)中,选择 src/main/assets。
完成此操作后,您会在项目浏览器中看到一个新的 assets 文件夹:
- 右键点击此文件夹,您会看到一个弹出式窗口,其中包含选项列表。其中一种方法是在文件系统中打开该文件夹。 找到适合您的操作系统的操作系统,然后选择该操作系统。(在 Mac 上,这将是在查找器中显示,在 Windows 中则为在资源管理器中打开,在 Ubuntu 上则是在“文件”中显示。)
- 将文件复制到该文件中。您可以从 Pixabay 等网站下载图片。建议将映像重命名为简单名称。在这种情况下,映像已重命名为
flower1.jpg
。
完成此操作后,请返回到 Android Studio,您应该会在资源文件夹中看到您的文件。
您现在可以为此图片加标签了!
6.编写分类代码来标记图片
(现在,大家都期待了在 Android 上实现计算机视觉!)
- 您要在
MainActivity
文件中编写代码,因此请在项目文件夹中的 com.google.devrel.imageclassifierstep1 下查找(如果选择了不同的命名空间,则等同于您的任何等效命名空间)。请注意,Android Studio 项目中通常设置有 3 个命名空间文件夹,一个用于应用,一个用于 Android 测试,一个用于测试。您会看到MainActivity
且在括号后没有说明的内容。
如果您选择使用 Kotlin,您可能想知道为什么父文件夹名为 Java。这是一个历史文物,从 Android Studio 到 Java 才开始出现。将来的版本可能会解决这个问题,但如果您想使用 Kotlin,也不必担心。这只是源代码的文件夹名称。
- 打开
MainActivity
文件,您会在代码编辑器中看到一个名为 MainActivity 的类文件。该属性应如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
在右花括号下方,您可以添加不属于该类但可供相应类使用的扩展代码。您需要添加扩展程序才能以位图形式从素材资源中读取文件。这会用于加载您之前复制到资源文件夹中的图片。
- 添加以下代码:
// extension function to get bitmap from assets
fun Context.assetsToBitmap(fileName: String): Bitmap?{
return try {
with(assets.open(fileName)){
BitmapFactory.decodeStream(this)
}
} catch (e: IOException) { null }
}
此时,Android Studio 可能会抱怨,并用红色突出显示一些代码,例如 Context
、Bitmap
和 IOException
:
不用担心!这是因为您尚未导入包含它们的库。Android Studio 提供了方便快捷的快捷方式。
- 将光标悬停在该字词上,然后按 Alt + Enter(在 Mac 上,则按 Option + Enter),系统会为您生成导入内容。
- 接下来,您可以从素材资源中加载位图,并将其放入 ImageView 中。返回 MainActivity 的
onCreateFunction
,在setContentView
行下方添加以下代码:
val img: ImageView = findViewById(R.id.imageToLabel)
// assets folder image file name with extension
val fileName = "flower1.jpg"
// get bitmap from assets folder
val bitmap: Bitmap? = assetsToBitmap(fileName)
bitmap?.apply {
img.setImageBitmap(this)
}
- 和之前一样,有些代码会用红色突出显示。将光标放在该行上,然后使用 Alt + Enter / Option + Enter 自动添加导入作业。
- 在您之前创建的
layout.xml
文件中,您为 ImageView 命名了 imageToLabel,因此第一行将使用该布局信息创建名为 ImageView 的对象实例。它使用内置 Android 函数findViewById
查找详细信息。然后,它会使用文件名flower1.jpg
,通过您在上一步中创建的assetsToBitmap
函数,从素材资源文件夹加载图片。最后,它使用位图抽象类将位图加载到 img 中。 - 布局文件有一个 TextView,用于渲染为图片推断的标签。接下来获取一个代码对象。在上一个代码下方添加以下代码:
val txtOutput : TextView = findViewById(R.id.txtOutput)
与之前一样,这会查找使用文本名称的布局文件信息(检查其名称为 txtOutput
的 XML),并使用它来实例化名为 txtOutput 的 TextView 对象。
同样,您需要创建一个按钮对象来表示该按钮,并用布局文件内容进行实例化。
在布局文件中,我们调用了按钮 btnTest
,因此我们可以将其实例化,如下所示:
val btn: Button = findViewById(R.id.btnTest)
现在,所有代码和控件均已初始化,下一步(也是最后一步)是使用它们获取图像的推断。
在继续操作之前,请确保您的 onCreate
代码如下所示:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val img: ImageView = findViewById(R.id.imageToLabel)
// assets folder image file name with extension
val fileName = "flower1.jpg"
// get bitmap from assets folder
val bitmap: Bitmap? = assetsToBitmap(fileName)
bitmap?.apply {
img.setImageBitmap(this)
}
val txtOutput : TextView = findViewById(R.id.txtOutput)
val btn: Button = findViewById(R.id.btnTest)
}
所有关键字都不应显示为红色,表示相应关键字尚未导入。如果是,请返回并执行 Alt + Enter 键以生成导入作业。
使用机器学习套件的图片标记器时,第一步通常是创建 Options 对象以自定义行为。您要将图片转换为机器学习套件可以识别的 InputImage 格式。然后,您可以创建 Labeler 对象来执行推理。它会为您提供包含结果的异步调用,然后您可以解析结果。
在您刚刚创建的按钮的 onClickListener
事件中,完成所有这些操作。完整代码如下:
btn.setOnClickListener {
val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)
val image = InputImage.fromBitmap(bitmap!!, 0)
var outputText = ""
labeler.process(image)
.addOnSuccessListener { labels ->
// Task completed successfully
for (label in labels) {
val text = label.text
val confidence = label.confidence
outputText += "$text : $confidence\n"
}
txtOutput.text = outputText
}
.addOnFailureListener { e ->
// Task failed with an exception
}
}
- 用户第一次点击该按钮时,代码会使用 ImageLabeling.getClient 实例化标记器,并向其传递 ImageLabelerOptions。它附带 DEFAULT_OPTIONS 属性,可用于快速启动并运行。
- 接下来,将使用位图的 fromBitmap 方法根据输入图片创建输入图片。InputImage 是机器学习套件处理图片所需的格式。
- 最后,标签添加者会处理图片,并在成功或失败时提供异步回调。如果推断成功,回调将包含一个标签列表。然后,您可以解析此标签列表,读取标签的文本和置信度值。如果执行失败,系统会返回异常,您可以用该异常来向用户报告。
这样就大功告成了!现在,您可以在 Android 设备或模拟器中运行该应用。如果您之前从未尝试过,可以参考此处的说明:https://developer.android.com/studio/run/emulator
以下是在模拟器中运行的应用。起初,您会看到图片和按钮,而标签将为空。
按一下按钮,您会获得该图片的一组标签。
在这里,您可以看到标签添加者确定图片包含花瓣、花朵、植物和天空的概率较高。这些都正确,它们都展示模型正在解析图像。
但目前还不能确定这是一张菊花的图片。为此,您需要一个针对特定花卉进行训练的自定义模型,您将在下一个实验中看到具体操作方式。
在以下步骤中,您将了解如何在 iOS 上构建同一应用。
7. 在 iOS 上创建图像分类器 - 开始使用
您可以使用 Xcode 在 iOS 上创建类似的应用。
- 启动 Xcode,然后从文件菜单中选择 New Project。您会看到以下对话框:
- 如下所示,选择 App(应用),然后点击 Next(下一步)。系统会要求您选择项目选项。输入名称和组织标识符,如下所示。确保接口类型为故事板,语言为 Swift(如下所示)。
- 如果您要部署到手机上,并设置了开发者资料,则可以设置您的团队设置。否则,请将其保持为 None,您可以使用 iOS 模拟器运行您的应用。
- 点击 Next,然后选择用于存储项目及其文件的文件夹。记住此项目的位置,您需要在下一步中使用该项目。
- 请暂时关闭 Xcode,因为在下一步中您将使用其他工作区文件重新打开 Xcode。
8. 使用 CocoaPods 集成机器学习套件
由于机器学习套件也适用于 iOS,因此您可以用非常类似的方式构建图像分类器。如需进行集成,您将使用 CocoaPods。如果您尚未安装此扩展程序,可以按照 https://cocoapods.org/ 上的说明进行安装
- 打开创建项目的目录。其中应包含您的 .xcodeproj 文件。
这里会显示 .xcodeproj 文件,指明我转到的位置正确。
- 在此文件夹中,创建一个名为 Podfile 的新文件。没有扩展程序,它只是 Podfile。在其中添加以下内容:
platform :ios, '10.0' target 'ImageClassifierStep1' do pod 'GoogleMLKit/ImageLabeling' end
- 保存,然后返回终端。在同一目录中,输入
pod install
。Cocoapods 将下载适当的库和依赖项,并创建一个将您的项目与其外部依赖项相结合的新工作区。
请注意,最后,您需要关闭 Xcode 会话,并在稍后使用工作区文件。打开此文件,Xcode 将与您的原始项目以及外部依赖项一起启动。
您现在可以继续执行下一步,并创建界面。
9. 使用 Storyboard 创建 iOS 界面
- 打开
Main.storyboard
文件,您将看到一个界面布局,其中包含手机的设计界面。 - 屏幕右上角会显示一个 + 按钮,供您添加控件。点击即可获取控件面板。
- 将 ImageView、Button 和 Label 拖放到设计图面上。将图片上下排列,如下所示:
- 双击此按钮可将其文本从按钮修改为分类。
- 拖动标签周围的控件手柄可放大标签。(假设宽度与 UIImageView 大致相同,高度为两倍)。
- 仍选择标签,请点击右上角的选择器按钮,以显示检查器调色板。
- 完成此操作后,找到线条设置,并确保将其设置为 0。这允许标签呈现动态数量的行。
现在,您可以执行下一步了 - 使用插座和操作将界面连接到代码中。
10. 创建操作和输出口
使用 Storyboard 进行 iOS 开发时,您可以使用输出口引用控件的布局信息,并定义当用户使用操作针对控件执行操作时要运行的代码。
在下一步中,您需要为 ImageView 和标签创建输出口。系统会在代码中引用 ImageView,以将图片加载到其中。将在代码中引用标签,以根据从机器学习套件返回的推断设置文本。
- 点击屏幕右上方的控件,然后点击紧邻其下方的在右侧添加编辑器按钮,以关闭检查器调色板。
- 屏幕布局会令人困惑,其中 main.storyboard 会打开两次。在左侧的项目导航器中,选择 ViewController.swift,以便打开视图控制器代码。左侧设计图板看起来似乎消失了,但别担心,它仍在原处!
- 如需重新显示,请点击 View Controller 场景中的 View Controller。尽量使您的界面如下所示:左侧的故事板显示设计,右侧是 ViewController.swift 的代码。
- 从左侧的设计图面中选择 UIImageView,在按住 Ctrl 键的同时,将其拖动到右侧的代码中,并放在
class
关键字下方(如上述屏幕截图中的第 11 行)。
拖动时,您将看到一个箭头,当您放下时,会看到如下弹出式窗口:
- 在名称字段中填入“imageView”,然后点击连接。
- 使用标签重复此过程,并将其命名为“lblOutput”。
- 重要提示:对于按钮,您需要执行相同的操作,但请确保将连接类型设置为操作(而非输出)!
- 将其命名为“doClassification”,然后点击连接。
完成后,您的代码应如下所示:(请注意,标签和图片视图声明为 IBOutlet(接口构建器输出)和按钮,即 IBAction(接口构建器操作)。)
import UIKit
class ViewController: UIViewController {
@IBAction func doClassification(_ sender: Any) {
}
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var lblOutput: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
- 最后,将图片与应用捆绑在一起,以便我们轻松进行分类。为此,请将文件浏览器中的文件拖动到 Xcode 左侧的资源管理器中。放下时,您会看到如下弹出式窗口:
- 确保选中添加到目标部分中的复选框(如图所示),然后点击完成。
该文件会与您的应用捆绑在一起,您现在可以轻松地对它进行分类了。现在,您可以编码界面以进行图像分类了!
11. 编写图片分类代码
现在,一切都已设置完毕,编写用于执行图片分类的代码非常简单。
- 首先关闭设计板设计器,方法是点击设计图面左上角的 X。这样您就可以专注于代码。在本实验的其余部分,您将修改 ViewController.swift。
- 通过在顶部(导入 UIKit 的下方)添加以下代码,导入 MLKitVision 和 MLKit ImageLabeling 库:
import MLKitVision
import MLKitImageLabeling
- 然后,在
viewDidLoad
函数中,使用我们在应用中捆绑的文件初始化 ImageView:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
imageView.image = UIImage(named:"flower1.jpg")
}
- 创建辅助函数以获取图片的标签(在
viewDidLoad()
正下方):
func getLabels(with image: UIImage){
- 根据图片创建 VisionImage。机器学习套件在执行图片分类时会使用此类型。因此,在 getLabels 函数内添加以下代码:
let visionImage = VisionImage(image: image)
visionImage.orientation = image.imageOrientation
- 接下来,为图片标记器创建选项。它将使用这些选项进行初始化。在本例中,您只需设置
confidenceThreshold
的基本选项。这意味着,您只需要让标签添加者返回置信度为 0.4 或更高的标签。例如,对于我们的花卉,“植物”或“花瓣”等类别的置信度较高,但“篮球”或“汽车”等类别的置信度较低。
let options = ImageLabelerOptions()
options.confidenceThreshold = 0.4
- 现在,使用以下选项创建标记器:
let labeler = ImageLabeler.imageLabeler(options: options)
- 添加标签添加者后,您便可对其进行处理。它将为您提供带有标签(如果成功)和错误(如果失败)的异步回调,然后您可以在稍后创建的其他函数中对其进行处理。
labeler.process(visionImage) { labels, error in
self.processResult(from: labels, error: error)
}
如果 Xcode 指出没有 processResult
成员,请不要担心。您尚未实现该目标,下一步将实现。
为方便起见,以下是完整的 getLabels 函数:
// This is called when the user presses the button
func getLabels(with image: UIImage){
// Get the image from the UI Image element and set its orientation
let visionImage = VisionImage(image: image)
visionImage.orientation = image.imageOrientation
// Create Image Labeler options, and set the threshold to 0.4
// so we will ignore all classes with a probability of 0.4 or less
let options = ImageLabelerOptions()
options.confidenceThreshold = 0.4
// Initialize the labeler with these options
let labeler = ImageLabeler.imageLabeler(options: options)
// And then process the image, with the callback going to self.processresult
labeler.process(visionImage) { labels, error in
self.processResult(from: labels, error: error)
}
}
因此,现在您需要实现 processResult
函数。现在,我们获得了标签和返回的错误对象,这非常简单。标签应转换为机器学习套件中的 ImageLabel 类型。
完成后,您可以遍历一组标签,提取说明和置信度值,然后将其添加到名为 labeltexts
的 var
中。完成所有迭代后,只需将 lblOutput.text 设置为该值即可。
以下是完整函数:
// This gets called by the labeler's callback
func processResult(from labels: [ImageLabel]?, error: Error?){
// String to hold the labels
var labeltexts = ""
// Check that we have valid labels first
guard let labels = labels else{
return
}
// ...and if we do we can iterate through the set to get the description and confidence
for label in labels{
let labelText = label.text + " : " + label.confidence.description + "\n"
labeltexts += labelText
}
// And when we're done we can update the UI with the list of labels
lblOutput.text = labeltexts
}
剩下的工作就是在用户按该按钮时调用 getLabels
。
创建操作时,已为您连接所有服务,因此您只需更新之前创建的名为 doClassificaiton
的 IBAction
即可调用 getLabels
。
以下代码只用来调用 imageView 的内容:
@IBAction func doClassification(_ sender: Any) {
getLabels(with: imageView.image!)
}
现在,您可以运行应用,然后试试看。您可以在这里查看实际应用:
请注意,布局可能会因设备而异。
此 Codelab 不会探讨每种设备的不同布局类型,这本身是一个相当复杂的概念。如果您没有看到界面,请返回故事板编辑器,您会在底部看到 View as: 部分,可以从中选择特定设备。请根据您要测试的图片或设备选择一项,并修改界面以使其适应测试。
随着深入探讨 iOS 开发,您将了解如何使用约束来确保界面在各手机之间保持一致,但这超出了本次实验的范围。
12. 恭喜!
现在,您已经在 Android 和 iOS 上实现了应用,它可利用通用模型为您提供基本计算机视觉。你已经完成了这项繁重工作。
在下一个 Codelab 中,您将构建一个可以识别不同类型的花卉的自定义模型,并且只需几行代码即可在该应用中实现自定义模型,使其更加实用!