iOS-調(diào)戲CoreML-這是花瓶姊舵?

CoreML 是 Apple 在 WWDC 2017 推出的機(jī)器學(xué)習(xí)框架盐类。但是其到底有什么功能呢寞奸,能不能識(shí)別花瓶呛谜,看看就知道了。

原文發(fā)表在個(gè)人博客iOS-CoreML-初探枪萄,轉(zhuǎn)載請(qǐng)注明出處隐岛。

模型

CoreML 中, Apple 定義了一套自己的模型格式瓷翻,后綴名為: mimodel聚凹,通過(guò) CoreML 框架,以及模型庫(kù)齐帚,可以在 App 層面進(jìn)行機(jī)器學(xué)習(xí)的功能研發(fā)妒牙。

CoreML

官網(wǎng)已經(jīng)提供四個(gè)模型庫(kù)供下載。

iOS-CoreML-Model

Demo

官網(wǎng)提供了一個(gè) Demo童谒,要求 XCode 9 + iOS 11 的環(huán)境单旁。

下載下來(lái) Run 了一下,不得不說(shuō)饥伊,Apple 對(duì)開(kāi)發(fā)者還是非常友好的象浑,直接將模型文件拖到項(xiàng)目中,Xcode 會(huì)自動(dòng)生成接口文件:

import CoreML

class MarsHabitatPricerInput : MLFeatureProvider {
    var solarPanels: Double
    var greenhouses: Double
    var size: Double
    
    var featureNames: Set<String> {
        get {
            return ["solarPanels", "greenhouses", "size"]
        }
    }
    
    func featureValue(for featureName: String) -> MLFeatureValue? {
        if (featureName == "solarPanels") {
            return MLFeatureValue(double: solarPanels)
        }
        if (featureName == "greenhouses") {
            return MLFeatureValue(double: greenhouses)
        }
        if (featureName == "size") {
            return MLFeatureValue(double: size)
        }
        return nil
    }
    
    init(solarPanels: Double, greenhouses: Double, size: Double) {
        self.solarPanels = solarPanels
        self.greenhouses = greenhouses
        self.size = size
    }
}

class MarsHabitatPricerOutput : MLFeatureProvider {
    let price: Double
    
    var featureNames: Set<String> {
        get {
            return ["price"]
        }
    }
    
    func featureValue(for featureName: String) -> MLFeatureValue? {
        if (featureName == "price") {
            return MLFeatureValue(double: price)
        }
        return nil
    }
    
    init(price: Double) {
        self.price = price
    }
}

@objc class MarsHabitatPricer:NSObject {
    var model: MLModel
    init(contentsOf url: URL) throws {
        self.model = try MLModel(contentsOf: url)
    }
    convenience override init() {
        let bundle = Bundle(for: MarsHabitatPricer.self)
        let assetPath = bundle.url(forResource: "MarsHabitatPricer", withExtension:"mlmodelc")
        try! self.init(contentsOf: assetPath!)
    }
    func prediction(input: MarsHabitatPricerInput) throws -> MarsHabitatPricerOutput {
        let outFeatures = try model.prediction(from: input)
        let result = MarsHabitatPricerOutput(price: outFeatures.featureValue(for: "price")!.doubleValue)
        return result
    }
    func prediction(solarPanels: Double, greenhouses: Double, size: Double) throws -> MarsHabitatPricerOutput {
        let input_ = MarsHabitatPricerInput(solarPanels: solarPanels, greenhouses: greenhouses, size: size)
        return try self.prediction(input: input_)
    }
}

可以看到琅豆,主要是定義了輸入愉豺,輸出以及預(yù)測(cè)的格式,調(diào)用的時(shí)候茫因,也非常簡(jiǎn)單蚪拦,傳參即可。

但是這些接口文件并沒(méi)有在 XCode 左邊的文件樹(shù)中出現(xiàn)冻押。

查了一下驰贷,是生成在 DerivedData 目錄下,估計(jì)是想開(kāi)發(fā)者使用起來(lái)更簡(jiǎn)潔洛巢。

運(yùn)行一下括袒,可以看到,主要功能是對(duì)價(jià)格進(jìn)行預(yù)測(cè)稿茉。

iOS-CoreML-Demo

貌似稍微有點(diǎn)不夠高大上...

Resnet50

官網(wǎng)提供的四個(gè)模型庫(kù)锹锰,我們還沒(méi)用呢,當(dāng)然要看下能用來(lái)干啥漓库,看了一下恃慧,貌似主要是物體識(shí)別,OK渺蒿,代碼走起痢士。

