start_time: 2024-04-27 08:13:58 +0800

初探 Core ML:学习建立一个图像识别 App

96
iOSDevLog Ec7b4a53 cd02 46ad b136 dfa3751cff1e
IP属地: 广东
0.5 2018.05.15 18:05 字数 3294

在 WWDC 2017 中,Apple 发表了许多令开发者们为之振奋的新框架(Framework) 及 API 。而在这之中,最引人注目的莫过于 Core ML 了。藉由 Core ML,你可以为你的 App 添增机器学习(Machine Learning)的能力。而最棒的是你不需要深入的了解关于神经网络(Neural Network)以及机器学习(Machine Learning)的相关知识。接下来我们将会使用 Apple 开发者网站上提供的 Core ML 模型来制作示例 App。话不多说,Let’s Start To Learn Core ML!

注: 接下来的教学会使用 Xcode 9 作为开发工具,同时需要有 iOS 11 的设备以便测试其中的功能。Xcode 9 支持 Swift 3.2 及 4.0,我们使用 Swift 4.0 开发。

什么是 Core ML

根据 Apple 官方说明:

Core ML lets you integrate a broad variety of machine learning model types into your app. In addition to supporting extensive deep learning with over 30 layer types, it also supports standard models such as tree ensembles, SVMs, and generalized linear models. Because it’s built on top of low level technologies like Metal and Accelerate, Core ML seamlessly takes advantage of the CPU and GPU to provide maximum performance and efficiency. You can run machine learning models on the device so data doesn’t need to leave the device to be analyzed.

Core ML 是在今年 WWDC 中发表的全新机器学习框架,将会随着 iOS 11 正式发布。使用 Core ML,你可以将机器学习整合进自己的 App 之中。 在这边我们先停一下,什么是机器学习(Machine Learning)呢?简单来说,机器学习是给予电脑可以在不明确撰写程式的情况下学习能力的应用。而一个完成训练的模型便是指将资料经由演算法结合后的成果。

trained-model

作为开发者,我们主要关心的是如何使用机器学习模型来做出有趣的玩意。幸运的是,Apple 让 Core ML 可以很简单的将不同的机器学习模型整合进我们的 App 中。如此一来一般的开发者们也将能够制作出图像识别、语言处理、输入预测等等功能。

听起来是不是很酷呢?让我们开始吧。

示例 App 概览

接下来要制作的 App 相当地简单。这个 App 能够让使用者拍照或是从相簿中选择一张相片,然后机器学习演算法将会试着辨识出相片中的物品是什么。虽然可能无法每次都识别成功,但你可以藉此思考出如何在你 App 里使用 Core ML。

coreml-app-demo

现在就开始吧!

首先,开启 Xcode 9 然后建立一个新项目。选择 Single View App,接着确认程式语言为 Swift。

xcode9-new-proj

制作界面

编注: 如果不想重头开始制作UI的话,你可以下载 后,直接阅读关于 Core ML 实作的段落

一开始我们要做的是打开 Main.storyboard 然后加入几个 UI 元件到 View 之中。因此我们先点选 StoryBoard 中的 ViewController,然后到 Xcode 的功能列中点选 Editor-> Embed In-> Navigation Controller。当完成后你会看到 Navigation Bar 出现在 View 之上,接着我们将这个 Navigation Bar 的标题命名为 Core ML(或是任何你觉得适合的文字)。

Core ML Demo UI

接下来,拖曳两个按钮到 Navigation Bar 里头,一个放在标题左边一个放右边。接着点选左边的按钮然后到右侧的 Attributes Inspector 里将按钮由 System Item 改为 「Camera」。右边的按钮则修改文字为 「Library」。这两个按钮的用途是让使用者可以从相簿中选取相片或开启相机拍照。

最后我们还需要加入两个元件,分别是 UILabel 及 UIImageView。拖曳 UIImageView 到 View 裡设定垂直水平置中以及长宽为 299,让 UIImageView 看起来是个正方形。现在轮到 UILabel,将其放入到 View 的底部并延伸两端到 View 的两侧。这样我们完成这个 App 的 UI 了。

虽然没有提到设定这些 View 的 Auto Layout,但很推荐你尝试设定 Auto Layout 以避免 UI 元件的错置。如果你不了解如何设定,也可以将 Storyboard 的尺寸设定为你要运行的设备尺寸。

coreml-storyboard

实作相机以及相簿功能

现在我们已经完成 UI 了,接下来往实作功能的方向前进吧。在这个段落中,我们将会实作相簿以及相机按钮功能。首先在 ViewController.swift 中,我们要先调用 UINavigationControllerDelegate ,因为后续的 UIImagePickerController 会需要用到这部份。

class ViewController: UIViewController, UINavigationControllerDelegate 

接着为画面上的 UILabel 及 UIImageView 加上 IBoutlet。为了方便起见,我将 UIImageView 命名为 imageView,UILabel 则命名为 classifier。完成后的代码应该会如下面所呈现的样子:

import UIKit
 
class ViewController: UIViewController, UINavigationControllerDelegate {
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var classifier: UILabel!
    
     override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

接下来,你需要为两个按钮分别建立 IBAction 。请将以下的 Action 方法加入至 Viewcontroller 中吧:

@IBAction func camera(_ sender: Any) {
    
    if !UIImagePickerController.isSourceTypeAvailable(.camera) {
        return
    }
    
    let cameraPicker = UIImagePickerController()
    cameraPicker.delegate = self
    cameraPicker.sourceType = .camera
    cameraPicker.allowsEditing = false
    
    present(cameraPicker, animated: true)
}
 
@IBAction func openLibrary(_ sender: Any) {
    let picker = UIImagePickerController()
    picker.allowsEditing = false
    picker.delegate = self
    picker.sourceType = .photoLibrary
    present(picker, animated: true)
}

到这边我们先了解一下上述的 Action 方法。我们各产生了一个 UIImagePickerController 常数,然后将其设定为不允许编辑图像(不论是相机拍摄或是相簿选取),接着将 Delegate 指向为自己。最后呈现 UIImagePickerController 给使用者。

因为我们尚未将 UIImagePickerControllerDelegate 的方法们加入至 ViewController.swift中,所以会发生错误。我们另外建立 Extension 来调用 delegate:

extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
}

上面的代码处理了使用者取消选取图像的动作,同时也指派了 UIImagePickerControllerDelegate 的类别方法到我们的 Swift 档案中。现在,你的代码会如同下面所示:

import UIKit
 
class ViewController: UIViewController, UINavigationControllerDelegate {
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var classifier: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func camera(_ sender: Any) {
        
        if !UIImagePickerController.isSourceTypeAvailable(.camera) {
            return
        }
        
        let cameraPicker = UIImagePickerController()
        cameraPicker.delegate = self
        cameraPicker.sourceType = .camera
        cameraPicker.allowsEditing = false
        
        present(cameraPicker, animated: true)
    }
    
    @IBAction func openLibrary(_ sender: Any) {
        let picker = UIImagePickerController()
        picker.allowsEditing = false
        picker.delegate = self
        picker.sourceType = .photoLibrary
        present(picker, animated: true)
    }
 
}
 
extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
}

现在回头确认一下 Storyboard 上的 UI 元件是否有与 Outlet 辨识及 Action 方法确实连结。

为了使用手机上的相机以及相簿,还有一项必需要做的事。前往 Info.plist 然后新增 Privacy – Camera Usage DescriptionPrivacy – Photo Library Usage Description。从 iOS 10 开始,你需要添注说明为何你的 App 需要使用相机及相簿功能。

coreml-plist-privacy

好了,现在你已经准备好前往本篇教学的核心部分了。再次提醒,如果你不想重头建立示例 App 的话,可以下载此份档案

整合 Core ML Data 模型

现在让我们转换一下开始整合 Core ML 资料模型到我们的 App。如同早先提到的,我们需要一份预先训练的资料模型来与 Core ML 合作。虽然你也可以自己建立一份资料模型,但在本次示例里我们会使用由 Apple 开发者网站所提供预先训练完毕的资料模型。

