随着技术的进步,我们的设备可以使用其内置摄像头使用预先训练的数据集准确识别和标记图像。您也可以训练自己的模型,但在本教程中,我们将使用开源模型来创建图像分类应用程序。
我将向您展示如何创建一个可以识别图像的应用程序。我们将从一个空的 Xcode 项目开始,一步一步实现mac机器学习驱动的图像识别。
入门
Xcode 版本
在开始之前,请确保您的 Mac 上安装了最新版本的 Xcode。这非常重要,因为 Core ML 将仅在 Xcode 9 或更高版本上可用。您可以通过打开 Xcode 并转到 上方工具栏中的 Xcode > About Xcode来检查您的版本。
如果您的 Xcode 版本早于 Xcode 9,您可以前往 Mac App Store 进行更新,或者如果您没有,请免费下载。
示例项目
新项目
在确定您拥有正确版本的 Xcode 之后,您需要创建一个新的 Xcode 项目。
继续打开 Xcode,然后单击 Create a new Xcode project。
接下来,您需要为您的新 Xcode 项目选择一个模板。使用Single View App很常见 ,因此请继续选择它并单击 Next。
您可以为您的项目命名任何您喜欢的名称,但我将命名我 的 CoreML 图像分类。 对于这个项目,我们将使用 swift,因此请确保在 Language 下拉列表中选择它。
准备调试
连接iphone
由于 Xcode Simulator 没有摄像头,因此您需要插入 iPhone。不幸的是,如果您没有 iPhone,则需要借一部才能学习本教程(以及任何其他与相机相关的应用程序)。如果您已经将 iPhone 连接到 Xcode,则可以跳到下一步。
Xcode 9 中一个非常棒的新功能是您可以在设备上无线调试您的应用程序,所以让我们现在花点时间进行设置:
在顶部菜单栏中,选择 Window > Devices and Simulators。 在出现的窗口中,确保 在顶部选择了设备 。
现在,使用避雷线插入您的设备。这应该使您的设备出现在“ 设备和模拟器 ”窗口的左窗格中。只需单击您的设备,然后选中 通过网络连接 框。
您现在可以在这款 iPhone 上为所有未来的应用程序进行无线调试。要添加其他设备,您可以遵循类似的过程。
模拟器选择
当您想最终使用您的 iPhone 进行调试时,只需从“运行”按钮旁边的下拉菜单中选择它。您应该在它旁边看到一个网络图标,表明它已连接以进行无线调试。我选择了 Vardhan 的 iPhone, 但您需要选择您的特定设备。
潜水更深
现在您已经创建了您的项目并将您的 iPhone 设置为模拟器,我们将更深入地研究并开始对实时图像分类应用程序进行编程。
准备你的项目
获取模型
为了能够开始制作您的 Core ML 图像分类应用程序,您首先需要从 Apple 网站获取 Core ML 模型。正如我之前提到的,您也可以训练自己的模型,但这需要一个单独的过程。如果您滚动到 Apple 机器学习网站的底部,您将能够选择并下载模型。
在本教程中,我将使用 MobileNet.mlmodel 模型,但您可以使用任何模型,只要您知道它的名称并确保它以 .mlmodel 结尾。
导入库
您需要导入几个框架以及通常的 UIKit. 在文件的顶部,确保存在以下导入语句:
import UIKit import AVKit import Vision
我们需要 AVKit ,因为我们将创建一个AVCaptureSession显示实时提要,同时实时对图像进行分类。此外,由于这是使用计算机视觉,我们需要导入 Vision框架。
设计你的用户界面
此应用程序的一个重要部分是显示图像分类数据标签以及来自设备摄像头的实时视频源。要开始设计用户界面,请前往 Main.storyboard 文件。
添加图像视图
前往 Object Library 并搜索 Image View。只需将其拖到您的视图控制器上即可添加。如果您愿意,您还可以添加一个占位符图像,这样您就可以大致了解应用程序在使用时的外观。
如果您确实选择了占位符图像,请确保将 Content Mode 设置为 Aspect Fit,并选中 Clip to Bounds框。这样,图像不会出现拉伸,也不会出现在 UIImageView 框外。
这是您的故事板现在的样子:
添加视图
返回 Object Library,搜索 View 并将其拖到 View Controller 上。这将为我们的标签提供一个很好的背景,这样它们就不会隐藏在正在显示的图像中。我们将把这个视图变成半透明的,这样一些预览层仍然可见(这只是对应用程序用户界面的一个很好的触摸)。
将其拖到屏幕底部,使其在三个侧面接触容器。你选择什么高度并不重要,因为我们将在这里设置约束。
添加标签
这也许是我们用户界面中最重要的部分。我们需要显示我们的应用程序认为该对象是什么,以及它的确定程度(置信度)。您可能已经猜到了,您需要将两个 标签从对象库拖到 我们刚刚创建的视图中 。 将这些标签拖到靠近中心的某个位置,彼此堆叠。
对于顶部标签,前往 Attributes Inspector 并单击 字体样式和大小旁边 的T 按钮,然后在弹出窗口中选择System 作为 字体。要将此与置信度标签区分开来,请选择 黑色 作为 样式。 最后,将 大小更改 为 24。
对于底部标签,按照相同的步骤,但不是选择 黑色 作为 样式,而是选择 常规, 对于 尺寸, 选择 17 。
下图显示了您的 当您添加了所有这些视图和标签后,Storyboard 应该会出现。如果它们与您的不完全相同,请不要担心;我们将在下一步中为它们添加约束。
添加约束
为了让这个应用程序在不同的屏幕尺寸上工作,添加约束很重要。此步骤对应用程序的其余部分并不重要,但强烈建议您在所有 ios 应用程序中执行此操作。
图像视图约束
首先要约束的是我们的 UIImageView. 为此,请选择您的图像视图,然后打开底部工具栏中的Pin 菜单 (这看起来像一个带有约束的正方形,它是右数第二个)。然后,您需要添加以下值:
在继续之前,请确保 未选中constrain to Margins框,因为这会在屏幕和实际图像视图之间产生间隙。然后,按 Enter。 现在你 UIImageView的屏幕居中,它应该在所有设备尺寸上看起来都正确。
查看约束
现在,下一步是约束出现标签的视图。选择视图,然后再次转到Pin Menu 。添加以下值:
现在,只需按 Enter 即可保存值。您的视图现在被限制在屏幕底部。
标签约束
由于视图现在受到约束,您可以将约束添加到相对于视图而不是屏幕的标签。如果您稍后决定更改标签或视图的位置,这将很有帮助。
选择两个标签,并将它们放在堆栈视图中。如果您不知道如何执行此操作,您只需按下按钮(左起第二个),该按钮看起来像一摞带有向下箭头的书。然后,您将看到按钮成为一个可选对象。
单击您的堆栈视图,然后单击对齐菜单(左起第三个)并确保选中以下框:
现在,按 Enter。您的标签应该在上一步的视图中居中,现在它们在所有屏幕尺寸上的显示都相同。
界面生成器插座
用户界面的最后一步是将元素连接到您的 ViewController()类。只需打开助手编辑器 ,然后按住 Control 单击并将每个元素拖动 到 ViewController.swift中类的顶部。这是我将在本教程中命名它们的名称:
UILabel:objectLabel
UILabel:confidenceLabel
UIImageView:imageView
当然,您可以随意命名它们,但这些是您可以在我的代码中找到的名称。
准备捕获会话
实时视频源需要一个AVCaptureSession,所以现在让我们创建一个。我们还将向用户实时显示我们的相机输入。进行捕获会话是一个相当长的过程,并且了解如何执行此操作非常重要,因为它对于您在任何 Apple 设备上使用板载摄像头进行的任何其他开发都很有用。
类扩展和功能
首先,我们可以创建一个类扩展,然后使其符合 AVCaptureVideodataOutputSampleBufferDelegate协议。您可以在实际课程中轻松做到这一点 ViewController,但我们在这里使用最佳实践,以便代码整洁有序(这是您为生产应用程序执行的方式)。
为了让我们可以在 内部调用它 viewDidLoad(),我们需要创建一个 setupSession()不接受任何参数的函数。你可以随意命名它,但是当我们稍后调用这个方法时要注意命名。
完成后,您的代码应如下所示:
// MARK: - AVCaptureSession extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { func setupSession() { // Your code goes here } }
设备输入和捕获会话
创建捕获会话的第一步是检查设备是否有摄像头。换句话说,如果没有相机,请不要尝试使用相机。然后我们需要创建实际的捕获会话。
将以下代码添加到您的 setupSession()方法中:
guard let device = AVCaptureDevice.default(for: .video) else { return } guard let input = try? AVCaptureDeviceInput(device: device) else { return } let session = AVCaptureSession() session.sessionPreset = .hd4K3840x2160
在这里,我们使用一个 guard let语句来检查设备 ( AVCaptureDevice) 是否有摄像头。当您尝试获取设备的摄像头时,您还必须指定 mediaType,在本例中为 .video。
然后,我们创建一个 AVCaptureDeviceInput,这是一个将媒体从设备带到捕获会话的输入。
最后,我们简单地创建一个 AVCaptureSession类的实例,然后将它分配给一个名为 的变量 session。我们已将会话比特率和质量自定义为 3840 x 2160 像素的超高清 (UHD)。您可以尝试使用此设置来查看适合您的设置。
预览图层和输出
进行设置的下一步 AVCaptureSession是创建一个预览层,用户可以在其中看到来自相机的输入。我们将把它添加到 UIImageView我们之前在 Storyboard 中创建的内容中。不过,最重要的部分实际上是为 Core ML 模型创建输出,以便在本教程后面处理,我们也将在这一步中完成。
在上一步的代码下面直接添加以下代码:
et previewLayer = AVCaptureVideoPreviewLayer(session: session) previewLayer.frame = view.frame imageView.layer.addSublayer(previewLayer) let output = AVCaptureVideoDataOutput() output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) session.addOutput(output)
我们首先创建一个 AVCaptureVideoPreviewLayer类的实例,然后使用我们在上一步中创建的会话对其进行初始化。完成后,我们将其分配给一个名为previewLayer. 该层用于实际显示来自相机的输入。
接下来,我们将通过将框架尺寸设置为视图的尺寸,使预览层充满整个屏幕。这样,所需的外观将适用于所有屏幕尺寸。UIImageView为了实际显示预览层,我们将把它添加为我们在制作用户界面时创建的子层 。
现在,对于重要的部分:我们创建一个 AVCaptureDataOutput类的实例并将其分配给一个名为 的变量 output。
输入和开始会话
最后,我们完成了捕获会话。在实际的 Core ML 代码之前剩下要做的就是添加输入并启动捕获会话。
在上一步下直接添加以下两行代码:
// Sets the input of the AVCaptureSession to the device's camera input session.addInput(input) // Starts the capture session session.startRunning()
这会将我们之前创建的输入添加到 中AVCaptureSession,因为在此之前,我们只创建了输入而没有添加它。最后,这行代码启动了我们花了很长时间创建的会话。
集成 Core ML 模型
我们已经下载了模型,所以下一步是在我们的应用程序中实际使用它。因此,让我们开始使用它对图像进行分类。
委托方法
首先,您需要将以下委托方法添加到您的应用程序中:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // Your code goes here }
此委托方法在写入新视频帧时触发。在我们的应用程序中,每次通过我们的实时视频源记录一帧时都会发生这种情况(其速度完全取决于应用程序运行的硬件)。
像素缓冲区和模型
现在,我们将把图像(来自实时源的一帧)转换为模型可识别的像素缓冲区。有了这个,我们以后就可以创建一个 VNCoreMLRequest.
在您之前创建的委托方法中添加以下两行代码:
guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } guard let model = try? VNCoreMLModel(for: MobileNet().model) else { return }
首先,我们从通过委托方法传入的参数创建一个像素缓冲区(Core ML 接受的格式),然后将其分配给一个名为 pixelBuffer. 然后我们将我们的 MobileNet模型分配给一个名为 的常量 model。
请注意,这两个都是使用语句创建 的,如果其中任何一个是值guard let,则函数将返回 。nil
创建请求
在执行了前两行代码之后,我们确定我们有一个像素缓冲区和一个模型。下一步是VNCoreMLRequest 使用它们创建一个。
在上一步的正下方,将以下代码行粘贴到委托方法中:
let request = VNCoreMLRequest(model: model) { (data, error) in { // Your code goes here }
在这里,我们创建了一个常量 ,并在将模型传递给它时request 为其分配方法的返回值 。VNCoreMLRequest
获取和排序结果
我们快完成了!我们现在需要做的就是得到我们的结果(模型认为我们的图像是什么),然后将它们显示给用户。
将接下来的两行代码添加到请求的完成处理程序中:
// Checks if the data is in the correct format and assigns it to results guard let results = data.results as? [VNClassificationObservation] else { return } // Assigns the first result (if it exists) to firstObject guard let firstObject = results.first else { return }
如果数据的结果(来自请求的完成处理程序)作为 的数组可用 VNClassificationObservations,则这行代码从我们之前创建的数组中获取第一个对象。然后它将被分配给一个名为 的常量 firstObject。该数组中的第一个对象是图像识别引擎最有信心的对象。
显示数据和图像处理
还记得我们何时创建了两个标签(信心和对象)吗?我们现在将使用它们来显示模型认为图像是什么。
在上一步之后附加以下代码行:
if firstObject.confidence * 100 >= 50 { self.objectLabel.text = firstObject.identifier.capitalized self.confidenceLabel.text = String(firstObject.confidence * 100) + "%" }
该 if声明确保算法至少 50% 确定其对对象的识别。然后我们只是将 设置 firstObject为文本, objectLabel因为我们知道置信度足够高。我们将仅使用 的 text 属性显示确定性百分比 confidenceLabel。由于 firstObject.confidence表示为小数,我们需要乘以 100 才能得到百分比。
最后要做的就是通过我们刚刚创建的算法来处理图像。为此,您需要在退出 captureOutput(_:didOutput:from:)委托方法之前直接键入以下代码行:
try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
结论
您在本教程中学到的概念可以应用于多种应用程序。我希望您喜欢学习使用手机对图像进行分类。虽然它可能还不完美,但您可以在未来训练自己的模型以使其更加准确。
这是应用程序完成后的样子:
- Xcode 版本
- 示例项目
- 新项目
- 准备调试
- 连接iphone
- 模拟器选择
- 准备你的项目
- 获取模型
- 设计你的用户界面
- 添加图像视图
- 添加视图
- 添加标签
- 添加约束
- 图像视图约束
- 查看约束
- 标签约束
- 界面生成器插座
- 准备捕获会话
- 类扩展和功能
- 设备输入和捕获会话
- 预览图层和输出
- 输入和开始会话
- 集成 Core ML 模型
- 委托方法
- 像素缓冲区和模型
- 创建请求
- 获取和排序结果
- 显示数据和图像处理