iOS-机器学习框架CoreML

Posted YI技之长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS-机器学习框架CoreML相关的知识,希望对你有一定的参考价值。

CoreML 是 Apple 在 WWDC 2017 推出的机器学习框架。但是其到底有什么功能呢?本文对其进行初探。

模型

在 CoreML 中, Apple 定义了一套自己的模型格式,后缀名为: mimodel,通过 CoreML 框架,以及模型库,可以在 App 层面进行机器学习的功能研发。

官网已经提供四个模型库供下载。

iOS-机器学习框架CoreML

Demo

官网提供了一个 Demo,要求 XCode 9 + ios 11 的环境。

下载下来 Run 了一下,不得不说,Apple 对开发者还是非常友好的,直接将模型文件拖到项目中,Xcode 会自动生成接口文件:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

import CoreML

class MarsHabitatPricerInput : MLFeatureProvider {

var solarPanels: Double

var greenhouses: Double

var size: Double

var featureNames: Set<String> {

get {

return ["solarPanels", "greenhouses", "size"]

}

}

func featureValue(for featureName: String) -> MLFeatureValue? {

if (featureName == "solarPanels") {

return MLFeatureValue(double: solarPanels)

}

if (featureName == "greenhouses") {

return MLFeatureValue(double: greenhouses)

}

if (featureName == "size") {

return MLFeatureValue(double: size)

}

return nil

}

init(solarPanels: Double, greenhouses: Double, size: Double) {

self.solarPanels = solarPanels

self.greenhouses = greenhouses

self.size = size

}

}

class MarsHabitatPricerOutput : MLFeatureProvider {

let price: Double

var featureNames: Set<String> {

get {

return ["price"]

}

}

func featureValue(for featureName: String) -> MLFeatureValue? {

if (featureName == "price") {

return MLFeatureValue(double: price)

}

return nil

}

init(price: Double) {

self.price = price

}

}

@objc class MarsHabitatPricer:NSObject {

var model: MLModel

init(contentsOf url: URL) throws {

self.model = try MLModel(contentsOf: url)

}

convenience override init() {

let bundle = Bundle(for: MarsHabitatPricer.self)

let assetPath = bundle.url(forResource: "MarsHabitatPricer", withExtension:"mlmodelc")

try! self.init(contentsOf: assetPath!)

}

func prediction(input: MarsHabitatPricerInput) throws -> MarsHabitatPricerOutput {

let outFeatures = try model.prediction(from: input)

let result = MarsHabitatPricerOutput(price: outFeatures.featureValue(for: "price")!.doubleValue)

return result

}

func prediction(solarPanels: Double, greenhouses: Double, size: Double) throws -> MarsHabitatPricerOutput {

let input_ = MarsHabitatPricerInput(solarPanels: solarPanels, greenhouses: greenhouses, size: size)

return try self.prediction(input: input_)

}

}


可以看到,主要是定义了输入,输出以及预测的格式,调用的时候,也非常简单,传参即可。

但是这些接口文件并没有在 XCode 左边的文件树中出现。

iOS-机器学习框架CoreML

查了一下,是生成在 DerivedData 目录下,估计是想开发者使用起来更简洁。

运行一下,可以看到,主要功能是对价格进行预测。

iOS-机器学习框架CoreML

貌似稍微有点不够高大上…

Resnet50

官网提供的四个模型库,我们还没用呢,当然要看下能用来干啥,看了一下,貌似主要是物体识别,OK,代码走起。

先下载模型库 Resnet50, 然后创建一个新的 Swift 项目,将其拖进去:

iOS-机器学习框架CoreML

从描述里面可以看出来,其实一个神经网络的分类器,输入是一张像素为 (224 * 224) 的图片,输出为分类结果。

自动生成的接口文件:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

import CoreML

class Resnet50Input : MLFeatureProvider {

var image: CVPixelBuffer

var featureNames: Set<String> {

get {

return ["image"]

}

}

func featureValue(for featureName: String) -> MLFeatureValue? {

if (featureName == "image") {

return MLFeatureValue(pixelBuffer: image)

}

return nil

}

init(image: CVPixelBuffer) {

self.image = image

}

}

class Resnet50Output : MLFeatureProvider {

let classLabelProbs: [String : Double]

let classLabel: String

var featureNames: Set<String> {

get {

return ["classLabelProbs", "classLabel"]

}

}

func featureValue(for featureName: String) -> MLFeatureValue? {

if (featureName == "classLabelProbs") {

return try! MLFeatureValue(dictionary: classLabelProbs as [NSObject : NSNumber])

}

if (featureName == "classLabel") {

return MLFeatureValue(string: classLabel)

}

return nil

}

init(classLabelProbs: [String : Double], classLabel: String) {

self.classLabelProbs = classLabelProbs

self.classLabel = classLabel

}

}

@objc class Resnet50:NSObject {

var model: MLModel

init(contentsOf url: URL) throws {

self.model = try MLModel(contentsOf: url)

}

convenience override init() {

let bundle = Bundle(for: Resnet50.self)

let assetPath = bundle.url(forResource: "Resnet50", withExtension:"mlmodelc")

try! self.init(contentsOf: assetPath!)

}

func prediction(input: Resnet50Input) throws -> Resnet50Output {

let outFeatures = try model.prediction(from: input)

let result = Resnet50Output(classLabelProbs: outFeatures.featureValue(for: "classLabelProbs")!.dictionaryValue as! [String : Double], classLabel: outFeatures.featureValue(for: "classLabel")!.stringValue)

return result

}

func prediction(image: CVPixelBuffer) throws -> Resnet50Output {

let input_ = Resnet50Input(image: image)

return try self.prediction(input: input_)

}

}


OK,要照片,而且是 CVPixelBuffer 类型的。

但是每次从相册选太烦了,所以我们直接摄像头走起。将 AVCam 的主要功能类复制到项目中。

iOS-机器学习框架CoreML

然后,禁用 CameraViewController 中一些不必要的按钮:


1

2

3

4

self.recordButton.isHidden = true

self.captureModeControl.isHidden = true

self.livePhotoModeButton.isHidden = true

self.depthDataDeliveryButton.isHidden = true


由于,AVCapturePhotoCaptureDelegate 拍照完成的回调为:


1

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?)


看了下 AVCaputrePhoto 的定义,里面刚好有 CVPixelBuffer 格式的属性:

iOS-机器学习框架CoreML

直接传进去试试:


1

2

3

4

5

6

// Predicte

if let pixelBuffer = photo.previewPixelBuffer {

guard let Resnet50CategoryOutput = try? model.prediction(image:pixelBuffer) else {

fatalError("Unexpected runtime error.")

}

}


一切看起来很完美,编译通过,运行起来,点一下拍照按钮,额,Crash了,异常:


1

[core] Error Domain=com.apple.CoreML Code=1 "Input image feature image does not match model description" UserInfo={NSLocalizedDescription=Input image feature image does not match model description, NSUnderlyingError=0x1c0643420 {Error Domain=com.apple.CoreML Code=1 "Image is not valid width 224, instead is 852" UserInfo={NSLocalizedDescription=Image is not valid width 224, instead is 852}}}


哦,忘记改大小了,找到 photoSetting,加上宽高:


1

2

3

4

5

if !photoSettings.availablePreviewPhotoPixelFormatTypes.isEmpty {

photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings.availablePreviewPhotoPixelFormatTypes.first!,

  kCVPixelBufferWidthKey as String : NSNumber(value:224),

  kCVPixelBufferHeightKey as String : NSNumber(value:224)]

}


重新 Run,WTF,Man,居然又报同样的错,好吧,Google 一下,貌似宽高的属性,在 Swift 里面不生效,额。。

没办法,那我们只能将 CVPixelBuffer 先转换成 UIImage,然后改下大小,再转回 CVPixelBuffer,试试:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

photoData = photo.fileDataRepresentation()

// Change Data to Image

guard let photoData = photoData else {

return

}

let image = UIImage(data: photoData)

// Resize

let newWidth:CGFloat = 224.0

let newHeight:CGFloat = 224.0

UIGraphicsBeginImageContext(CGSize(width:newWidth, height:newHeight))

image?.draw(in:CGRect(x:0, y:0, width:newWidth, height:newHeight))

let newImage = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

guard let finalImage = newImage else {

return

}

// Predicte

guard let Resnet50CategoryOutput = try? model.prediction(image:pixelBufferFromImage(image: finalImage)) else {

fatalError("Unexpected runtime error.")

}


重新 Run,OK,一切很完美。

最后,为了用户体验,加上摄像头流的暂停和重启,免得在识别的时候,摄像头还一直在动,另外,识别结果通过提醒框弹出来,具体参考文末的源码。

开始玩啦,找支油笔试一下:

识别成,橡皮擦,好吧,其实是有点像。

再拿小绿植试试:

花瓶,Are you kidding me ?? 

其实,效果还是蛮不错的。

刚好下午要去上海 CES Asia,一路拍过去玩,想想都有点小激动。

最后,源码奉上,想玩的同学直接下载编译就行了,别忘了 Star~


以上是关于iOS-机器学习框架CoreML的主要内容,如果未能解决你的问题,请参考以下文章

AoE工程实践 —— 记CoreML模型在CocoaPods应用中的集成(上)

教程 | Apple机器学习框架Core ML入门教程

尴尬了:苹果Core ML机器学习框架将老款Mac Pro识别为加热器

Apple 机器学习框架 Core ML 教程

平安夜的平安果——Apple机器学习框架Core ML教程

Core ML入门:构建一个简单的图像识别应用