前往 Apple 开发者网站的 Machine Learning 页面然后拉到最底下,你会找到四个已预先训练好的 Core ML 资料模型。

coreml-pretrained-model

在这里,我们使用了 Inception v3 模型。当然,你也可以程式其他另外三种的资料模型。当你下载完 Inception v3 后,将它放入 Xcode 项目中,然后看一下他显示了哪些东西。

Core ML Inception v3 model

注:请确认已选择了项目的 Target Membership,否则你的 App 将无法存取档案。

从上面的画面中,你可以看到资料模型的类型也就是神经网络(Neural Networks)的分类器。其他你需要注意的资讯有模型评估参数(Model Evaluation Parameters),这告诉你模型放入的是什么,输出的又是什么。以这来说,这个模型可以放入一张 299×299 的图像,然后回传给你这张图像最有可能的分类以及每种分类的可能性。

另外一个你会注意到的是模型的类别(Model Class)。这个模型类别(Inceptionv3)是由机器学习模型中产生出来并且可以让我们直接在代码里使用。如果点击 Inceptionv3 旁的箭头,你可以看到这个类别的原始码。

inceptionv3-class

现在,让我们把资料模型加入至我们的代码中吧。回到 ViewController.swift,将 CoreML 引入:

import CoreML

接着,为 Inceptionv3 宣告一个 model 变数并且在 viewWillAppear() 中初始化。

var model: Inceptionv3!
 
override func viewWillAppear(_ animated: Bool) {
    model = Inceptionv3()
}

我知道你现在在想什么。

「为何我们不更早一点初始化呢?」

「在 viewWillAppear 中定义的要点是什么?」

这要点是当你的 App 试着识别你的图像里有哪些物件时,会快上许多。

现在,回头看一下 Inceptionv3.mlmodel,我们看到这个模型只能放入尺寸为 299x299 的图像。所以,我们该如何让一张图像符合这样的尺寸呢?这就是我们接下来要做的。

图像转换

ViewController.swift 的 Extension 中,添加下述的代码。在新增的代码里,我们实作了 imagePickerController(_:didFinishPickingMediaWithInfo) 来处理选取完照片的后续动作。

extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        picker.dismiss(animated: true)
        classifier.text = "Analyzing Image..."
        guard let image = info["UIImagePickerControllerOriginalImage"] as? UIImage else {
            return
        } 
        
        UIGraphicsBeginImageContextWithOptions(CGSize(width: 299, height: 299), true, 2.0)
        image.draw(in: CGRect(x: 0, y: 0, width: 299, height: 299))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        
        let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
        var pixelBuffer : CVPixelBuffer?
        let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(newImage.size.width), Int(newImage.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
        guard (status == kCVReturnSuccess) else {
            return
        } 
        
        CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
        let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)
        
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        let context = CGContext(data: pixelData, width: Int(newImage.size.width), height: Int(newImage.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) //3
        
        context?.translateBy(x: 0, y: newImage.size.height)
        context?.scaleBy(x: 1.0, y: -1.0)
        
        UIGraphicsPushContext(context!)
        newImage.draw(in: CGRect(x: 0, y: 0, width: newImage.size.width, height: newImage.size.height))
        UIGraphicsPopContext()
        CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
        imageView.image = newImage
    }
}

在上述代码中被标记起来的部分:

  1. 第 7-11 行: 我们从 info 这个 Dictionary (使用 UIImagePickerControllerOriginalImage 这个 key)里取回了选取的的图像。同时我们让 UIImagePickerController 在我们选取图像后消失。
  2. 第 13-16 行: 因为我们使用的模型只接受 299x299 的尺寸,所以将图像转换为正方形,并将这个新的正方形图像指定给另个常数 newImage
  3. 第 18-23 行: 我们把 newImage 转换为 CVPixelBuffer。 给对于 CVPixelBuffer 不熟悉的人, CVPixelBuffers 是一个将像数(Pixcel)存在主记忆体里的图像缓冲器。你可以从这里了解更多关于 CVPixelBuffers 的资讯
  4. 第 31-32 行: 然后我们取得了这个图像里的像数并转换为设备的 RGB 色彩。接着把这些资料作成 CGContext。这样一来每当我们需要渲染(或是改变)一些底层属性时可以很轻易的呼叫使用。最后的两行代码即是以此进行翻转以及缩放。
  5. 第 34-38 行: 最后,我们完成新图像的绘製并把旧的资料移除,然后将 newImage 指定给 imageView.image

