Core ML and Vision Framework on iOS 11

导语 :机器学习和计算机视觉在 iOS 上虽然早已有了系统级的支持,但 WWDC 17 发布的 iOS 11 将它们的使用门槛大大降低。苹果提供了设计合理且容易上手的 API,让那些对基础理论知识一窍不通的门外汉也能玩转高大上的前沿科技,这是苹果一贯的风格。

这是一篇 WWDC 2017 Session 506,608,703 和 710 的学习笔记,以及分享自己尝试写的 Demo Core-ML- Sample

Core ML

简介

Core ML 大大降低了开发者在苹果设备上使用机器学习技术预测模型的门槛和成本。苹果制定了自己的模型文件格式,统一的格式和全新的 API 设计使得 Core ML 支持苹果生态下多个平台。

将数据经过预处理后输入 MLMODEL 文件,输出为模型的预测结果。使用 Core ML 只需要很少的代码就可以构建起一个机器学习的应用。只需关注代码即可,无需关注模型的定义,网络的构成。这跟以前写 MPS 代码时构成了强烈的反差:开发者需要写大量 MPS 代码用于构建和描述一个完整的网络,而加载的文件仅仅是模型的权重而已。MLMODEL 文件包含了权重和模型结构等信息,并可以自动生成相关的代码,节省开发者大量时间。

Model 转换工具

苹果提供了一个 Python 工具,可以将业内一些常用的机器学习框架导出的 Model 转成 MLMODEL 文件。代码会编译成可执行二进制文件,而 MLMODEL 会编译成 Bundle 文件,在代码文件中可以直接调用 MLMODEL 生成的类,这些都是需要 Xcode 9 做支撑,也就是说,现阶段并不支持动态下发 MLMODEL 文件。Core ML 的预测过程全都在客户端进行,保证用户隐私不会泄露。

Core ML 支持 DNN,RNN,CNN,SVM,Tree ensembles,Generalized linear models,Pipeline models 等,对应的模型转换工具 Core ML Tools 也支持了一些常用机器学习框架模型的转换。虽然目前没有直接支持 Google 的 TensorFlow,但可以使用 Keras 曲线救国。coremltools 已经开源,并提供可拓展性的底层接口,可以编写适配其他机器学习框架模型的转换工具。

MLMODEL 文件中还包含了很多元数据,比如作者,License,输入输出的描述文字。这些元数据都可以通过 coremltools 的接口进行设置。coremltools 上手很简单,可以查看完整详细的使用文档

把 MLMODEL 文件拖拽到 Xcode 工程中后,记得要勾选对应的 target,这样 Xcode 才会自动生成对应的代码。生成的类名就是 MLMODEL 文件名,输入和输出的变量名和类型也可以在 Xcode 中查看:

底层计算性能

Core ML 的底层是 Accelerate BNNS 和 MPS,并可以根据实际情况进行无缝切换。比如在处理图片的场景下使用 MPS,处理文字场景下使用 Accelerate,甚至可以在同一个 model 的不同层使用不同的底层技术来预测。Vision 和 NLP 可以结合 Core ML 一起使用。Core ML 对硬件做了性能优化,而且支持的模型种类更多,开发者不用关注底层的一些细节,苹果全都封装好了。

当然,这些也都是建立在 MPS 更新的基础上,MPS 在 iOS 11 中拓展了支持向量和矩阵的数据结构 MPSVectorMPSMatrix,以及它们之间相乘的 API。而且提供了更多的神经网络类型(比如 RNN 等),在卷积神经网络中也提供了更多种类的卷积核,用于满足更多特殊场景。

苹果在 Metal 2 中补充 MPS 大量功能的同时,也提供了用于描述神经网络结构的语言:Neural Network Graph API。使用它可以极大简化代码逻辑,代码量缩减到以前的四分之一(以 Inception V3 为例)。并且使用 NN Graph API 可以并行使用 CPU 和 GPU。这种图语言跟主流的分布式机器学习框架的使用很像:先用简单的 Python 语言描述好网络结构,定义好输入输出格式,然后一次性提交到后端去执行。后端对底层性能做了很多细节优化,然而开发者完全不用关心这些。新增的 MPSNNGraph 提供了异步接口使得 CPU 不用再等待 GPU 的执行结果,性能也得到提升。

