在 WWDC 2017 中塞颁,Apple 發(fā)表了許多令開發(fā)者們?yōu)橹駣^的新框架(Framework) 及 API 祠锣。而在這之中锤岸,最引人注目的莫過(guò)于 Core ML 了是偷。藉由 Core ML蛋铆,你可以為你的 App 添增機(jī)器學(xué)習(xí)(Machine Learning)的能力刺啦。而最棒的是你不需要深入的了解關(guān)于神經(jīng)網(wǎng)絡(luò)(Neural Network)以及機(jī)器學(xué)習(xí)(Machine Learning)的相關(guān)知識(shí)玛瘸。接下來(lái)我們將會(huì)使用 Apple 開發(fā)者網(wǎng)站上提供的 Core ML 模型來(lái)制作示例 App糊渊。話不多說(shuō)渺绒,Let’s Start To Learn Core ML!
注: 接下來(lái)的教學(xué)會(huì)使用 Xcode 9 作為開發(fā)工具,同時(shí)需要有 iOS 11 的設(shè)備以便測(cè)試其中的功能殷绍。Xcode 9 支持 Swift 3.2 及 4.0主到,我們使用 Swift 4.0 開發(fā)镰烧。
什么是 Core ML
根據(jù) Apple 官方說(shuō)明:
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 中發(fā)表的全新機(jī)器學(xué)習(xí)框架,將會(huì)隨著 iOS 11 正式發(fā)布结执。使用 Core ML献幔,你可以將機(jī)器學(xué)習(xí)整合進(jìn)自己的 App 之中蜡感。 在這邊我們先停一下,什么是機(jī)器學(xué)習(xí)(Machine Learning)呢犀斋?簡(jiǎn)單來(lái)說(shuō)叽粹,機(jī)器學(xué)習(xí)是給予電腦可以在不明確撰寫程式的情況下學(xué)習(xí)能力的應(yīng)用虫几。而一個(gè)完成訓(xùn)練的模型便是指將資料經(jīng)由演算法結(jié)合后的成果辆脸。
作為開發(fā)者,我們主要關(guān)心的是如何使用機(jī)器學(xué)習(xí)模型來(lái)做出有趣的玩意空执。幸運(yùn)的是辨绊,Apple 讓 Core ML 可以很簡(jiǎn)單的將不同的機(jī)器學(xué)習(xí)模型整合進(jìn)我們的 App 中门坷。如此一來(lái)一般的開發(fā)者們也將能夠制作出圖像識(shí)別默蚌、語(yǔ)言處理苇羡、輸入預(yù)測(cè)等等功能。
聽(tīng)起來(lái)是不是很酷呢攘轩?讓我們開始吧度帮。
示例 App 概覽
接下來(lái)要制作的 App 相當(dāng)?shù)睾?jiǎn)單。這個(gè) App 能夠讓使用者拍照或是從相簿中選擇一張相片冕屯,然后機(jī)器學(xué)習(xí)演算法將會(huì)試著辨識(shí)出相片中的物品是什么安聘。雖然可能無(wú)法每次都識(shí)別成功浴韭,但你可以藉此思考出如何在你 App 里使用 Core ML念颈。
現(xiàn)在就開始吧榴芳!
首先窟感,開啟 Xcode 9 然后建立一個(gè)新項(xiàng)目柿祈。選擇 Single View App躏嚎,接著確認(rèn)程式語(yǔ)言為 Swift卢佣。
制作界面
編注: 如果不想重頭開始制作UI的話晚缩,你可以下載 后,直接閱讀關(guān)于 Core ML 實(shí)作的段落
一開始我們要做的是打開 Main.storyboard
然后加入幾個(gè) UI 元件到 View 之中待笑。因此我們先點(diǎn)選 StoryBoard 中的 ViewController暮蹂,然后到 Xcode 的功能列中點(diǎn)選 Editor-> Embed In-> Navigation Controller
仰泻。當(dāng)完成后你會(huì)看到 Navigation Bar 出現(xiàn)在 View 之上集侯,接著我們將這個(gè) Navigation Bar 的標(biāo)題命名為 Core ML(或是任何你覺(jué)得適合的文字)棠枉。
接下來(lái)辈讶,拖曳兩個(gè)按鈕到 Navigation Bar 里頭,一個(gè)放在標(biāo)題左邊一個(gè)放右邊月幌。接著點(diǎn)選左邊的按鈕然后到右側(cè)的 Attributes Inspector
里將按鈕由 System Item
改為 「Camera」飞醉。右邊的按鈕則修改文字為 「Library」缅帘。這兩個(gè)按鈕的用途是讓使用者可以從相簿中選取相片或開啟相機(jī)拍照钦无。
最后我們還需要加入兩個(gè)元件盖袭,分別是 UILabel 及 UIImageView失暂。拖曳 UIImageView 到 View 裡設(shè)定垂直水平置中以及長(zhǎng)寬為 299彼宠,讓 UIImageView 看起來(lái)是個(gè)正方形。現(xiàn)在輪到 UILabel弟塞,將其放入到 View 的底部并延伸兩端到 View 的兩側(cè)凭峡。這樣我們完成這個(gè) App 的 UI 了。
雖然沒(méi)有提到設(shè)定這些 View 的 Auto Layout决记,但很推薦你嘗試設(shè)定 Auto Layout 以避免 UI 元件的錯(cuò)置。如果你不了解如何設(shè)定系宫,也可以將 Storyboard 的尺寸設(shè)定為你要運(yùn)行的設(shè)備尺寸索昂。
實(shí)作相機(jī)以及相簿功能
現(xiàn)在我們已經(jīng)完成 UI 了,接下來(lái)往實(shí)作功能的方向前進(jìn)吧扩借。在這個(gè)段落中椒惨,我們將會(huì)實(shí)作相簿以及相機(jī)按鈕功能。首先在 ViewController.swift
中潮罪,我們要先調(diào)用 UINavigationControllerDelegate
康谆,因?yàn)楹罄m(xù)的 UIImagePickerController
會(huì)需要用到這部份。
class ViewController: UIViewController, UINavigationControllerDelegate
接著為畫面上的 UILabel 及 UIImageView 加上 IBoutlet错洁。為了方便起見(jiàn)秉宿,我將 UIImageView
命名為 imageView,UILabel
則命名為 classifier屯碴。完成后的代碼應(yīng)該會(huì)如下面所呈現(xiàn)的樣子:
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()
}
}
接下來(lái)描睦,你需要為兩個(gè)按鈕分別建立 IBAction 。請(qǐng)將以下的 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 方法导而。我們各產(chǎn)生了一個(gè) UIImagePickerController
常數(shù)忱叭,然后將其設(shè)定為不允許編輯圖像(不論是相機(jī)拍攝或是相簿選取)今艺,接著將 Delegate 指向?yàn)樽约涸铣蟆W詈蟪尸F(xiàn) UIImagePickerController
給使用者。
因?yàn)槲覀兩形磳?UIImagePickerControllerDelegate
的方法們加入至 ViewController.swift
中虚缎,所以會(huì)發(fā)生錯(cuò)誤撵彻。我們另外建立 Extension 來(lái)調(diào)用 delegate:
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
}
上面的代碼處理了使用者取消選取圖像的動(dòng)作,同時(shí)也指派了 UIImagePickerControllerDelegate
的類別方法到我們的 Swift 檔案中∈的担現(xiàn)在陌僵,你的代碼會(huì)如同下面所示:
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)
}
}
現(xiàn)在回頭確認(rèn)一下 Storyboard 上的 UI 元件是否有與 Outlet 辨識(shí)及 Action 方法確實(shí)連結(jié)。
為了使用手機(jī)上的相機(jī)以及相簿创坞,還有一項(xiàng)必需要做的事碗短。前往 Info.plist
然后新增 Privacy – Camera Usage Description 及 Privacy – Photo Library Usage Description。從 iOS 10 開始题涨,你需要添注說(shuō)明為何你的 App 需要使用相機(jī)及相簿功能偎谁。
好了总滩,現(xiàn)在你已經(jīng)準(zhǔn)備好前往本篇教學(xué)的核心部分了。再次提醒巡雨,如果你不想重頭建立示例 App 的話闰渔,可以下載此份檔案。
整合 Core ML Data 模型
現(xiàn)在讓我們轉(zhuǎn)換一下開始整合 Core ML 資料模型到我們的 App鸯隅。如同早先提到的澜建,我們需要一份預(yù)先訓(xùn)練的資料模型來(lái)與 Core ML 合作。雖然你也可以自己建立一份資料模型蝌以,但在本次示例里我們會(huì)使用由 Apple 開發(fā)者網(wǎng)站所提供預(yù)先訓(xùn)練完畢的資料模型。
前往 Apple 開發(fā)者網(wǎng)站的 Machine Learning 頁(yè)面然后拉到最底下何之,你會(huì)找到四個(gè)已預(yù)先訓(xùn)練好的 Core ML 資料模型跟畅。
在這里,我們使用了 Inception v3 模型溶推。當(dāng)然徊件,你也可以程式其他另外三種的資料模型。當(dāng)你下載完 Inception v3 后蒜危,將它放入 Xcode 項(xiàng)目中虱痕,然后看一下他顯示了哪些東西。
注:請(qǐng)確認(rèn)已選擇了項(xiàng)目的 Target Membership辐赞,否則你的 App 將無(wú)法存取檔案部翘。
從上面的畫面中,你可以看到資料模型的類型也就是神經(jīng)網(wǎng)絡(luò)(Neural Networks)的分類器响委。其他你需要注意的資訊有模型評(píng)估參數(shù)(Model Evaluation Parameters)新思,這告訴你模型放入的是什么,輸出的又是什么赘风。以這來(lái)說(shuō)夹囚,這個(gè)模型可以放入一張 299×299 的圖像,然后回傳給你這張圖像最有可能的分類以及每種分類的可能性邀窃。
另外一個(gè)你會(huì)注意到的是模型的類別(Model Class)荸哟。這個(gè)模型類別(Inceptionv3
)是由機(jī)器學(xué)習(xí)模型中產(chǎn)生出來(lái)并且可以讓我們直接在代碼里使用。如果點(diǎn)擊 Inceptionv3
旁的箭頭瞬捕,你可以看到這個(gè)類別的原始碼鞍历。
現(xiàn)在,讓我們把資料模型加入至我們的代碼中吧山析⊙吡牵回到 ViewController.swift
,將 CoreML 引入:
import CoreML
接著笋轨,為 Inceptionv3
宣告一個(gè) model
變數(shù)并且在 viewWillAppear()
中初始化秆剪。
var model: Inceptionv3!
override func viewWillAppear(_ animated: Bool) {
model = Inceptionv3()
}
我知道你現(xiàn)在在想什么赊淑。
「為何我們不更早一點(diǎn)初始化呢?」
「在 viewWillAppear
中定義的要點(diǎn)是什么?」
這要點(diǎn)是當(dāng)你的 App 試著識(shí)別你的圖像里有哪些物件時(shí)仅讽,會(huì)快上許多陶缺。
現(xiàn)在,回頭看一下 Inceptionv3.mlmodel
洁灵,我們看到這個(gè)模型只能放入尺寸為 299x299
的圖像饱岸。所以,我們?cè)撊绾巫屢粡垐D像符合這樣的尺寸呢徽千?這就是我們接下來(lái)要做的苫费。
圖像轉(zhuǎn)換
在 ViewController.swift
的 Extension 中,添加下述的代碼双抽。在新增的代碼里百框,我們實(shí)作了 imagePickerController(_:didFinishPickingMediaWithInfo)
來(lái)處理選取完照片的后續(xù)動(dòng)作。
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
}
}
在上述代碼中被標(biāo)記起來(lái)的部分:
-
第 7-11 行: 我們從
info
這個(gè) Dictionary (使用UIImagePickerControllerOriginalImage
這個(gè) key)里取回了選取的的圖像牍汹。同時(shí)我們讓UIImagePickerController
在我們選取圖像后消失铐维。 -
第 13-16 行: 因?yàn)槲覀兪褂玫哪P椭唤邮?
299x299
的尺寸,所以將圖像轉(zhuǎn)換為正方形慎菲,并將這個(gè)新的正方形圖像指定給另個(gè)常數(shù)newImage
嫁蛇。 -
第 18-23 行: 我們把
newImage
轉(zhuǎn)換為CVPixelBuffer
。 給對(duì)于CVPixelBuffer
不熟悉的人露该, CVPixelBuffers 是一個(gè)將像數(shù)(Pixcel)存在主記憶體里的圖像緩沖器睬棚。你可以從這里了解更多關(guān)于CVPixelBuffers
的資訊 -
第 31-32 行: 然后我們?nèi)〉昧诉@個(gè)圖像里的像數(shù)并轉(zhuǎn)換為設(shè)備的 RGB 色彩。接著把這些資料作成
CGContext
有决。這樣一來(lái)每當(dāng)我們需要渲染(或是改變)一些底層屬性時(shí)可以很輕易的呼叫使用闸拿。最后的兩行代碼即是以此進(jìn)行翻轉(zhuǎn)以及縮放。 -
第 34-38 行: 最后书幕,我們完成新圖像的繪製并把舊的資料移除新荤,然后將
newImage
指定給imageView.image
。
如果你有點(diǎn)不明白上面的代碼台汇,別擔(dān)心苛骨。這些是有點(diǎn)進(jìn)階的 Core Image
語(yǔ)法,并不在這次教學(xué)范圍內(nèi)苟呐。你只要明白這些是要將選取的圖像轉(zhuǎn)換為資料模型可以接受的資料即可痒芝。不過(guò)推薦你可以換個(gè)數(shù)值執(zhí)行幾次,看看執(zhí)行結(jié)果以更進(jìn)一步的了解牵素。
使用 Core ML
無(wú)論如何严衬,讓我們把注意力拉回到 Core ML 上吧。我們使用 Inceptionv3 模型來(lái)作物件識(shí)別笆呆。藉由 Core ML请琳,我們只需幾行代碼就可以完成工作了粱挡。貼上下述的代碼到 imageView.image = newImage
底下吧。
guard let prediction = try? model.prediction(image: pixelBuffer!) else {
return
}
classifier.text = "I think this is a \(prediction.classLabel)."
沒(méi)錯(cuò)俄精,就是這樣询筏!Inceptionv3
類別已經(jīng)產(chǎn)生了名為 prediction(image:)
的方法,它被用來(lái)預(yù)測(cè)所提供的圖像裡的物件竖慧。這裡我們把 pixelBuffer
變數(shù)放入方法中嫌套,這個(gè)變數(shù)代表的是縮放后的圖像。一旦完成預(yù)測(cè)會(huì)以字串形式回傳結(jié)果圾旨,我們把 classifier
的文字內(nèi)容更新為收到的結(jié)果文字踱讨。
是時(shí)候來(lái)測(cè)試我們的 App 蘿!在模擬器或上手機(jī)上(需安裝 iOS 11)Build 及 Run 砍的,接著從相簿選取或相機(jī)拍攝圖像勇蝙,App 就會(huì)告訴你圖像是什么。
當(dāng)測(cè)試 App 時(shí)挨约,你可能注意到 App 并不能很正確的預(yù)測(cè)出內(nèi)容。這并不是你的代碼有問(wèn)題产雹,而是出在這份資料模型上诫惭。
小結(jié)
我希望你現(xiàn)在了解了如何將 Core ML 整合至你的 App 之中。本篇只是介紹性的教學(xué)文章蔓挖,如果你對(duì)如何將其他的機(jī)器學(xué)習(xí)模型(如:Caffe夕土、Keras、SciKit)整合至 Core ML 模型感興趣的話瘟判,敬請(qǐng)鎖定我們 Core ML 系列的下篇教學(xué)文章怨绣。我將會(huì)講述如何將這些模型轉(zhuǎn)換至 Core ML 模型。
如果想了解整個(gè) Demo App 的話拷获,你可以到 GitHub 上下載完整項(xiàng)目篮撑。
如果想知道更多關(guān)于 Core ML 的資訊,你可以參考 Core ML 官方文件匆瓜∮浚或是參考 Apple 于 WWDC 2017 上關(guān)于 Core ML 的 Session 演講:
至此,你對(duì)于 Core ML 有任何的想法嗎驮吱?歡迎分享你的意見(jiàn)茧妒。
原文:Introduction to Core ML: Building a Simple Image Recognition App
簡(jiǎn)寶玉寫作群日更打卡第 25 天