iOS 机器学习(Core ML) -- (一)集成已有模型

一、机器学习

机器学习就是通过对数据进行分析,来改进优化算法。
机器学习有三个要数:数据、学习算法、模型。

数据:机器学习的样本。如在对某支股票的股价预测中,该股过去一段时间的涨跌价格就是数据。

算法:有线性回归、逻辑回归、以及深度学习中的卷积神经网络、循环神经网络等算法。我们如果只是在App中使用模型,不自己训练模型,可以不需要了解这些算法。

模型:即机器从样本数据中找出的规律,根据这个规律,用于对新数据的判断。

机器学习过程如下: 样本数据 --> 算法 --> 模型 预测数据 --> 模型 --> 预测结果

二、Core ML

Core ML是苹果公司于2017年推出的一种离线机器学习框架,其中ML是Machine Learning的缩写,即机器学习。App 可以使用 Core ML API 对用户数据进行预测。苹果为了保护用户数据的私密性和 App 的响应速度,机器学习模型被严格限定在用户设备上,无需任何网络连接。

Core ML 可以自动为模型生成可供调用的 API,其作用相当于机器学习模型与App之间的中间媒介,关系如下图所示:

Core ML.png

另外,可以使用 Xcode 内置的 Create ML App 来构建和训练模型。使用 Create ML 训练的模型是 Core ML 格式(文件后缀是 .mlmodel),并能直接在 App 中使用。

当然,使用 Core ML Tools 将其他格式的模型转换成 Core ML 格式,供App使用。

三、使用Core ML

1、获取模型

最简单的获取方式是在苹果官网下载,具体是在Model模块中。目前该模块下有以下模型:

FCRN-DepthPrediction:深度估计(根据一幅图像来预测深度)

MNIST:涂鸦分类 (对单个手写数字进行分类 (支持数字 0-9))

UpdatableDrawingClassifier:涂鸦分类( K-近邻算法(KNN))

MobileNetV2:图像分类

Resnet50:图像分类(残差神经网络)

SqueezeNet:图像分类 (小型深度神经网络)

DeeplabV3:图像分割

YOLOv3:对象检测 (对相机取景框内或图像中 80 种不同类型的对象进行定位和分类)

YOLOv3-Tiny: 对象检测 (实时)(对相机取景框内或图像中 80 种不同类型的对象进行定位和分类)

PoseNet:姿态估计

BERT-SQuAD:问答 (查找文本段落相关问题的答案)

当以上模型不能够满足需求的时候,就需要自己训练模型了,苹果提了转换器 Core ML Tools ,该转换器是基于Python实现的,可用它把训练出来的模型转为适配Core ML的模型。

2、导入模型(以Resnet50为例)

模型后缀名是mlpackage,直接将Resnet50.mlpackage拖入工程即可。打开模型,可以看到改模型的大小、支持版本、预测准确率、分类标签、作者、版本等信息。如下图:

打开模型.png

其中Preview可以在工程中预览预测结果,如添加一张金毛图片,其预测结果如下:
预测金毛.png

金毛的概率87%,拉布拉多的概率6%,网球的概率1%。识别还是比较准确的。

Predictions项则说明如何使用这个模型,如下图中输入Input是图片,大小224*224,输出Output是预测后各标签的概率,其中classLabel是概率最高的标签。

输入输出.jpg

3、项目中使用(以Resnet50为例)

import "Resnet50.h" 引入 Resnet50,按模型要求调用api即可。

核心代码如下:

Resnet50 *resnet50Model = [[Resnet50 alloc] init];
NSError *error = nil;
Resnet50Output *output = [resnet50Model predictionFromImage:imageRef error:&error];
self.resultLabel.text = [NSString stringWithFormat:@"预测结果:%@",output.classLabel];

预测结果如下:


预测结果.png

4、最后贴上完整代码

//
//  ViewController.m
//  CoreMLDemo
//
//  Created by 胡sir on 2023/2/18.
//

#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "Resnet50.h"

@interface ViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>
@property (nonatomic, strong) UIImageView *imageView;//预测的图片
@property (nonatomic, strong) UILabel *resultLabel;//预测结果
@property (nonatomic, strong) UIButton *startBtn;//按钮
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.resultLabel];
    [self.view addSubview:self.imageView];
    [self.view addSubview:self.startBtn];
}

#pragma mark -懒加载-
- (UILabel *)resultLabel {
    if (!_resultLabel) {
        _resultLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 40)];
        _resultLabel.textAlignment = NSTextAlignmentCenter;
        _resultLabel.textColor = [UIColor blackColor];
        _resultLabel.font = [UIFont systemFontOfSize:18];
    }
    return _resultLabel;
}

- (UIImageView *)imageView {
    if (!_imageView) {
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.resultLabel.frame), [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-240)];
    }
    return _imageView;
}

- (UIButton *)startBtn {
    if (!_startBtn) {
        _startBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        _startBtn.frame = CGRectMake(0, CGRectGetMaxY(self.imageView.frame), [UIScreen mainScreen].bounds.size.width, 100);
        [_startBtn setTitle:@"选择照片" forState:UIControlStateNormal];
        [_startBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_startBtn addTarget:self action:@selector(startBtnClick) forControlEvents:UIControlEventTouchUpInside];
    }
    return _startBtn;
}

- (void)startBtnClick {
    UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
    imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imagePickerController.delegate = self;
    imagePickerController.allowsEditing = YES;
//    imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
//    imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
    [self presentViewController:imagePickerController animated:YES completion:nil];
}

#pragma mark -UIImagePickerControllerDelegate-
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {

    CGSize thesize = CGSizeMake(224, 224);
    UIImage *theimage = [self image:info[UIImagePickerControllerEditedImage] scaleToSize:thesize];
    self.imageView.image = theimage;
    
    CVPixelBufferRef imageRef = [self pixelBufferFromCGImage:theimage.CGImage];
    Resnet50 *resnet50Model = [[Resnet50 alloc] init];
    NSError *error = nil;
    Resnet50Output *output = [resnet50Model predictionFromImage:imageRef
                                                          error:&error];
    if (error == nil) {
        self.resultLabel.text = [NSString stringWithFormat:@"预测结果:%@",output.classLabel];
    } else {
        NSLog(@"Error is %@", error.localizedDescription);
    }

    UIImagePickerController *imagePickerVC = picker;
    [imagePickerVC dismissViewControllerAnimated:YES completion:^{
        
    }];
}

#pragma mark -图片处理-
//image转PixelBuffer
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
    NSDictionary *options = @{
                              (NSString*)kCVPixelBufferCGImageCompatibilityKey : @YES,
                              (NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
                              };

    CVPixelBufferRef pxbuffer = NULL;
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, CGImageGetWidth(image),
                                          CGImageGetHeight(image), kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    if (status!=kCVReturnSuccess) {
        NSLog(@"Operation failed");
    }
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);

    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, CGImageGetWidth(image),
                                                 CGImageGetHeight(image), 8, 4*CGImageGetWidth(image), rgbColorSpace,
                                                 kCGImageAlphaNoneSkipFirst);
    NSParameterAssert(context);

    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGAffineTransform flipVertical = CGAffineTransformMake( 1, 0, 0, -1, 0, CGImageGetHeight(image) );
    CGContextConcatCTM(context, flipVertical);
    CGAffineTransform flipHorizontal = CGAffineTransformMake( -1.0, 0.0, 0.0, 1.0, CGImageGetWidth(image), 0.0 );
    CGContextConcatCTM(context, flipHorizontal);

    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                           CGImageGetHeight(image)), image);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    return pxbuffer;
}

//将一个UIImage缩放变换到指定Size
-(UIImage*)image:(UIImage *)image scaleToSize:(CGSize)size {
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容