Metal 2 使用 MPS 进行图像处理的性能也得到了提升,在不同的设备上大约提升了百分之二十多。

Demo: 数据预处理

Core-ML-Sample 使用了 Core ML 和 Vision 技术实现对摄像头拍摄的图像实时预测物体种类。因为图像来源是摄像头,所以需要将 CMSampleBuffer 转成 CVPixelBuffer。因为 Xcode 9 已经生成好了代码,直接调用 Inceptionv3 类的 prediction 方法即可完成预测。生成的 Inceptionv3Output 类含有 classLabelclassLabelProbs 两个属性,可以获取预测的分类标签名以及每种标签的可能性。可以点击 Xcode Model View 中 Model Class 的生成源码箭头来查看这些类的信息。

let inceptionv3model = Inceptionv3()

func handleImageBufferWithCoreML(imageBuffer: CMSampleBuffer) {
   guard let pixelBuffer = CMSampleBufferGetImageBuffer(imageBuffer) else {
       return
   }
   do {
       let prediction = try self.inceptionv3model.prediction(image: self.resize(pixelBuffer: pixelBuffer)!)
       DispatchQueue.main.async {
           if let prob = prediction.classLabelProbs[prediction.classLabel] {
               self.predictLabel.text = "\(prediction.classLabel) \(String(describing: prob))"
           }
       }
   }
   catch let error as NSError {
       fatalError("Unexpected error ocurred: \(error.localizedDescription).")
   }
}

在 Xcode Model View 中可以看到 Inceptionv3 模型的输入图片为 Image,所以需要对摄像头采集到的图像进行预处理。我的转换流程是:CVPixelBuffer->CVPixelBuffer->CIImage->CIImage(resized)->CVPixelBuffer。最后一步 CIImageCVPixelBuffer 是通过 CIContext 渲染完成。

/// resize CVPixelBuffer
///
/// - Parameter pixelBuffer: CVPixelBuffer by camera output
/// - Returns: CVPixelBuffer with size (299, 299)
func resize(pixelBuffer: CVPixelBuffer) -> CVPixelBuffer? {
   let imageSide = 299
   var ciImage = CIImage(cvPixelBuffer: pixelBuffer, options: nil)
   let transform = CGAffineTransform(scaleX: CGFloat(imageSide) / CGFloat(CVPixelBufferGetWidth(pixelBuffer)), y: CGFloat(imageSide) / CGFloat(CVPixelBufferGetHeight(pixelBuffer)))
   ciImage = ciImage.applying(transform).cropping(to: CGRect(x: 0, y: 0, width: imageSide, height: imageSide))
   let ciContext = CIContext()
   var resizeBuffer: CVPixelBuffer?
   CVPixelBufferCreate(kCFAllocatorDefault, imageSide, imageSide, CVPixelBufferGetPixelFormatType(pixelBuffer), nil, &resizeBuffer)
   ciContext.render(ciImage, to: resizeBuffer!)
   return resizeBuffer
}

除了图片需要预处理外,其他数据可能也需要预处理。这需要看训练的模型的输入是什么形式,比如分析一段文本所表达的情绪是开心还是沮丧,可能需要写个预处理程序统计词频,然后输入到训练好的模型中进行预测。

总结

  • Model 极速集成
  • 支持多种数据类型
  • 硬件优化
  • 适配主流机器学习框架

Vision

应用场景

  1. 人脸检测:支持检测笑脸、侧脸、局部遮挡脸部、戴眼镜和帽子等场景,可以标记出人脸的矩形区域
  2. 人脸特征点:可以标记出人脸和眼睛、眉毛、鼻子、嘴、牙齿的轮廓,以及人脸的中轴线
  3. 图像配准
  4. 矩形检测
  5. 二维码/条形码检测
  6. 文字检测
  7. 目标跟踪:脸部,矩形和通用模板

Vision 使用姿势

将各种功能的 Request 提供给一个 RequestHandler,Handler 持有图片信息,并将处理结果分发给每个 Request 的 completion Block 中。可以从 results 属性中得到 Observation 数组,然后进行更新 UI 等操作。因为 completion Block 所执行的队列跟 perform request 的队列相同,所以更新 UI 时记得使用主队列。

Vision 操作流水线分为两类:分析图片和跟踪队列。可以使用图片检测出的物体或矩形结果(Observation)来作为跟踪队列请求(Request)的参数。

Vision 支持的图片数据类型:

  • CVPixelBufferRef
  • CGImageRef
  • CIImage
  • NSURL
  • NSData

这几乎涵盖了 iOS 中图片相关的 API,很实用很强大。

Vision 有三种 resize 图片的方式,无需使用者再次裁切缩放

  • VNImageCropAndScaleOptionCenterCrop
  • VNImageCropAndScaleOptionScaleFit
  • VNImageCropAndScaleOptionScaleFill

Vision 与 iOS 上其他几种带人脸检测功能框架的对比:

Demo: 与 Core ML 集成

Core ML 具有更好的性能,Vision 可为其提供图片处理的流程。Core ML 生成的代码中含有 MLModel 类型的 model 对象,可以用它初始化 VNCoreMLModel 对象,这样就将 Core ML 的 Model 集成进 Vision 框架中了:

private var requests = [VNRequest]()

func setupVision() {
   guard let visionModel = try? VNCoreMLModel(for: inceptionv3model.model) else {
       fatalError("can't load Vision ML model")
   }
   let classificationRequest = VNCoreMLRequest(model: visionModel) { (request: VNRequest, error: Error?) in
       guard let observations = request.results else {
           print("no results:\(error!)")
           return
       }

       let classifications = observations[0...4]
           .flatMap({ $0 as? VNClassificationObservation })
           .filter({ $0.confidence > 0.2 })
           .map({ "\($0.identifier) \($0.confidence)" })
       DispatchQueue.main.async {
           self.predictLabel.text = classifications.joined(separator: "\n")
       }
   }
   classificationRequest.imageCropAndScaleOption = VNImageCropAndScaleOptionCenterCrop

   self.requests = [classificationRequest]
}

上面的代码实现了 Vision 的工作流,并在 completion Block 中对预测结果进行了处理:从 top5 中筛选可能性大于 0.2 的结果,并转成文本描述。因为所有结果的可能性总和为 1,所以最终的结果不会达到 5 个,实际测试中其实结果往往只有 1-2 个。

对摄像头传入的每帧图片进行预测。虽然 Vision 帮我们完成了预处理等流程上的工作,但是需要我们传入一些额外的信息。

func handleImageBufferWithVision(imageBuffer: CMSampleBuffer) {
   guard let pixelBuffer = CMSampleBufferGetImageBuffer(imageBuffer) else {
       return
   }

   var requestOptions:[VNImageOption : Any] = [:]

   if let cameraIntrinsicData = CMGetAttachment(imageBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) {
       requestOptions = [.cameraIntrinsics:cameraIntrinsicData]
   }

   let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: self.exifOrientationFromDeviceOrientation, options: requestOptions)
   do {
       try imageRequestHandler.perform(self.requests)
   } catch {
       print(error)
   }
}

需要向图片传入 EXIF Orientation 信息:

/// only support back camera
var exifOrientationFromDeviceOrientation: Int32 {
   let exifOrientation: DeviceOrientation
   enum DeviceOrientation: Int32 {
       case top0ColLeft = 1
       case top0ColRight = 2
       case bottom0ColRight = 3
       case bottom0ColLeft = 4
       case left0ColTop = 5
       case right0ColTop = 6
       case right0ColBottom = 7
       case left0ColBottom = 8
   }
   switch UIDevice.current.orientation {
   case .portraitUpsideDown:
       exifOrientation = .left0ColBottom
   case .landscapeLeft:
       exifOrientation = .top0ColLeft
   case .landscapeRight:
       exifOrientation = .bottom0ColRight
   default:
       exifOrientation = .right0ColTop
   }
   return exifOrientation.rawValue
}

总结

  • Vision 是一个关于计算机视觉的顶层新框架。
  • 一个界面,多重跟踪检测
  • 集成 Core ML 轻松使用自己的 model

感受