如果你有点不明白上面的代码,别担心。这些是有点进阶的 Core Image 语法,并不在这次教学范围内。你只要明白这些是要将选取的图像转换为资料模型可以接受的资料即可。不过推荐你可以换个数值执行几次,看看执行结果以更进一步的了解。

使用 Core ML

无论如何,让我们把注意力拉回到 Core ML 上吧。我们使用 Inceptionv3 模型来作物件识别。藉由 Core ML,我们只需几行代码就可以完成工作了。贴上下述的代码到 imageView.image = newImage 底下吧。

guard let prediction = try? model.prediction(image: pixelBuffer!) else {
    return
}
 
classifier.text = "I think this is a \(prediction.classLabel)."

没错,就是这样!Inceptionv3 类别已经产生了名为 prediction(image:) 的方法,它被用来预测所提供的图像裡的物件。这裡我们把 pixelBuffer 变数放入方法中,这个变数代表的是缩放后的图像。一旦完成预测会以字串形式回传结果,我们把 classifier 的文字内容更新为收到的结果文字。

是时候来测试我们的 App 萝!在模拟器或上手机上(需安装 iOS 11)Build 及 Run ,接着从相簿选取或相机拍摄图像,App 就会告诉你图像是什么。

coreml-successful-case

当测试 App 时,你可能注意到 App 并不能很正确的预测出内容。这并不是你的代码有问题,而是出在这份资料模型上。

coreml-failed-case

小结

我希望你现在了解了如何将 Core ML 整合至你的 App 之中。本篇只是介绍性的教学文章,如果你对如何将其他的机器学习模型(如:Caffe、Keras、SciKit)整合至 Core ML 模型感兴趣的话,敬请锁定我们 Core ML 系列的下篇教学文章。我将会讲述如何将这些模型转换至 Core ML 模型。

如果想了解整个 Demo App 的话,你可以到 GitHub 上下载完整项目。

如果想知道更多关于 Core ML 的资讯,你可以参考 Core ML 官方文件。或是参考 Apple 于 WWDC 2017 上关于 Core ML 的 Session 演讲:

至此,你对于 Core ML 有任何的想法吗?欢迎分享你的意见。

原文Introduction to Core ML: Building a Simple Image Recognition App

简宝玉写作群日更打卡第 25 天

最后编辑于 :2018-05-16 12:04:39
©著作权归作者所有,转载或内容合作请联系作者
序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 159117 评论 4 赞 362
序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 67328 评论 1 赞 293
文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
8 开封第一讲书人 阅读 108839 评论 0 赞 243
文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
8 开封第一讲书人 阅读 44007 评论 0 赞 206
正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 52384 评论 3 赞 287
文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
8 开封第一讲书人 阅读 40629 评论 1 赞 219
那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 31880 评论 2 赞 313
文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
8 开封第一讲书人 阅读 30593 评论 0 赞 198
序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 34313 评论 1 赞 243
正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 30575 评论 2 赞 246
正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 32066 评论 1 赞 260
序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 28392 评论 2 赞 253
正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 33052 评论 3 赞 236
文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
8 开封第一讲书人 阅读 26082 评论 0 赞 8
文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
8 开封第一讲书人 阅读 26844 评论 0 赞 195
我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
783c64db 45e5 48d7 82e4 95736f50533e 沈念sama 阅读 35662 评论 2 赞 274
正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
388e473c fe2f 40e0 9301 e357ae8f1b41 茶点故事 阅读 35575 评论 2 赞 270
end_time: 2024-04-27 08:13:59 +0800 Completed in 137.518573ms