本文將介紹如何在具體的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妻导;
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.從MobileNetOutput的classLabel屬性面哼,就是我們需要的分類名
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é)果吧: