先看看大约效果呢
Core ML简介
参考官方文书档案
CoreML简介
通过Core ML
我们得以将曾经磨练好的机器学习模型集成到App
中。
Core ML与App的关系
贰个磨炼好的模子是指,将机械学习算法应用到一组织练习练多少集中得出的结果。该模型依照新的输入数据开始展览结果的展望,举个例子,依照某地点历史房价实行磨练的模子,当给定卧室和卫生间的多寡就能够估摸房子的价钱。
Core ML
是特定领域框架和效能的根底。Core ML
扶助使用Vision
开始展览图片分析,Foundation
提供了自然语言处理(比如,使用NSLinguisticTagger类
),GameplayKit
提供了评估学习的决策树,Core ML
自笔者是创造在上层的初级原语(primitives
),基于如:Accelerate
、BNNS
以及Metal
和Performance Shaders
等。
Core ML架构
Core ML
对配备品质进行了优化,优化了内存和功耗。运维在当地设备上既保障了用户的心曲,又足以在一向不网络连接时保险应用的效益完全并能够对请求做出响应。
机器学习模型在盘算时,总括量往往都不小,单纯依赖CPU
算算很难满意总结必要,通过上海教室不难发现,整个架构中Accelerate
、BNNS
、Metal
和Performance Shaders
位于最底部,Core ML
直白采纳DSP
、GPU
等硬件加速总结和渲染,上层用户不再需求一贯操作硬件调用相关C函数,那也是Core ML
展开优化的一局地。在Core ML
之上,提供了Vision库
用来图像分析,Foundation库
提供了自然语言处理的成效,GameplayKit
应该是在嬉戏中央银行使的,那么些库封装了苹果提供的机器学习模型,提供上层接口供开发者使用。
Vision库
是二个高质量的图像和录制分析库,提供了包含人脸识别、特征检查和测试、场景分类等功用。本文也会举相关栗子。对于自然语言处理和GameplayKit
笔者没有读书,不做举例。
综上说述,Core ML
提供了高品质的本地化运维机器模型的成效,能够很方便的落到实处机械学习模型本地化运转,并提供了部分装进好的模子接口供上层使用。当中最要害的当然正是机械学习模型,Core ML
只支持mlmodel
格式的模型,但苹果提供了3个转换工具得以将Caffe
、Keras
等框架陶冶的机械学习模型转换为mlmodel
格式供应用利用,还有局部第贰方的工具得以将Tensorflow
、MXNet
转换为mlmodel
格式的模子,苹果官方也提供了一些mlmodel
格式的吃水学习模型,如VGG16
、GooLeNet
等用于ImageNet
实体识别效用的模型,具体可在官网Apple
Machine
Learning下载。
Vision使用
@interface VNCoreMLModel : NSObject
- (instancetype) init NS_UNAVAILABLE;
/*!
@brief Create a model container to be used with VNCoreMLRequest based on a Core ML model. This can fail if the model is not supported. Examples for a model that is not supported is a model that does not take an image as any of its inputs.
@param model The MLModel from CoreML to be used.
@param error Returns the error code and description, if the model is not supported.
*/
+ (nullable instancetype) modelForMLModel:(MLModel*)model error:(NSError**)error;
@end
在上面VNCoreMLModel
类中,大家能够观望其开首化方法之一一个modelForMLModel
,而init
是无用的,在modelForMLModel
中,有MLModel
那样四个目的的参数,而在Core ML
模型类中,我们也发现有那般三个属性,看来大家得以因而那几个关系将其关联起来。
@interface Resnet50 : NSObject
@property (readonly, nonatomic, nullable) MLModel * model;
在当前类继续往下翻,就能来看类VNCoreMLRequest
@interface VNCoreMLRequest : VNImageBasedRequest
/*!
@brief The model from CoreML wrapped in a VNCoreMLModel.
*/
@property (readonly, nonatomic, nonnull) VNCoreMLModel *model;
@property (nonatomic)VNImageCropAndScaleOption imageCropAndScaleOption;
/*!
@brief Create a new request with a model.
@param model The VNCoreMLModel to be used.
*/
- (instancetype) initWithModel:(VNCoreMLModel *)model;
/*!
@brief Create a new request with a model.
@param model The VNCoreMLModel to be used.
@param completionHandler The block that is invoked when the request has been performed.
*/
- (instancetype) initWithModel:(VNCoreMLModel *)model completionHandler:(nullable VNRequestCompletionHandler)completionHandler NS_DESIGNATED_INITIALIZER;
- (instancetype) init NS_UNAVAILABLE;
- (instancetype) initWithCompletionHandler:(nullable VNRequestCompletionHandler)completionHandler NS_UNAVAILABLE;
@end
在个中,我们看看方法initWithModel
和VNCoreMLModel
类相关联,于是就有了下边包车型客车代码
- (void)predictionWithResnet50WithImage:(CIImage * )image
{
//两种初始化方法均可
// Resnet50* resnet50 = [[Resnet50 alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Resnet50" ofType:@"mlmodelc"]] error:nil];
Resnet50* resnet50 = [[Resnet50 alloc] init];
NSError *error = nil;
//创建VNCoreMLModel
VNCoreMLModel *vnCoreMMModel = [VNCoreMLModel modelForMLModel:resnet50.model error:&error];
// 创建request
VNCoreMLRequest *request = [[VNCoreMLRequest alloc] initWithModel:vnCoreMMModel completionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
}];
}
到此地,好像还险些什么,是的,貌似我们的图纸并未关系上来,只可以去找寻资料,最终发现八个最根本的类,那正是VNImageRequestHandler
,在那几个类中,作者还发现三个不胜重要的主意
- (BOOL)performRequests:(NSArray<VNRequest *> *)requests error:(NSError **)error;
一时半刻间就将VNCoreMLRequest
类关联起来了,因为VNCoreMLRequest
最后还是延续VNRequest
,在连锁文书档案的援助下,最后有了上面包车型大巴代码
- (void)predictionWithResnet50WithImage:(CIImage * )image
{
//两种初始化方法均可
// Resnet50* resnet50 = [[Resnet50 alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Resnet50" ofType:@"mlmodelc"]] error:nil];
Resnet50* resnet50 = [[Resnet50 alloc] init];
NSError *error = nil;
//创建VNCoreMLModel
VNCoreMLModel *vnCoreMMModel = [VNCoreMLModel modelForMLModel:resnet50.model error:&error];
// 创建处理requestHandler
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCIImage:image options:@{}];
NSLog(@" 打印信息:%@",handler);
// 创建request
VNCoreMLRequest *request = [[VNCoreMLRequest alloc] initWithModel:vnCoreMMModel completionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
CGFloat confidence = 0.0f;
VNClassificationObservation *tempClassification = nil;
for (VNClassificationObservation *classification in request.results) {
if (classification.confidence > confidence) {
confidence = classification.confidence;
tempClassification = classification;
}
}
self.descriptionLable.text = [NSString stringWithFormat:@"识别结果:%@,匹配率:%.2f",tempClassification.identifier,tempClassification.confidence];
}];
// 发送识别请求
[handler performRequests:@[request] error:&error];
if (error) {
NSLog(@"%@",error.localizedDescription);
}
}
由此这几个点子,大家就足以绝不再去考虑图片的高低了,全部的处理和询问Vision
现已帮大家缓解了。
到那边甘休,还有几个疑问
- (instancetype)initWithCIImage:(CIImage *)image options:(NSDictionary<VNImageOption, id> *)options;
/*!
@brief initWithCIImage:options:orientation creates a VNImageRequestHandler to be used for performing requests against the image passed in as a CIImage.
@param image A CIImage containing the image to be used for performing the requests. The content of the image cannot be modified.
@param orientation The orientation of the image/buffer based on the EXIF specification. For details see kCGImagePropertyOrientation. The value has to be an integer from 1 to 8. This superceeds every other orientation information.
@param options A dictionary with options specifying auxilary information for the buffer/image like VNImageOptionCameraIntrinsics
@note: Request results may not be accurate in simulator due to CI's inability to render certain pixel formats in the simulator
*/
- (instancetype)initWithCIImage:(CIImage *)image orientation:(CGImagePropertyOrientation)orientation options:(NSDictionary<VNImageOption, id> *)options;
就是在VNImageRequestHandler
再有许多开始化函数,而且还有个别参数,一时半刻还没去研讨,后续钻探好了,再来补充。
上边照旧奉上demo,有啥错误,还望各位多多指教。
Core ML实战 – 实时捕获与识别
先是,使用官网提供的模型尝试一下,在上边的网站中,能够下载到实体识别相关的模型有MobileNet
、SqueezeNet
、ResNet50
、Inception V3
、VGG16
,本文以VGG16
为例实行教学,你也足以下载四个相比小的模型做不难的尝试。
将下载的模型mlmodel
文件拖入到XCode
工程中,单击该文件能够见到有关描述,如下图所示:
mlmodel文件讲述
在那一个描述中,Machine Learning Model
下能够见到模型的连锁描述音讯。模型文件拖入工程以后也会扭转模型相关的接口文件,单击Model Class
下的VGG16
即可跳转到VGG16
模型的接口文件中。Model Evaluation Parameters
则提供了模型的输入输出音讯,如上海体育地方,输入为224*224大小的三通道图片,输出有七个,classLabelProbs
输出每多个分拣的置信度字典,该VGG16
能够对一千个类型举行辨别,由此字典会有一千个key-value
键值对,classLabel
则输出置信度最高的归类的标签。
单击VGG16
去查占星关接口评释如下:
//VGG16模型的输入类
@interface VGG16Input : NSObject<MLFeatureProvider>
/*
创建输入类,需要传入一个CVPixelBufferRef类型的图像数据
类型需要为kCVPixelFormatType_32BGRA,大小为224*224像素
*/
@property (readwrite, nonatomic) CVPixelBufferRef image;
- (instancetype)init NS_UNAVAILABLE;
//直接使用该构造函数创建对象即可
- (instancetype)initWithImage:(CVPixelBufferRef)image;
@end
//VGG16模型的输出类
@interface VGG16Output : NSObject<MLFeatureProvider>
//前文讲述的1000分类的名称和置信度字典
@property (readwrite, nonatomic) NSDictionary<NSString *, NSNumber *> * classLabelProbs;
//前文讲述的置信度最高的类别名称
@property (readwrite, nonatomic) NSString * classLabel;
//一般不手动创建输出对象,通过模型对象识别时返回结果为输出对象
- (instancetype)init NS_UNAVAILABLE;
//在进行预测是,Core ML会调用该方法创建一个output对象返回给调用者
- (instancetype)initWithClassLabelProbs:(NSDictionary<NSString *, NSNumber *> *)classLabelProbs classLabel:(NSString *)classLabel;
@end
//VGG16模型接口
@interface VGG16 : NSObject
//MLModel即为Core ML抽象的模型对象
@property (readonly, nonatomic, nullable) MLModel * model;
//构造函数,可以直接使用默认构造函数,则默认加载工程文件中名称为VGG16的mlmodel模型文件
//也可以提供远程下载的功能,使用该构造函数来加载模型
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error;
/*
进行预测的方法,需要传入VGG16Input对象和一个NSError指针的指针
返回结果为VGG16Ouput对象,从返回的对象中即可获取到识别的结果
*/
- (nullable VGG16Output *)predictionFromFeatures:(VGG16Input *)input error:(NSError * _Nullable * _Nullable)error;
//直接通过CVPixelBufferRef进行预测,省去了创建input对象的步骤
- (nullable VGG16Output *)predictionFromImage:(CVPixelBufferRef)image error:(NSError * _Nullable * _Nullable)error;
@end
本条接口文件中只评释了五个类,VGG16Input
代表模型的输入对象、VGG16Output
表示模型的出口对象、VGG16
代表模型对象,其实对于其他mlmodel
格式的深浅学习模型,最后生成的接口文件皆以均等的,差异就在于输入输出的例外,所以,驾驭了三个模型的运用办法,其余模型都以通用的。
接下去看一下有血有肉的应用代码:
//模型拖入工程后,使用默认构造函数进行模型加载,就会去加载同名的VGG16.mlmodel文件
VGG16 *vgg16Model = [[VGG16 alloc] init];
//加载一个需要识别图片,一定是224*224大小的,否则不能识别
UIImage *image = [UIImage imageNamed:@"test.png"];
//将图片转换为CVPixelBufferRef格式的数据
CVPixelBufferRef imageBuffer = [self pixelBufferFromCGImage:[image CGImage]];
//使用转换后的图片数据构造模型输入对象
VGG16Input *input = [[VGG16Input alloc] initWithImage:imageBuffer];
NSError *error;
//使用VGG16模型进行图片的识别工作
VGG16Output *output = [vgg16Model predictionFromFeatures:input error:&error];
//根据error判断识别是否成功
if (error) {
NSLog(@"Prediction Error %@", error);
} else {
//识别成功,输出可能性最大的分类标签
NSLog(@"Label: %@", output.classLabel);
}
//由于在转换UIImage到CVPixelBufferRef时,手动开辟了一个空间,因此使用完成后需要及时释放
CVPixelBufferRelease(imageBuffer);
//CGImageRef转换CVPiexlBufferRef格式数据
- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image {
NSDictionary *options = @{
(NSString*)kCVPixelBufferCGImageCompatibilityKey : @YES,
(NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
};
CVPixelBufferRef pxbuffer = NULL;
/*
注意需要使用kCVPixelFormatType_32BGRA的格式
深度学习常用OpenCV对图片进行处理,OpenCV使用的不是RGBA而是BGRA
这里使用CVPixelBufferCreate手动开辟了一个空间,因此使用完成后一定要释放该空间
*/
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, CGImageGetWidth(image),
CGImageGetHeight(image), kCVPixelFormatType_32BGRA, (__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;
}
编写翻译运维之后就能够看出输出结果啦,对于指标识别那样的粗略难点的话,输入为一张图片,输出为1个分拣结果,全部的模子大约都是这么的拍卖步骤。首先获得要甄其余图纸,创建模型对象,制造模型输入对象,通过模型对象举行鉴定分别来获得模型输出对象,从出口对象获得结果。
对于官网提供的别样目的识别,Resnet50
、GoogLeNet
等,不再举例了,进程都以相同的,读者可自动实验。
接下去做一些妙趣横生的尝尝,通过手提式有线电话机水墨画头实时取得拍戏的数据,然后去实时检查和测试对象并交由分类结果。
首先须求做肯定的限量,输入图片必要是224*224大大小小的,通过录像头拿走的图像数据是1080*一九二零的,假如直白转换为224*224会有拉伸,影响识别结果,所以,小编接纳的章程是取得中间区域部分的纺锤形图像,然后转换为指标大小。
代码如下:
//定义一个深度学习模型执行的block,方便在一个应用内,调用不同的模型
typedef void(^machineLearningExecuteBlock)(CVPixelBufferRef imageBuffer);
//实时目标检测视图类,需要实现一个协议用于获取摄像头的输出数据
@interface RealTimeDetectionViewController() <AVCaptureVideoDataOutputSampleBufferDelegate>
//当前获取到的设备
@property (nonatomic, strong) AVCaptureDevice *device;
/*
设备的输入,表示摄像头或麦克风这样的实际物理硬件
通过AVCaptureDevice对象创建,可以控制实际的物理硬件设备
*/
@property (nonatomic, strong) AVCaptureDeviceInput *input;
//视频输出,还有音频输出等类型
@property (nonatomic, strong) AVCaptureVideoDataOutput *output;
//设备连接,用于将session会话获取的数据与output输出数据,可以同时捕获视频和音频数据
@property (nonatomic, strong) AVCaptureConnection *videoConnection;
/*
捕捉设备会话,从实际的硬件设备中获取数据流,可以从摄像头或麦克风中获取
将数据流输出到一个或数个目的地,对于图像可以预设捕捉图片的大小质量等
*/
@property (nonatomic, strong) AVCaptureSession *session;
//设备预览layer,对于图像来说,摄像头拍摄到的图像数据直接展示在该layer上
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview;
//感兴趣的区域,即将摄像头上该区域的图像捕获去进行识别
@property (nonatomic, assign) CGRect interestRegionRect;
//目标图像的大小,针对不同模型,有不同的输入图像大小
@property (nonatomic, assign) CGSize targetSize;
//一个框,类似于扫描二维码的,提示在这个框内的图像会被用于实时检测
@property (nonatomic, strong) UIImageView *interestRegionImageView;
//session传递数据是阻塞的,使用单独的串行队列处理
@property (nonatomic) dispatch_queue_t videoCaptureQueue;
//显示识别结果的label
@property (nonatomic, strong) UILabel *titleLabel;
//退出button
@property (nonatomic, strong) UIButton *exitButton;
//切换前后摄像头button
@property (nonatomic, strong) UIButton *reverseCameraButton;
//机器学习模型识别block
@property (nonatomic, copy) machineLearningExecuteBlock executeBlock;
@end
地点的代码应用AVFoundation
框架来落到实处摄像数据的破获,注释很详细,不再赘言了。
/**
初始化设备、界面函数
@param interestRegion 感兴趣区域,如果为CGRectZero不创建提示imageView
@param targetSize 目标图像大小,如果为CGSizeZero不转换图像大小
@param executeBlock 机器学习模型执行block
*/
- (void)setUpWithInterestRegion:(CGRect)interestRegion targetSize:(CGSize)targetSize machineLearningExecuteBlock:(machineLearningExecuteBlock)executeBlock {
self.view.backgroundColor = [UIColor whiteColor];
//创建一个串行队列,用于处理session获取到的数据,即执行回调函数
self.videoCaptureQueue = dispatch_queue_create("VideoCaptureQueue", DISPATCH_QUEUE_SERIAL);
//获取一个默认摄像头设备,默认使用后置摄像头
//self.device = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];
//指定获取前置或后置摄像头
self.device = [self cameraWithPosition:AVCaptureDevicePositionFront];
//设置摄像头自动对焦
if (self.device.isFocusPointOfInterestSupported && [self.device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
[self.device lockForConfiguration:nil];
[self.device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
[self.device unlockForConfiguration];
}
//创建一个捕获输入
self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
//创建捕获输出
self.output = [[AVCaptureVideoDataOutput alloc] init];
//设置输出数据的代理为self,代理函数执行队列为创建的串行队列
[self.output setSampleBufferDelegate:self queue:self.videoCaptureQueue];
//创建一个字典,用于限制输出数据类型,输出数据的格式为kCVPixelFormatType_32BGRA
NSDictionary *outputSettings = @{(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]};
//限制输出数据类型
[self.output setVideoSettings:outputSettings];
//创建一个session会话
self.session = [[AVCaptureSession alloc] init];
//设置输出数据为高质量,输出图像大小为1080*1920
[self.session setSessionPreset:AVCaptureSessionPresetHigh];
//将input、output添加进session会话进行管理
if ([self.session canAddInput:self.input])
[self.session addInput:self.input];
if ([self.session canAddOutput:self.output])
[self.session addOutput:self.output];
//创建一个connection
self.videoConnection = [self.output connectionWithMediaType:AVMediaTypeVideo];
self.videoConnection.enabled = NO;
//设置拍摄图像是竖屏,1080*1920,否则默认会旋转
[self.videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
//创建一个预览layer
self.preview = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
self.preview.videoGravity = AVLayerVideoGravityResize;
//设置这个预览layer为全屏
self.preview.frame = self.view.frame;
self.preview.contentsScale = [[UIScreen mainScreen] scale];
//将预览layer插入到view.layer的第一个,作为子layer
[self.view.layer insertSublayer:self.preview atIndex:0];
//判断是否有感兴趣的区域,如果有就创建一个imageView防一个框在该区域中,用于提示用户
if (!CGRectEqualToRect(interestRegion, CGRectZero)) {
self.interestRegionImageView = [[UIImageView alloc] initWithFrame:interestRegion];
self.interestRegionImageView.image = [UIImage imageNamed:@"InterestRegionBG"];
[self.view addSubview:self.interestRegionImageView];
self.interestRegionRect = interestRegion;
}
//创建显示结果的label
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50, self.view.frame.size.width, 50)];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.textColor = [UIColor redColor];
[self.view addSubview:self.titleLabel];
//创建退出button
self.exitButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.exitButton.titleLabel.textColor = [UIColor redColor];
[self.exitButton setTitle:@"Exit" forState:UIControlStateNormal];
self.exitButton.frame = CGRectMake(20, 20, 80, 40);
[self.exitButton addTarget:self action:@selector(exitButtonClickedHandler) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.exitButton];
//创建摄像头翻转butotn
self.reverseCameraButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.reverseCameraButton.titleLabel.textColor = [UIColor redColor];
[self.reverseCameraButton setTitle:@"ReverseCamera" forState:UIControlStateNormal];
self.reverseCameraButton.frame = CGRectMake(self.view.frame.size.width - 20 - 200, 20, 200, 40);
[self.reverseCameraButton addTarget:self action:@selector(reverseCameraButtonClickedHandler) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.reverseCameraButton];
//记录targetsize和block
self.targetSize = targetSize;
self.executeBlock = executeBlock;
//启动session,打开摄像头,开始收数据了
[self.session startRunning];
self.videoConnection.enabled = YES;
}
/**
初始化函数
@return return value description
*/
- (instancetype)initWithVGG16 {
if (self = [super init]) {
//加载本地的VGG16模型
VGG16 *vgg16Model = [[VGG16 alloc] init];
//获取一个weakSelf,在block中使用,防止引用循环
__weak typeof(self) weakSelf = self;
/*
调用setUpWithInterestRegion:targetSize:machineLearningExecuteBlock函数进行初始化操作
感兴趣区域放在屏幕正中央,大小为224*224,目标大小也为224*224
*/
[self setUpWithInterestRegion:CGRectMake((self.view.frame.size.width - 224) / 2.0, (self.view.frame.size.height - 224) / 2.0, 224, 224) targetSize:CGSizeMake(224, 224) machineLearningExecuteBlock:^(CVPixelBufferRef imageBuffer) {
//使用block的参数构造VGG16的输入对象
VGG16Input *input = [[VGG16Input alloc] initWithImage:imageBuffer];
NSError *error;
//使用VGG16模型识别imageBuffer获取输出数据
VGG16Output *output = [vgg16Model predictionFromFeatures:input error:&error];
//获取一个strong的self
__strong typeof(weakSelf) strongSelf = weakSelf;
//如果识别失败,在主队列即主线程中修改titleLbale
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.titleLabel.text = @"Error!!!";
});
} else {
//识别成功,在主队列即主线程中修改titleLabel
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.titleLabel.text = output.classLabel;
});
}
}];
}
return self;
}
地方的八个函数正是有血有肉的开始化设备和施行机器学习模型识其他代码,能够看看识别的代码照旧和上个栗子一样简洁明了。
接下去看一下AVFoundation
的代办函数,怎样将摄像数据经过一多重转换交给executeBlock
做识别。
/**
AVCaptureVideoDataOutputSampleBufferDelegate代理方法,摄像头获取到数据后就会回调该方法
默认是30FPS,每秒获取30张图像
@param output output description
@param sampleBuffer 摄像头获取到的图像数据格式
@param connection connection description
*/
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
//需要将CMSampleBufferRef转换为CVPixelBufferRef才能交给机器学习模型识别
CVPixelBufferRef buffer;
//首先判断是否需要将图像转换为目标大小
if (!CGSizeEqualToSize(self.targetSize, CGSizeZero)) {
//将CMSampleBufferRef转换为UIImage类型的对象
UIImage *image = [self convertImage:sampleBuffer];
//截图,获取感兴趣区域的图像,然后转换为目标大小
image = [self getSubImage:self.interestRegionRect image:image targetSize:self.targetSize];
//将CGImageRef图像转换为CVPixelBufferRef,使用CVPixelBufferCreate手动开辟的空间,使用完成后需要释放
buffer = [self pixelBufferFromCGImage:[image CGImage]];
} else {
//不需要转化,则直接将CMSampleBufferRef转换为CVPixelBufferRef类型
//这个函数虽然是叫get ImageBuffer,其实CVPixelBufferRef是CVImageBufferRef的别称
//不是手动开辟的空间,随着弹栈自动释放内存
buffer = CMSampleBufferGetImageBuffer(sampleBuffer);
}
//如果executeBlock不为空,传递CVPixelBufferRef数据,执行识别
if (self.executeBlock) {
self.executeBlock(buffer);
}
//自己创建的buffer自己清理,不是自己创建的交由系统清理
if (!CGSizeEqualToSize(self.targetSize, CGSizeZero)) {
CVPixelBufferRelease(buffer);
}
}
/**
截图函数
@param rect 感兴趣区域
@param image 要截的图
@param targetSize 目标大小
@return 截过的图片
*/
- (UIImage*)getSubImage:(CGRect)rect image:(UIImage*)image targetSize:(CGSize)targetSize {
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
//因为输出图像是1080*1920,屏幕大小比这个小,所以需要根据比例将感兴趣区域换算到1080*1920大小的图像上
CGRect imageRegion = CGRectMake((rect.origin.x / screenWidth) * image.size.width, (rect.origin.y / screenHeight) * image.size.height, (rect.size.width / screenWidth) * image.size.width, (rect.size.height / screenHeight) * image.size.height);
//使用C函数直接截取感兴趣区域的图像
CGImageRef imageRef = image.CGImage;
CGImageRef imagePartRef = CGImageCreateWithImageInRect(imageRef, imageRegion);
//获取到截取的UIImage对象
UIImage *cropImage = [UIImage imageWithCGImage:imagePartRef];
//释放创建的CGImageRef数据
CGImageRelease(imagePartRef);
//开启一个绘制图像的上下文,设置大小为目标大小
UIGraphicsBeginImageContext(targetSize);
//将前面裁剪的感兴趣的图像绘制在目标大小上
[cropImage drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
//获取绘制完成的UIImage对象
UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext();
//释放内存
UIGraphicsEndImageContext();
return resImage;
}
/**
官方给的转换代码
@param sampleBuffer sampleBuffer description
@return return value description
*/
- (UIImage*)convertImage:(CMSampleBufferRef)sampleBuffer {
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
//UIImage *image = [UIImage imageWithCGImage:quartzImage];
UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0f orientation:UIImageOrientationUp];
// Release the Quartz image
CGImageRelease(quartzImage);
return image;
}
地点的代码就落到实处了实时的检查和测试,代理函数会以30帧的速率执行,但有时数据来了,前2个分辨还没竣事,这一帧就会被丢掉,所以实时的检查和测试对纵深学习模型和设备质量的需要很高。代码很简短,整个流程就是从获得到的图像依据比例截取感兴趣区域后再更换为目的大小,然后交由深度学习模型去辨别后显得结果,注释很详细,不再讲解了。
下边是局地扭转录制头、对焦之类的援救函数
#pragma mark touchsbegin
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint point = [touch locationInView:self.view];
//触摸屏幕,实现对焦
[self focusOnPoint:point];
}
}
#pragma mark avfoundation function
/**
根据deviceDiscoverySession类型,获取前置或后置摄像头
@param position position description
@return return value description
*/
- (AVCaptureDevice*)cameraWithPosition:(AVCaptureDevicePosition)position {
//iOS10以后使用该类方法获取想要的设备
AVCaptureDeviceDiscoverySession *deviceDiscoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:position];
//遍历获取到的所有设备,返回需要的类型
for (AVCaptureDevice* device in deviceDiscoverySession.devices) {
if(device.position == position)
return device;
}
return nil;
}
/**
手动对焦
@param point 需要对焦的区域点
*/
- (void)focusOnPoint:(CGPoint)point {
if ([self.device isFocusPointOfInterestSupported] && [self.device lockForConfiguration:nil]) {
self.device.focusPointOfInterest = point;
[self.device unlockForConfiguration];
}
}
#pragma mark - UIButton target-action handler
- (void)exitButtonClickedHandler {
[self dismissViewControllerAnimated:YES completion:nil];
}
/**
前后摄像头翻转
*/
- (void)reverseCameraButtonClickedHandler {
//创建一个新的设备
AVCaptureDevice *newCamera;
//根据当前设备类型,获取翻转后的摄像头设备
if (self.device.position == AVCaptureDevicePositionBack) {
newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
} else {
newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
}
//根据设备创建一个新的input
AVCaptureDeviceInput *newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
self.device = newCamera;
//会话开启一个配置阶段
[self.session beginConfiguration];
//删除旧设备input
[self.session removeInput:self.input];
//添加新设备input
[self.session addInput:newInput];
self.input = newInput;
//关闭connection,重新创建一个,否则切换摄像头时输出的图片又默认旋转了
self.videoConnection.enabled = NO;
self.videoConnection = [self.output connectionWithMediaType:AVMediaTypeVideo];
self.videoConnection.enabled = NO;
[self.videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
self.videoConnection.enabled = YES;
//提交配置,自动更新
[self.session commitConfiguration];
}
拥有的谜底检测代码就完了了,读者能够编制多少个initWithVGG16
如此那般的构造函数,通用整个代码实行模型切换。上面是在作者的装备上运营的结果:
运转结果
Core ML 和 Vision使用
在应用从前,你无法不要确认保障你的环境是在xcode 9.0
+
iOS 11
,然后你能够去官网下载Core ML
模型,近来早已有6种模型了,分别如下
1.png
2.png
3.png
4.png
从其介绍大家得以看出分其他效果
MobileNet
:马虎是从一组1000个门类中检查和测试出图像中的占主导地位的实体,如树、动物、食物、车辆、人等等。
SqueezeNet
:同上
Places205-GoogLeNet
:大意是从20五个连串中检查和测试到图像的场所,如飞机场终端、卧室、森林、海岸等。
ResNet50
:大意是从一组一千个品种中检查和测试出图像中的占主导地位的物体,如树、动物、食品、车辆、人等等
Inception v3
:同上
VGG16
:同上
本来那都以苹果提供的模型,假诺您有谈得来的模子的话,能够由此工具将其更换,参照文书档案
在询问上边的模子功用后,大家得以接纳性的对其开始展览下载,近来本身那里下载了三种模型
model.png
将下载好的模型,间接拖入工程中,那里要求专注的难点是,要求检查下
check.png
其一岗位是或不是有该模型,笔者不驾驭是或不是本身那个xcode
版本的bug
,当自个儿拖入的时候,后边并不曾,这么些时候就供给手动进行添加一遍,在那事后,大家还亟需检查下模型类是还是不是变动,点击你供给用的模型,然后查看下边地点是或不是有箭头
modelOk.png
当以此地方箭头生成好后,我们就能够展开代码的编辑撰写了
Core ML简介及实时目的检查和测试,Caffe、Tensorflow与Core ML模型转换、Vision库的利用
转发请申明出处 https://www.jianshu.com/p/fccf318a76a2
本篇小说首先会不难介绍iOS 11
推出的Core ML
机器学习框架,接着会以实际的早已陶冶好的Caffe
、Tensorflow
模型为例,讲解coremltools
改换工具的运用,以及哪些在iOS
端运维相关模型。
于今是人造智能元年,随着深度学习的炽热,人工智能又二遍出现在万众视野中,对于客户端开发人士来说,大家也应该踏入这些小圈子拥有精通,将机械学习与观念App
重组,开发出更“懂”用户的施用。Google
的Tensorflow
曾经扶助在Android
上运行,苹果在iOS8
推出的Metal
能够用来访问GPU
,使用Metal
就足以兑现机械学习的本地化运转,但上学费用太高,在iOS11
中生产的Core ML
使得机器学习本地化运维变得更为有益。
能够预言的是,本地化模型必然是发展趋势,对于实时性较高的选取,如:目的检查和测试、自然现象文本识别与一定、实时翻译等,若是经过互联网传输到后台分析,网络延迟就足足让用户屏弃那一个App
了,比如微信的扫一扫中有翻译的效应,要求经过后台分析结果,所以甄其余进程一点也不快,实用性不强,有道词典完全落实了离线识别和翻译的法力。本地化模型也是对用户隐衷的很好维护。
我水平有限,对于价值观机器学习方法领会不多,对于深度学习只在图像识别、指标检查和测试、自然现象文本定位与识别相关领域有着涉猎,所以本文的关键性在于本地化运维深度学习模型,局限于实时图片识别,本文栗子包涵:VGG16
、Resnet
、AlexNet
,以及部分在Caffe Model Zoo
上当众的幽默的模型。对于语音语义相关领域尚未讨论,由此,本文的栗子均为图像检测、目的识别相关。
本文也不会讲课深度学习的连带内容,小编还并未力量将有关内容讲的很透彻,想要深刻到各类模型互联网中,直接看散文是最好的选料。
coreml1.gif
Tensorflow模型的变换
Tensorflow
用的愈加多了,所以也亟需精晓一下转换方法,coremltools
权且还不帮助Tensorflow
的更换,但苹果官方推荐使用tfcoreml
开展转换,说实话,用起来没有转caffe
的那么便宜。
安装tfcoreml
pip install tfcoreml
现实应用可参考github
上的栗子:tfcoreml
Github
能够下载Tensorflow
教练好的Inception/Resnet
v2模型,下载完结后含有八个frozen.pb
文件和imagenet_slim_labels.txt
的ImageNet
一千分拣的竹签文件,接着使用如下代码即可完毕更换:
tf_model_dir = '/Users/chenyoake/Desktop/MLModel/inception_resnet'
tf_model_path = os.path.join(tf_model_dir, 'inception_resnet_v2_2016_08_30_frozen.pb')
mlmodel_path = os.path.join(tf_model_dir, 'InceptionResnetV2.mlmodel')
mlmodel = tf_converter.convert(
tf_model_path = tf_model_path,
mlmodel_path = mlmodel_path,
output_feature_names = ['InceptionResnetV2/Logits/Predictions:0'],
input_name_shape_dict = {'input:0':[1,299,299,3]},
image_input_names = ['input:0'],
red_bias = -1,
green_bias = -1,
blue_bias = -1,
image_scale = 2.0/255.0,
class_labels = '/Users/chenyoake/Desktop/MLModel/inception_resnet/imagenet_slim_labels.txt')
转移时索要传入互连网的输入和输出层名称,钦命输入数据的维度等音讯,所以须要开发者对有关网络布局具有驾驭,最好能查看源码。
更换完毕后的应用和VGG16
的板栗一样,不再赘述了。
教之道 贵以专 昔孟母 择邻处 子不学 断机杼
备注
鉴于我水平有限,难免出现漏洞,如有毛病还请不吝赐教。
怎么是机械学习?
在Core ML
出现从前,机器学习应该照旧比较难学的,可是这一涌出,直接大大下跌了学习的门槛,可知苹果在那上边花的活力仍然广大。那么机器学习到底是怎么样啊?简单的讲,正是用大批量的数据去收集物体的性状特征,将其装入模型,当大家用的时候,能够透过查询模型,来快速差距出当下实体属于怎么类,有何特色等等。而Core ML
实质上做的作业正是应用事先操练好的模型,在动用时,对相关模块实行预测,最终回到结果,那种在地点开始展览展望的点子能够不依靠互连网,也足以减低处理时间。能够如此说,Core ML
让我们更便于在
App
中采用操练过的模子,而Vision
让大家轻松访问苹果的模子,用于面部检查和测试、面部特征点、文字、矩形、条形码和物体。
Vision库的行使
在篇章的最开端,大家上课了Vision库
在Core ML
的上层,所以本质上,Vision库
是包裹了一部分机械学习模型,并提供了便于使用的上层接口。包涵人脸识别、人脸特征检查和测试、目的识别、指标追踪等。但经过自家的尝试,对于动态实时识别就像是并不是很适量,像指标追踪,只好追踪物体,作者尝试追踪人脸失利了,但物体追踪效果也不是很好,人脸识其余准确率比较高。
Vision库
是基于Core ML
的,而mlmodel
的模型是Core ML
支持的,所以Vision库
也得以推行mlmodel
的机械学习模型,但自个儿在试验时其实用起来没有一贯动用mlmodel
接口文件方便,但是它提供了3个架空,能够很自在的切换模型,直接运用mlmodel
接口则不负有那样的能力,因为每3个mlmodel
都以直接接轨自NSObject
的。
Vision库
法定文书档案地址: Vision Apple
Document
首先以GoogLeNetPlaces
模型为例,简要介绍下Vision
什么实施mlmodel
模型:
- (void)classification {
//加载一个mlmodel
GoogLeNetPlaces *model = [[GoogLeNetPlaces alloc] init];
NSError *error;
//通过mlmodel模型创建一个Vision能够使用的VNCoreMLModel
VNCoreMLModel *visionModel = [VNCoreMLModel modelForMLModel:model.model error:&error];
//根据转换后的模型创建一个机器学习模型识别的request
VNCoreMLRequest *request = [[VNCoreMLRequest alloc] initWithModel: visionModel completionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
//机器学习模型运行完成后的回调块,在request中可以查看到相关信息
CGFloat confidence = 0.f;
VNClassificationObservation *tempClassification = nil;
//GoogLeNetPlaces输出多个结果,遍历查找置信度最高的
for (VNClassificationObservation *classification in request.results) {
if (classification.confidence > confidence) {
confidence = classification.confidence;
tempClassification = classification;
}
}
//输出结果
NSLog(@"Classification Value %@ %@ %lf", tempClassification, tempClassification.identifier, tempClassification.confidence);
}];
//创建一个请求的handler
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCGImage:[[UIImage imageNamed:@"a.jpeg"] CGImage] options:nil];
//执行请求
[handler performRequests:@[request] error:&error];
if (error) {
NSLog(@"ERROR CLASSIFICATION");
}
}
地点的代码相比简单,整个工艺流程就是先获得多个mlmodel
然后转换为Vision
识别的VNCoreMLModel
,接着创设二个request
编辑运算完成后的回调块,然后创立二个requestHandler
用于传递输入数据并推行运算。能够看来Vision库
提供了三个华而不实,各样mlmodel
都足以转移为VNCoreMLModel
,那样的话就能够依照要求很方便的转移模型,还有一些便是,它的输入是一张图纸,并从未供给图片的尺寸,所以在中间Vision
帮大家处理的图片大小的适配难点,就不须要手动转换了。具体选哪些看个人喜好了,笔者认为直接使用mlmodel
接口更有利于。
接下去举2个Vision库
进展人脸检查和测试的栗子:
- (instancetype)initWithFaceDetection {
if (self = [super init]) {
//Vision人脸检测的输入图片大小没有限制,所以可以全屏检测
[self setUpWithInterestRegion:CGRectZero targetSize:CGSizeZero machineLearningExecuteBlock:^(CVPixelBufferRef imageBuffer) {
/*
首先创建一个人脸检测的请求Requst,通过名称可以知道
这是一个人脸矩形框检测的请求,返回值是一个bounding box bbox
*/
VNDetectFaceRectanglesRequest *request = [[VNDetectFaceRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
//遍历计算结果
for (VNFaceObservation *faceObservation in request.results) {
//拿到bbox
CGRect boundingBox = faceObservation.boundingBox;
//获取图像大小
CGSize imageSize = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer));
//bbox给的是在图片中的比例,所以需要自己转换
CGFloat width = boundingBox.size.width * imageSize.width;
CGFloat height = boundingBox.size.height * imageSize.height;
CGFloat x = boundingBox.origin.x * imageSize.width;
CGFloat y = imageSize.height - (boundingBox.origin.y * imageSize.height) - height;
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
//在屏幕上计算真实的位置
CGRect faceRegion = CGRectMake((x / imageSize.width) * screenWidth, (y / imageSize.height) * screenHeight, (width / imageSize.width) * screenWidth, (height / imageSize.height) * screenHeight);
NSLog(@"FACE REGION %lf %lf %lf %lf %@", faceRegion.origin.x, faceRegion.origin.y, faceRegion.size.width, faceRegion.size.height, faceObservation.uuid);
}];
//使用CVPixelBufferRef创建一个handler
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCVPixelBuffer:imageBuffer options:@{}];
NSError *error;
//执行请求
[handler performRequests:@[request] error:&error];
if (error) {
NSLog(@"ERRROROROROR %@", error);
}
}];
}
return self;
}
下边包车型客车代码也很简单,使用方法比前一个板栗还简要,只需求创建request
和handlerRequest
然后实施请求就好了,由于面部检查和测试非常的慢,大约100ms就能做贰回,所以就从未有过打框了,打框的效果不是很好,有趣味的读者可以自动完成。
读者还足以查看VNDetectFaceLandmarksRequest
的接口,该接口能够检查和测试到人脸特征,包罗眼睛、眉毛、鼻子、嘴巴和脸的概貌。就不再举例了,使用方式是一样的。
最终,举一个对象追踪的栗子:
/**
目标追踪
@return return value description
*/
- (instancetype)initWithObjectTracking {
if (self = [super init]) {
/*
@property (nonatomic, strong) NSMutableDictionary<NSString*, VNDetectedObjectObservation*> *trackingObservationDict;
@property (nonatomic, strong) NSMutableDictionary<NSString*, CALayer*> *trackingBBoxDict;
*/
//每一个检测的结果observation都有一个uuid用于区分,构造一个字典,用于记录要追踪的目标observation
self.trackingObservationDict = [[NSMutableDictionary alloc] init];
//要追踪目标的bbox的layer,手动画上去的
self.trackingBBoxDict = [[NSMutableDictionary alloc] init];
//weak一下防止引用循环
__weak typeof(self) weakSelf = self;
//不需要限制输入大小
[self setUpWithInterestRegion:CGRectZero targetSize:CGSizeZero machineLearningExecuteBlock:^(CVPixelBufferRef imageBuffer) {
//strong一下
__strong typeof(weakSelf) strongSelf = weakSelf;
//首先需要查看要追踪的observation是否为空,如果为空,就需要先去找一个要追踪的目标
if (strongSelf.trackingObservationDict.count == 0) {
//查找初始要追踪的目标
[strongSelf detectTrackingFace:imageBuffer];
return ;
}
//每一个目标的observation进行追踪都需要一个单独的request,创建一个集合来保存
NSMutableArray<VNTrackObjectRequest*> *trackingRequestArray = [[NSMutableArray alloc] init];
//遍历要追踪目标observation的字典
for (NSString *uuid in strongSelf.trackingObservationDict.allKeys) {
//根据uuid获取到这个observation
VNDetectedObjectObservation *faceObservation = strongSelf.trackingObservationDict[uuid];
//创建一个追踪目标的请求,需要传入要追踪目标的observation,内部应该有一个反馈的操作
VNTrackObjectRequest *trackingRequest = [[VNTrackObjectRequest alloc] initWithDetectedObjectObservation:faceObservation completionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
//判断是否有错,追踪目标的结果数量是不是大于0
if (error == nil && request.results.count > 0) {
//获取追踪后的第一个observation
VNFaceObservation *observation = request.results.firstObject;
//更新追踪observation的字典
[strongSelf.trackingObservationDict setObject:observation forKey:uuid];
//如果置信度小于0.5就抛弃掉
if (observation.confidence < 0.5) {
[strongSelf.trackingObservationDict removeObjectForKey:uuid];
dispatch_async(dispatch_get_main_queue(), ^{
CALayer *layer = strongSelf.trackingBBoxDict[uuid];
[layer removeFromSuperlayer];
[strongSelf.trackingBBoxDict removeAllObjects];
});
return ;
}
//置信度满足要求,获取bbox
CGRect boundingBox = observation.boundingBox;
//一系列换算
CGSize imageSize = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer));
CGFloat width = boundingBox.size.width * imageSize.width;
CGFloat height = boundingBox.size.height * imageSize.height;
CGFloat x = boundingBox.origin.x * imageSize.width;
CGFloat y = imageSize.height - (boundingBox.origin.y * imageSize.height) - height;
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
//获取到在屏幕上的region rect
CGRect faceRegion = CGRectMake((x / imageSize.width) * screenWidth, (y / imageSize.height) * screenHeight, (width / imageSize.width) * screenWidth, (height / imageSize.height) * screenHeight);
//画一个layer 打框
CALayer *layer = strongSelf.trackingBBoxDict[uuid];
if (!layer) {
layer = [[CALayer alloc] init];
layer.frame = faceRegion;
layer.borderWidth = 2;
layer.cornerRadius = 3;
layer.borderColor = [UIColor redColor].CGColor;
[strongSelf.trackingBBoxDict setObject:layer forKey:uuid];
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf.view.layer addSublayer:layer];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
layer.frame = faceRegion;
});
}
} else {
//有问题,就清除所有的bbox layer
[strongSelf.trackingObservationDict removeObjectForKey:uuid];
dispatch_async(dispatch_get_main_queue(), ^{
CALayer *layer = strongSelf.trackingBBoxDict[uuid];
[layer removeFromSuperlayer];
[strongSelf.trackingBBoxDict removeAllObjects];
});
}
}];
//设置tracking以准确率为主
trackingRequest.trackingLevel = VNRequestTrackingLevelAccurate;
//添加请求到数组中
[trackingRequestArray addObject:trackingRequest];
}
NSError *error = nil;
//需要保留这个sequenceRequestHandler,每次追踪都需要使用这个,否则结果不正确
if (!strongSelf.sequenceRequestHandler) {
strongSelf.sequenceRequestHandler = [[VNSequenceRequestHandler alloc] init];
}
//执行一系列的请求
[strongSelf.sequenceRequestHandler performRequests:trackingRequestArray onCVPixelBuffer:imageBuffer error:&error];
if (error) {
//出错就删除框
[strongSelf.trackingObservationDict removeAllObjects];
dispatch_async(dispatch_get_main_queue(), ^{
for (NSString *uuid in strongSelf.trackingBBoxDict.allKeys) {
CALayer *layer = strongSelf.trackingBBoxDict[uuid];
[layer removeFromSuperlayer];
}
[strongSelf.trackingBBoxDict removeAllObjects];
});
}
}];
}
return self;
}
/**
初始的目标检测,检测需要跟踪的目标
@param imageBuffer imageBuffer description
*/
- (void)detectTrackingFace:(CVPixelBufferRef)imageBuffer {
//创建一个人脸检测的请求
VNDetectFaceRectanglesRequest *request = [[VNDetectFaceRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
for (VNFaceObservation *faceObservation in request.results) {
//由于tracking的时候只能使用VNDetectedObjectObservation,而VNDetectFaceRectanglesRequest是其子类
//所以作者使用检测到的人脸的区域来创建一个observation想让Vision追踪人脸,但失败了。。
VNDetectedObjectObservation *observation = [VNDetectedObjectObservation observationWithBoundingBox:faceObservation.boundingBox];
[self.trackingObservationDict setObject:observation forKey:observation.uuid.UUIDString];
}
}];
//读者可以试验,检测目标的持续追踪
// VNDetectRectanglesRequest *request = [[VNDetectRectanglesRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
// for (VNDetectedObjectObservation *observation in request.results) {
// [self.trackingObservationDict setObject:observation forKey:observation.uuid.UUIDString];
// }
// }];
//创建一个检测的handler
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCVPixelBuffer:imageBuffer options:@{}];
NSError *error;
//执行请求
[handler performRequests:@[request] error:&error];
if (error) {
//出错就清空所有数据
[self.trackingObservationDict removeAllObjects];
dispatch_async(dispatch_get_main_queue(), ^{
for (NSString *uuid in self.trackingBBoxDict.allKeys) {
CALayer *layer = self.trackingBBoxDict[uuid];
[layer removeFromSuperlayer];
}
[self.trackingBBoxDict removeAllObjects];
});
}
}
评释很详细,篇幅难题不细讲了,读者能够自行实验,通超过实际验发现,指标检查和测试、目的追踪的效益实在不太好,人脸检查和测试和人脸特征检查和测试效果相比较好,速度也相当慢。
乘机苹果新品iPhone x
的发布,正式版iOS 11
也就应声要推送过来了,在正式版本到来此前相比较奇怪,于是就去下载了个Beat
本子刷了下,感觉还不易。WWDC 2017
出产了机器学习框架和ARKit
八个相比有趣的东西,本想先来学学深造AR
,无奈手提式有线电话机刚刚不在版本中…..真受伤,只可以来学学深造机器学习了,上面进入正题吧。
coremltools转换本身的模子
前文讲解了贰个详实的实时检查和测试的板栗,但深度学习模型的调用其实依旧很简短的,官方的模型玩完事后,咱们就足以品味将磨炼好的模型转换为mlmodel
格式,苹果官方推出的python
包coremltools
正是做到那一个工作的,然而那么些包只补助caffe
和keras
,一些第叁方的能够扶助Tensorflow
,然而它协助的操作比较少,有个别模型不可能转换,还亟需等开发者们继续完善。
安装coremltools
pip install coremltools
设置达成后,就可以去下载你想要的模子了,你可以在Caffe Model
Zoo上下载的磨练好的模子,作者此前看来过2个CVPRubicon2014的岁数和性别预测的故事集,那里就以这一个模型为例讲解一下更换进度。
故事集下载地址:
CNN_AgeGenderEstimation
模型下载地址: CNN_AgeGender Caffe
Model
下载实现后获得了.caffemodel
的权值文件、.prototxt
的网络布局布局文件,假使那是三个多分类的模子,最好交给模型最后输出的竹签,以txt
的格式,一行2个标签,那样,在转移达成后的mlmodel
文件就一向出口最后的不难读的结果,而不是最终一层输出的数量。
>>> import coremltools
>>> model = coremltools.converters.caffe.convert(('age_net.caffemodel', 'deploy_age.prototxt'), image_input_names='data', class_labels='age_labels.txt')
>>> model.save('AgePredictionNet.mlmodel')
运营上述代码后,就能够生出转换后的mlmodel
文本了,转换代码也很简单,只要求3个tuple
花色的数量,传入.caffemodel
文件,.prototxt
网络布局布局文件,后边都是可选参数了,当中class_labels
的age_labels.txt
是作者自身写的,在舆论中得以见到这些岁数预测的互连网最后输出结果是两个系列中的3个,假使不友善写标签文件,输出结果便是0-7的数字,文件内容如下:
0-2
4-6
8-13
15-20
25-32
38-43
48-53
60
每四个标签占一行,转换时会将该内容集成进mlmodel
中,最终在出口时,直接能够获得易于读的标签数据。
将更换后的模型拖入Xcode
中,能够查阅到如下音信:
Age
开发者关怀的是网络接口定义,输入与出口音讯,输入为227*227的图像数据,输出结果有多个,1个是多个项目各自置信度的字典,还有三个是置信度最高的花色称号,即眼下的class_labels
填写的剧情。由于篇幅难题,该网络具体的选择就不赘述了,和前边的VGG16
是一样的。读者能够自动实验一下性别分类互连网的更换,这么些互联网的出口是二分拣难点,所以能够不须求class_labels
友善分析输出结果就好了,当然也可以写叁个文书标识。
看一下该方法的定义:
def convert(model, image_input_names=[], is_bgr=False,
red_bias=0.0, blue_bias=0.0, green_bias=0.0, gray_bias=0.0,
image_scale=1.0, class_labels=None, predicted_feature_name=None):
model
正是2个tuple,包涵.caffemodel
、.prototxt
、.binaryproto
均值文件,当中前八个是必须的。
image_input_names
可选参数,表示网络输入层的名号,能够在.prototxt
文件中查看到。
is_bgr
事先在日前的栗子说过caffe
的图像是BGRA
格式的。
red_bias blue_bias green_bias image_scale
顾名思义啦,种种偏置和缩放比例。
class_labels
就是前边举例的不难读和获取最后结出的公文。
predicted_feature_name
模型输出体系称号,感觉没什么用
代码部分
在写代码此前,大家还须求领会一些东西,那正是模型生成的类中都有何艺术,那里大家就以Resnet50
为类,在ViewController
中程导弹入头文件#import "Resnet50.h"
,当我们在输入Res
的时候,就会自行补全,导入别的模型的时候,也能够这样来效仿。在进入Resnet50
头文件中,我们得以看来里边分为八个类,分别为:Resnet50Input
、Resnet50Output
、Resnet50
,看其意思也能猜到,分别为输入、输出、和主要性选择类。
在Resnet50
中,我们能够看来五个格局,分别如下:
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error;
/**
Make a prediction using the standard interface
@param input an instance of Resnet50Input to predict from
@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 Resnet50Output
*/
- (nullable Resnet50Output *)predictionFromFeatures:(Resnet50Input *)input error:(NSError * _Nullable * _Nullable)error;
/**
Make a prediction using the convenience interface
@param image Input image of scene to be classified as color (kCVPixelFormatType_32BGRA) 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 Resnet50Output
*/
- (nullable Resnet50Output *)predictionFromImage:(CVPixelBufferRef)image error:(NSError * _Nullable * _Nullable)error;
第一个应该是早先化方法,前面四个应该是出口对象的章程,看到那里,不由的当即初步动手了。都说欲速则不达,果然是如此,前边赶上一堆堆坑,容我慢慢道来。
一早先笔者的开始化方法是那般的
Resnet50* resnet50 = [[Resnet50 alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Resnet50" ofType:@"mlmodel"]] error:nil];
咋一看,恩,应该是一对一的perfect
,然则现实是凶暴的,出意外的倒台了…
日志如下
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSURL initFileURLWithPath:]: nil string parameter'
为了找准地点,作者控制打个全局断点,信心倍增的起来下二次运行,可是依然一如既往的功力,气的本身,果断直接只写了上面的开头化方法
Resnet50* resnet50 = [[Resnet50 alloc] init];
本次没有崩溃,而是直接进去了下边包车型大巴图
bb.png
偶尔的空子,见识到了Resnet50
里面包车型大巴兑现格局,首先映入眼帘的是mlmodelc
那一个类型….想必我们也领略了呢!可是咋就进来了那么些地点了?幸运的是让断点继续执行一次就ok
了,于是作者胆大揣摸,是或不是断点引起的,立即撤废断点,重新Run
,耶,果然没错,一切顺遂进行中…此时的自己是泪崩的。
这一密密麻麻经过证实:
壹 、模型的后缀为mlmodelc
二 、调节和测试的时候能够收回断点,方便调节和测试,省的点来点去,当然如若想看看里面贯彻,能够加上断点
在那里调通后,便是下一步输出的难题了,下边也旁观了有多少个措施,二个是依照Resnet50Input
1个是依照CVPixelBufferRef
,而在Resnet50Input
中又有这么三个初叶化方法
- (instancetype)initWithImage:(CVPixelBufferRef)image;
看来那几个CVPixelBufferRef
是少不了的了
有关那些,小编在网上找了二个格局,方法如下
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image{
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
nil];
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;
}
在这么些办法写完事后,作者将事先的方法开始展览了一揽子,获得下边包车型客车代码
- (NSString*)predictionWithResnet50:(CVPixelBufferRef )buffer
{
Resnet50* resnet50 = [[Resnet50 alloc] init];
NSError *predictionError = nil;
Resnet50Output *resnet50Output = [resnet50 predictionFromImage:buffer error:&predictionError];
if (predictionError) {
return predictionError.description;
} else {
return [NSString stringWithFormat:@"识别结果:%@,匹配率:%.2f",resnet50Output.classLabel, [[resnet50Output.classLabelProbs valueForKey:resnet50Output.classLabel]floatValue]];
}
}
怀着激动的心怀,添加了imageview
和lable
,和上边包车型地铁代码
CGImageRef cgImageRef = [imageview.image CGImage];
lable.text = [self predictionWithResnet50:[self pixelBufferFromCGImage:cgImageRef]];
Run
…
error1.png
error.png
看样子这些结果,消极的半天不想张嘴,万幸有日记,仔细看日志,你会意识,好像是图形的大大小小不对…提醒说是要224
,好吧,那就改改尺寸看看
- (UIImage *)scaleToSize:(CGSize)size image:(UIImage *)image {
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
UIImage *scaledImage = [self scaleToSize:CGSizeMake(224, 224) image:imageview.image];
CGImageRef cgImageRef = [scaledImage CGImage];
lable.text = [self predictionWithResnet50:[self pixelBufferFromCGImage:cgImageRef]];
再Run
…
success.png
终于成功了!!!,至于结果嘛,还足以算壮志未酬,究竟狼王加内特就是打篮球的
,哈哈。
末端小编又尝试了别样类,小编原以为尺寸都以224
,然而在Inceptionv3
的时候,提醒作者是要用229
,于是自身就仔细翻看了下类代码,发现里头已经有那上面的表达….
/// Input image to be classified as color (kCVPixelFormatType_32BGRA) image buffer, 299 pixels wide by 299 pixels high
/// Input image of scene to be classified as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high
到此突然想到,在上面,大家查阅模型的图中,也有认证,正是inputs
有关参数那列。
到此处,好像我们还有1个类没有运用,那就是Vision
,那么通过Vision
又怎么和Core ML
来叁只落到实处呢?