二层亿、使用Core ML加載.mlmodel模型文件

本文將介紹如何在具體的iOS項目中使用 Core ML模型桦卒。

主要步驟

一、準備一個Core ML模型

Core ML模型文件一般以.mlmodel作為后綴匿又,我們可以通過很多途徑獲得一個已經(jīng)訓練好的模型方灾,最直接的方式,就是從蘋果官網(wǎng)上直接下載碌更。

點開鏈接裕偿,我們選擇MobileNet下載,獲得一個MobileNet.mlmodel文件痛单。

我們可以看一下MobileNet的介紹:

MobileNets are based on a streamlined architecture that have depth-wise separable convolutions to build lightweight, deep neural networks. Detects the dominant objects present in an image from a set of 1000 categories such as trees, animals, food, vehicles, people, and more.

這是一個圖片分類的模型嘿棘,可以識別像樹木、動物旭绒、食物鸟妙、車輛、人物等挥吵。由此可見重父,我們需要事先準備一些包含這些物體的場景或者圖片。

二蔫劣、準備一個iOS項目

在開始之前坪郭,我們需要準備一個iOS項目,來獲取圖片脉幢,加載模型歪沃,最終完成圖片分類。作為一個iOS工程師嫌松,這部分將不再贅述沪曙,你可以直接從我的GitHub獲取一個已經(jīng)構(gòu)建好的項目:

git clone git@github.com:yangchenlarkin/CoreML.git

項目的首頁是一個UITableViewController,點擊第一個“物體識別”萎羔,即可進入到我們的Demo頁面ORViewController液走,代碼位于ObjectRecognition文件加下。
ORViewController可以通過后置攝像頭的拍攝來獲取圖片贾陷。

查看ORViewController最后的TODO部分缘眶,這里就是我們處理圖片的代碼了:

#pragma mark - predict

- (void)predict:(UIImage *)image {
    //TODO
}

三、導入.mlmodel模型文件

我們將第一步獲得的MobileNet.mlmodel文件髓废,拖拽到項目中巷懈,勾選Copy items if needed。在項目中選中MobileNet.mlmodel,讓我們來看一下這個模型。


MobileNet.mlmodel

可以看到該文件包含三個部分:

  • 模型描述;
  • 模型類MobileNet妻导;
    Xcode幫我們創(chuàng)建了一個叫MobileNet的類涌攻,我們點擊旁邊的箭頭可以查看它的頭文件欧引,我們也可以在代碼中直接引用、使用這個類恳谎。
  • 模型參數(shù)芝此;
    這里主要包含了模型輸入和輸出的描述,在MobileNet中惠爽,入?yún)⑹且粋€224x224的圖片癌蓖,出參有兩個,classLabelProbs一個字典婚肆,key是分類名租副,value是入?yún)D片命中該分類的概率,classLabel是入?yún)D片最有可能(概率值最大)的分類名较性。

從文件中可以看到用僧,我們需要將圖片轉(zhuǎn)化成224x224的尺寸大小(如果你有一定的機器學習基礎赞咙,就可以理解這里圖片大小固定的原因)责循。而模型會告訴我們這張圖片最有可能包含哪一樣物體。

四攀操、查看MobileNet類

在寫代碼之前院仿,我們來看一下MobileNet類。

點擊MobileNet.mlmodel文件中MobileNet旁邊的箭頭速和,我們可以查看該類的頭文件歹垫。暫時無視掉其他代碼,我們關注一下這個方法:

/**
    Make a prediction using the convenience interface
    @param image Input image to be classified as color (kCVPixelFormatType_32ARGB) image buffer, 224 pixels wide by 224 pixels high:
    @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
    @return the prediction as MobileNetOutput
*/
- (nullable MobileNetOutput *)predictionFromImage:(CVPixelBufferRef)image  error:(NSError * _Nullable * _Nullable)error;

  • 入?yún)ⅲ?br> 從方法聲明和注釋中颠放,我們發(fā)現(xiàn)排惨,我們的輸入圖片需要是CVPixelBufferRef 類型的,格式需要是kCVPixelFormatType_32ARGB格式的碰凶。

  • 返回值:
    該函數(shù)返回了一個MobileNetOutput類對象暮芭。我們跟進此類中去(其實也在這個文件里)。我們發(fā)現(xiàn)欲低,他包含兩個屬性:

/// Probability of each category as dictionary of strings to doubles
@property (readwrite, nonatomic, strong) NSDictionary<NSString *, NSNumber *> * classLabelProbs;

/// Most likely image category as string value
@property (readwrite, nonatomic, strong) NSString * classLabel;

這兩個屬性正好對應了MobileNet.mlmodel中的兩個輸出辕宏。

至此,我們腦海中大概有了一個使用模型的流程了:

1.將UIImage轉(zhuǎn)化成CVPixelBufferRef砾莱,圖片格式是kCVPixelFormatType_32ARGB匾效,圖片尺寸是224x224
2.創(chuàng)建模型對象,調(diào)用預測方法恤磷,獲得MobileNetOutput
3.從MobileNetOutputclassLabel屬性面哼,就是我們需要的分類名
4.如果業(yè)務需要,我們也可以從classLabelProbs屬性中扫步,獲得所有分類及其概率魔策。

五、添加代碼河胎,實現(xiàn)圖片分類

1.圖片縮放

首先我們需要對圖片做居中剪裁和縮放闯袒,將圖片的中間的正方形部分,縮放到224x224大小游岳。

我們定義一個縮放方法:


#pragma mark - predict

- (UIImage *)scaleImage:(UIImage *)image size:(CGFloat)size {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size, size), YES, 1);
    
    CGFloat x, y, w, h;
    CGFloat imageW = image.size.width;
    CGFloat imageH = image.size.height;
    if (imageW > imageH) {
        w = imageW / imageH * size;
        h = size;
        x = (size - w) / 2;
        y = 0;
    } else {
        h = imageH / imageW * size;
        w = size;
        y = (size - h) / 2;
        x = 0;
    }
    
    [image drawInRect:CGRectMake(x, y, w, h)];
    UIImage * scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

然后調(diào)用他:


#pragma mark - predict

- (UIImage *)scaleImage:(UIImage *)image size:(CGFloat)size {
//...
}

- (void)predict:(UIImage *)image {
    UIImage *scaledImage = [self scaleImage:image size:224];
    //TODO
}

2.格式轉(zhuǎn)換
直接上代碼吧:


#pragma mark - predict