苹果为开发者带来了炫酷的功能,并且这些示例很有针对性,更实用。Vision 更像是一个工具库,对一些高频场景进行了封装,比如人脸、条形码、矩形和文字等,这些基于底层 API 封装的高级功能可以帮助开发者很快地完成老板的功能。而 Core ML 给出的 Model 也很有代表性,贴近实际应用场景,很容易激发开发者使用的热情。我想这正是苹果最擅长的,把复杂的事情简单化,提供极易上手的 Demo,并循序渐进,给予开发者更高深的玩法,不失拓展性和定制化。

coremltools 肯定还存在一些兼容性问题,并且会随着各大机器学习框架的更新而不断更新,我想这也是为何苹果将其开源的原因吧。使用 python 也更方便维护,而且主流的机器学习框架都是用 python 作为前端语言。

Core ML 功能强大,支持的模型种类很多。与此同时,MPS 在 iOS 11 也得到了升级,新增的数据类型更方便使用。总之其实还是新增了对底层数据和算法的封装,然后 Core ML 在此基础上又进行了一层高级的封装。可以看出苹果这一年在底层下的功夫确实不少,在这之后才有了更强大更全面的 API。我预测在这之后 Core ML 还会有更多的模型得到支持,Vision 也会加入更丰富的应用场景。

如果苹果能够发挥硬件上性能的优势,可能在以后还会演示出更炫酷的 Demo,比如视频实时防抖的处理,更牛逼的滤镜效果。对高性能计算和 GPU 图像处理感兴趣的话,推荐看下 Metal 2 相关的 Session,尤其是 Session 608。

同时也会发现苹果在机器学习的道路上避开了各个训练框架的锋芒,尤其是最近大红大紫的 Google TensorFlow。它选择另辟蹊径,在移动端模型预测性能优化和低成本接入的道路上另辟蹊径,充分发挥自身平台的优势。毕竟在移动端训练模型意义较小,还是交给服务端比较合理。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

254页PPT!这是一份写给NLP研究者的编程指南

这份内容干货满满,仅仅只是看了 slide 就知道是非常有意思的一次演讲了。slide 共有 254 页之多,在「赤乐君」知乎专栏分享内容的基础上,机器之心为大...

804
来自专栏华章科技

数据专家必知必会的7款Python工具

我咨询了我们真正的数据专家,收集整理了他们认为所有数据专家都应该会的七款 Python 工具。The Galvanize Data Science 和 Galv...

683
来自专栏PPV课数据科学社区

手把手教你学习R语言

随着分析数据的方式在近两年发生了翻天覆地的变化,随着互联网在人们的生活中广泛的普及,人手一部智能机的时代,人们的衣食住行都接上的互联网,这使得数据的获取量得以指...

5958
来自专栏WOLFRAM

Mathematica 11.1.1 中文版已发布

1233
来自专栏申龙斌的程序人生

零基础学编程017:画出我的公众号LOGO

在《零基础学编程014:小海龟做画》和《零基础学编程015:画些有趣的图案》里,我们已经可以用编程中的循环结构,加上turtle中的前进、转向等指令画些有趣的图...

2726
来自专栏大数据钻研

真正的数据科学家 必备七大技术

如果你有志于做一个数据专家,你就应该保持一颗好奇心,总是不断探索,学习,问各种问题。在线入门教程和视频教程能帮你走出第一步,但是最好的方式就是通过熟悉各种已经在...

2506
来自专栏CSDN技术头条

数据专家必知必会的7款Python工具

如果你有志于做一个数据专家,你就应该保持一颗好奇心,总是不断探索,学习,问各种问题。在线入门教程和视频教程能帮你走出第一步,但是最好的方式就是通过熟悉各种已经在...

2136
来自专栏人工智能LeadAI

MLSQL解决了什么问题

1、项目难以重现,可阅读性和环境要求导致能把另外一个同事写的python项目运行起来不得不靠运气

1073
来自专栏数据小魔方

ggplot2玫瑰图案例——星巴克门店分布图

使用ggplot2制作放射状玫瑰图本不是什么难事,仅需将普通单序列柱形图添加添加一个极坐标转化参数即可。 但是遇到比较小清新的案例,还是值得手动操作一下的。 本...

3626
来自专栏人工智能头条

资源 | 5月Python好文TOP 10新鲜出炉,精选自1000篇文章,你都看了吗?

1563

扫码关注云+社区