先下載模型庫(kù) Resnet50, 然后創(chuàng)建一個(gè)新的 Swift 項(xiàng)目,將其拖進(jìn)去:

iOS-CoreML-Model-Resnet50

從描述里面可以看出來(lái)茂装,其實(shí)一個(gè)神經(jīng)網(wǎng)絡(luò)的分類器良瞧,輸入是一張像素為 (224 * 224) 的圖片陪汽,輸出為分類結(jié)果训唱。

自動(dòng)生成的接口文件:

import CoreML

class Resnet50Input : MLFeatureProvider {
    var image: CVPixelBuffer
    
    var featureNames: Set<String> {
        get {
            return ["image"]
        }
    }
    
    func featureValue(for featureName: String) -> MLFeatureValue? {
        if (featureName == "image") {
            return MLFeatureValue(pixelBuffer: image)
        }
        return nil
    }
    
    init(image: CVPixelBuffer) {
        self.image = image
    }
}

class Resnet50Output : MLFeatureProvider {
    let classLabelProbs: [String : Double]
    let classLabel: String
    
    var featureNames: Set<String> {
        get {
            return ["classLabelProbs", "classLabel"]
        }
    }
    
    func featureValue(for featureName: String) -> MLFeatureValue? {
        if (featureName == "classLabelProbs") {
            return try! MLFeatureValue(dictionary: classLabelProbs as [NSObject : NSNumber])
        }
        if (featureName == "classLabel") {
            return MLFeatureValue(string: classLabel)
        }
        return nil
    }
    
    init(classLabelProbs: [String : Double], classLabel: String) {
        self.classLabelProbs = classLabelProbs
        self.classLabel = classLabel
    }
}

@objc class Resnet50:NSObject {
    var model: MLModel
    init(contentsOf url: URL) throws {
        self.model = try MLModel(contentsOf: url)
    }
    convenience override init() {
        let bundle = Bundle(for: Resnet50.self)
        let assetPath = bundle.url(forResource: "Resnet50", withExtension:"mlmodelc")
        try! self.init(contentsOf: assetPath!)
    }
    func prediction(input: Resnet50Input) throws -> Resnet50Output {
        let outFeatures = try model.prediction(from: input)
        let result = Resnet50Output(classLabelProbs: outFeatures.featureValue(for: "classLabelProbs")!.dictionaryValue as! [String : Double], classLabel: outFeatures.featureValue(for: "classLabel")!.stringValue)
        return result
    }
    func prediction(image: CVPixelBuffer) throws -> Resnet50Output {
        let input_ = Resnet50Input(image: image)
        return try self.prediction(input: input_)
    }
}

OK褥蚯,要照片,而且是 CVPixelBuffer 類型的况增。

但是每次從相冊(cè)選太煩了赞庶,所以我們直接攝像頭走起。將 AVCam 的主要功能類復(fù)制到項(xiàng)目中澳骤。

iOS-CoreML-AVCam

然后歧强,禁用 CameraViewController 中一些不必要的按鈕:

self.recordButton.isHidden = true
self.captureModeControl.isHidden = true
self.livePhotoModeButton.isHidden = true
self.depthDataDeliveryButton.isHidden = true

由于,AVCapturePhotoCaptureDelegate 拍照完成的回調(diào)為:

 func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) 

看了下 AVCaputrePhoto 的定義为肮,里面剛好有 CVPixelBuffer 格式的屬性:

iOS-CoreML-AVCam-PreviewPixelBuffer

直接傳進(jìn)去試試:

// Predicte
if let pixelBuffer = photo.previewPixelBuffer {
    guard let Resnet50CategoryOutput = try? model.prediction(image:pixelBuffer) else {
        fatalError("Unexpected runtime error.")
    }
}

一切看起來(lái)很完美摊册,編譯通過(guò),運(yùn)行起來(lái)颊艳,點(diǎn)一下拍照按鈕茅特,額,Crash了棋枕,異常:

[core] Error Domain=com.apple.CoreML Code=1 "Input image feature image does not match model description" UserInfo={NSLocalizedDescription=Input image feature image does not match model description, NSUnderlyingError=0x1c0643420 {Error Domain=com.apple.CoreML Code=1 "Image is not valid width 224, instead is 852" UserInfo={NSLocalizedDescription=Image is not valid width 224, instead is 852}}}

哦白修,忘記改大小了,找到 photoSetting重斑,加上寬高:

if !photoSettings.availablePreviewPhotoPixelFormatTypes.isEmpty {
    photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.availablePreviewPhotoPixelFormatTypes.first!,
        kCVPixelBufferWidthKey as String : NSNumber(value:224),
        kCVPixelBufferHeightKey as String : NSNumber(value:224)]
}

重新 Run兵睛,WTF,Man窥浪,居然又報(bào)同樣的錯(cuò)祖很,好吧,Google 一下漾脂,貌似寬高的屬性假颇,在 Swift 里面不生效,額符相。拆融。

沒(méi)辦法,那我們只能將 CVPixelBuffer 先轉(zhuǎn)換成 UIImage啊终,然后改下大小镜豹,再轉(zhuǎn)回 CVPixelBuffer,試試:

photoData = photo.fileDataRepresentation()
 
// Change Data to Image
guard let photoData = photoData else {
    return
}
let image = UIImage(data: photoData)
                
// Resize
let newWidth:CGFloat = 224.0
let newHeight:CGFloat = 224.0
UIGraphicsBeginImageContext(CGSize(width:newWidth, height:newHeight))
image?.draw(in:CGRect(x:0, y:0, width:newWidth, height:newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
 UIGraphicsEndImageContext()
                
guard let finalImage = newImage else {
    return
}
                
// Predicte
guard let Resnet50CategoryOutput = try? model.prediction(image:pixelBufferFromImage(image: finalImage)) else {
    fatalError("Unexpected runtime error.")
}

重新 Run蓝牲,OK趟脂,一切很完美。

最后例衍,為了用戶體驗(yàn)昔期,加上攝像頭流的暫停和重啟已卸,免得在識(shí)別的時(shí)候,攝像頭還一直在動(dòng)硼一,另外累澡,識(shí)別結(jié)果通過(guò)提醒框彈出來(lái),具體參考文末的源碼般贼。

開(kāi)始玩啦愧哟,找支油筆試一下:

iOS-CoreML-Pen

識(shí)別成,橡皮擦哼蛆,好吧蕊梧,其實(shí)是有點(diǎn)像。

再拿小綠植試試:

iOS-CoreML-FlowerPot

花瓶腮介,Are you kidding me ??

其實(shí)肥矢,效果還是蠻不錯(cuò)的。

剛好下午要去上海 CES Asia叠洗,一路拍過(guò)去玩甘改,想想都有點(diǎn)小激動(dòng)。

最后惕味,源碼奉上楼誓,想玩的同學(xué)直接下載編譯就行了,別忘了 Star~

看了又看:
深度學(xué)習(xí)是怎么識(shí)別人臉的?
300行代碼實(shí)現(xiàn)手寫漢字識(shí)別
如何在一周內(nèi)做一款拼音輸入法
iOS-簽名機(jī)制和證書原理
iOS-線程同步詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末名挥,一起剝皮案震驚了整個(gè)濱河市疟羹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禀倔,老刑警劉巖榄融,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異救湖,居然都是意外死亡愧杯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門鞋既,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)力九,“玉大人,你說(shuō)我怎么就攤上這事邑闺〉埃” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵陡舅,是天一觀的道長(zhǎng)抵乓。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么灾炭? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任茎芋,我火速辦了婚禮,結(jié)果婚禮上蜈出,老公的妹妹穿的比我還像新娘田弥。我一直安慰自己,他們只是感情好掏缎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布皱蹦。 她就那樣靜靜地躺著,像睡著了一般眷蜈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沈自,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天酌儒,我揣著相機(jī)與錄音,去河邊找鬼枯途。 笑死忌怎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酪夷。 我是一名探鬼主播榴啸,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晚岭!你這毒婦竟也來(lái)了鸥印?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坦报,失蹤者是張志新(化名)和其女友劉穎库说,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體片择,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜的,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了字管。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啰挪。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嘲叔,靈堂內(nèi)的尸體忽然破棺而出亡呵,到底是詐尸還是另有隱情,我是刑警寧澤借跪,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布政己,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏歇由。R本人自食惡果不足惜卵牍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沦泌。 院中可真熱鬧糊昙,春花似錦、人聲如沸谢谦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)回挽。三九已至没咙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間千劈,已是汗流浹背祭刚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墙牌,地道東北人涡驮。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喜滨,于是被迫代替她去往敵國(guó)和親捉捅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容