- (UIImage *)scaleImage:(UIImage *)image size:(CGFloat)size {
//...
}

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
    NSDictionary *options = @{
                              (NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES,
                              (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
                              (NSString *)kCVPixelBufferIOSurfacePropertiesKey: [NSDictionary dictionary]
                              };
    CVPixelBufferRef pxbuffer = NULL;
    
    CGFloat frameWidth = CGImageGetWidth(image);
    CGFloat frameHeight = CGImageGetHeight(image);
    
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                          frameWidth,
                                          frameHeight,
                                          kCVPixelFormatType_32ARGB,
                                          (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
    
    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    NSParameterAssert(pxdata != NULL);
    
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    
    CGContextRef context = CGBitmapContextCreate(pxdata,
                                                 frameWidth,
                                                 frameHeight,
                                                 8,
                                                 CVPixelBufferGetBytesPerRow(pxbuffer),
                                                 rgbColorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
    NSParameterAssert(context);
    CGContextConcatCTM(context, CGAffineTransformIdentity);
    CGContextDrawImage(context, CGRectMake(0,
                                           0,
                                           frameWidth,
                                           frameHeight),
                       image);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    
    return pxbuffer;
}

- (void)predict:(UIImage *)image {
    UIImage *scaledImage = [self scaleImage:image size:224];
    CVPixelBufferRef buffer = [self pixelBufferFromCGImage:scaledImage.CGImage];
    //TODO
}

3.預測

我們需要生成MobileNet類對象政敢,需要在文件開始處引入MobileNet.h

#import "MobileNet.h"

然后添加代碼:


#pragma mark - predict

- (UIImage *)scaleImage:(UIImage *)image size:(CGFloat)size {
//...
}

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
//...
}


- (void)predict:(UIImage *)image {
    UIImage *scaledImage = [self scaleImage:image size:224];
    CVPixelBufferRef buffer = [self pixelBufferFromCGImage:scaledImage.CGImage];
    
    NSError *error = nil;
    
    MobileNet *model = [[MobileNet alloc] init];//模型的創(chuàng)建和初始化,可以放在self屬性中懶加載胚迫,這樣不用每次重新創(chuàng)建
    MobileNetOutput *output = [model predictionFromImage:buffer error:&error];
    if (error) {
        NSLog(@"%@", error);
        return;
    }
    
    //TODO
}

4.顯示預測

在ORViewController中有一個屬性:

@property (nonatomic, copy) NSArray <NSArray <NSString *> *> *array;

我們可以通過設置這個數(shù)組來控制一個tableview的顯示:


#pragma mark - predict

- (UIImage *)scaleImage:(UIImage *)image size:(CGFloat)size {
//...
}

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
//...
}


- (void)predict:(UIImage *)image {
    UIImage *scaledImage = [self scaleImage:image size:224];
    CVPixelBufferRef buffer = [self pixelBufferFromCGImage:scaledImage.CGImage];
    
    NSError *error = nil;
    
    MobileNet *model = [[MobileNet alloc] init];//模型的創(chuàng)建和初始化喷户,可以放在self屬性中懶加載,這樣不用每次重新創(chuàng)建
    MobileNetOutput *output = [model predictionFromImage:buffer error:&error];
    if (error) {
        NSLog(@"%@", error);
        return;
    }
    
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:output.classLabelProbs.count + 1];
    [result addObject:@[@"這張圖片可能包含:", output.classLabel]];
    [output.classLabelProbs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
        NSString *title = [NSString stringWithFormat:@"%@的概率:", key];
        [result addObject:@[title, obj.stringValue]];
    }];
    self.array = result;
}

5.測試

至此我們的代碼就算寫完了访锻,是不是很簡單褪尝!讓我們運行一下,到Google上搜索一些圖片看看結(jié)果吧:


小奶貓
飛機
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末期犬,一起剝皮案震驚了整個濱河市河哑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌龟虎,老刑警劉巖璃谨,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鲤妥,居然都是意外死亡佳吞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門旭斥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來容达,“玉大人,你說我怎么就攤上這事垂券』ㄑ危” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵菇爪,是天一觀的道長算芯。 經(jīng)常有香客問我,道長凳宙,這世上最難降的妖魔是什么熙揍? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮氏涩,結(jié)果婚禮上届囚,老公的妹妹穿的比我還像新娘有梆。我一直安慰自己,他們只是感情好意系,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布泥耀。 她就那樣靜靜地躺著,像睡著了一般蛔添。 火紅的嫁衣襯著肌膚如雪痰催。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天迎瞧,我揣著相機與錄音夸溶,去河邊找鬼。 笑死凶硅,一個胖子當著我的面吹牛缝裁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咏尝,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼压语,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了编检?” 一聲冷哼從身側(cè)響起胎食,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎允懂,沒想到半個月后厕怜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蕾总,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年粥航,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片生百。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡递雀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚀浆,到底是詐尸還是另有隱情缀程,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布市俊,位于F島的核電站杨凑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏摆昧。R本人自食惡果不足惜撩满,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伺帘,春花似錦昭躺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至礼殊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間针史,已是汗流浹背晶伦。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啄枕,地道東北人婚陪。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像频祝,于是被迫代替她去往敵國和親泌参。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348