CoreML 与Vision

本文环境为Swift4和iOS11.4 参考文章1, 参考文章2

CoreML

  • CoreML是苹果在WWDC2017 新发布的Framework,方便了Machine Learning在苹果自家平台的接接入与使用,同时苹果提供了Pythoncoremltools,方便将各大开源模型训练工具的现有模型转化为MLModel

Vision

Vision是一个新的,强大的,易于使用的框架,是苹果于WWDC 2017上针对CoreML使用所提出的新Framework,能快速有效的用于面部检测、面部特征点、文字、矩形、条形码和物体。

开始

下载起始项目。它已包含了用于显示图片的用户界面,并允许用户从照片库中选择另一张图片。这样你就可以专注于实现 App 的机器学习和视觉方面。

构建并运行该项目;可以看到一张城市夜景图,以及一个按钮:

示例图片

从“照片” App 的照片库中选择另一张图片。此起始项目的 Info.plist 已经有 Privacy – Photo Library Usage Description,所以你会被提示允许使用。

图片和按钮之间的空隙有一个 label,将会在此显示模型对图片场景的分类。

将 Core ML 模型集成到你的 App

本教程使用 Places205-GoogLeNet 模型,可以从苹果的“机器学习”页面下载。下载第一个。还在这个页面,注意一下其它三个模型,它们都用于在图片中检测物体——树、动物、人等等。

为你的项目添加模型

下载 GoogLeNetPlaces.mlmodel 后,把它从 Finder 拖到项目导航器的 Resources 组里:

选择该文件,然后等一会儿。Xcode 生成了模型类后会显示一个箭头:

点击箭头,查看生成的类:

Xcode 已生成了输入和输出类以及主类 GoogLeNetPlaces,主类有一个 model 属性和两个 prediction 方法。

Vision 框架会把 GoogLeNetPlacesOutput 属性转换为自己的 results 类型,并管理对 prediction 方法的调用,所以在所有生成的代码中,我们只会使用 model 属性。

在 Vision Model 中包装 Core ML Model

终于,要开始写代码了!打开 ViewController.swift,并在 import UIKit 下面 import 两个框架:

import CoreML
import Vision

下一步,在 IBActions 扩展下方添加如下扩展:

// MARK: - Methods
extension ViewController {

  func detectScene(image: CIImage) {
    answerLabel.text = "detecting scene..."

    // 从生成的类中加载 ML 模型
    guard let model = try? VNCoreMLModel(for: GoogLeNetPlaces().model) else {
      fatalError("can't load Places ML model")
    }
  }
}

我们上面的代码做了这些事:

首先,给用户显示一条消息,让他们知道正在发生什么事情。

GoogLeNetPlaces 的指定初始化方法会抛出一个 error,所以创建时必须用 try

VNCoreMLModel 只是用于 Vision 请求的 Core ML 模型的容器。

标准的 Vision 工作流程是创建模型,创建一或多个请求,然后创建并运行请求处理程序。我们刚刚已经创建了模型,所以下一步是创建请求。

detectScene(image:) 的末尾添加如下几行:

// 创建一个带有 completion handler 的 Vision 请求
let request = VNCoreMLRequest(model: model) { [weak self] request, error in
  guard let results = request.results as? [VNClassificationObservation],
    let topResult = results.first else {
      fatalError("unexpected result type from VNCoreMLRequest")
  }

  // 在主线程上更新 UI
  let article = (self?.vowels.contains(topResult.identifier.first!))! ? "an" : "a"
  DispatchQueue.main.async { [weak self] in
    self?.answerLabel.text = "\(Int(topResult.confidence * 100))% it's \(article) \(topResult.identifier)"
  }
}

VNCoreMLRequest 是一个图像分析请求,它使用 Core ML 模型来完成工作。它的 completion handler 接收 requesterror 对象。

检查 request.results 是否是 VNClassificationObservation 对象数组,当 Core ML 模型是分类器,而不是预测器或图像处理器时,Vision 框架就会返回这个。而 GoogLeNetPlaces 是一个分类器,因为它仅预测一个特征:图像的场景分类。

VNClassificationObservation 有两个属性:identifier - 一个 String,以及 confidence - 介于0和1之间的数字,这个数字是是分类正确的概率。使用对象检测模型时,你可能只会看到那些 confidence 大于某个阈值的对象,例如 30% 的阈值。

然后取第一个结果,它会具有最高的 confidence 值,然后根据 identifier 的首字母把不定冠词设置为“a”或“an”。最后,dispatch 回到主线程来更新 label。你很快会明白分类工作为什么不在主线程,因为它会很慢。

现在,做第三步:创建并运行请求处理程序。

把下面几行添加到 detectScene(image:) 的末尾:

// 在主线程上运行 Core ML GoogLeNetPlaces 分类器
let handler = VNImageRequestHandler(ciImage: image)
DispatchQueue.global(qos: .userInteractive).async {
  do {
    try handler.perform([request])
  } catch {
    print(error)
  }
}

VNImageRequestHandler 是标准的 Vision 框架请求处理程序;不特定于 Core ML 模型。给它 image 作为 detectScene(image:) 的参数。然后调用它的 perform 方法来运行处理程序,传入请求数组。在这个例子里,我们只有一个请求。

perform 方法会抛出 error,所以用 try-catch 将其包住。

使用模型来分类场景

哇,刚刚写了好多代码!但现在只需要在两个地方调用 detectScene(image:) 就好了。

把下面几行添加到 viewDidLoad() 的末端和 imagePickerController(_:didFinishPickingMediaWithInfo:) 的末端:

guard let ciImage = CIImage(image: image) else {
  fatalError("couldn't convert UIImage to CIImage")
}

detectScene(image: ciImage)

现在构建并运行。不需要多久就可以看见分类:

哈哈,是的,图片里有 skyscrapers(摩天大楼)。还有一列火车。

如果仔细看一下



可以注意到图片的大小会有要求, 如果大小不一样可能会影响到图片识别的效果
所以我们需要对代码进行一些改进

extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        dismiss(animated: true, completion: nil)
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        picker.dismiss(animated: true)
        UIGraphicsBeginImageContextWithOptions(CGSize(width: 224, height: 224), true, 2.0)
    image.draw(in: CGRect(x: 0, y: 0, width: 299, height: 299))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    
    let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
    var pixelBuffer : CVPixelBuffer?
    let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(newImage.size.width), Int(newImage.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
    guard (status == kCVReturnSuccess) else {
      return
    }
    
    CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
    let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)
    
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let context = CGContext(data: pixelData, width: Int(newImage.size.width), height: Int(newImage.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) //3
    
    context?.translateBy(x: 0, y: newImage.size.height)
    context?.scaleBy(x: 1.0, y: -1.0)
    
    UIGraphicsPushContext(context!)
    newImage.draw(in: CGRect(x: 0, y: 0, width: newImage.size.width, height: newImage.size.height))
    UIGraphicsPopContext()
    CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
    scene.image = newImage
    guard let prediction = try? Resnet50().prediction(image: pixelBuffer!) else {
      return
    }
    answerLabel.text = prediction.classLabel

    }
}

直接在图片选择的delegate里, 对图片进行处理, 将宽高设置为模型要求的大小

如何将你自己的模型转换成CoreML

苹果官方文档
下面将进行傻瓜式教学

  • 安装python
    如果你正在使用Mac,系统是OS X 10.8或者最新的10.9 Mavericks,恭喜你,系统自带了Python 2.7。如果你的系统版本低于10.8,请自行备份系统并免费升级到最新的10.9,就可以获得Python 2.7。
  • 查看python版本
    打开terminal输入
python --version
python查看版本
  • 安装pip
    输入命令行
sudo easy_install pip
  • 安装coremltools
pip install  -U  coremltools

使用命令行

pip list

来查看自己安装的python第三方package


pip list
  • 转换Caffe Model
    这里可以获取一些需要的caffe模型用来实验转CoreML
    Caffe Model

    将项目下载到本地以后, 移动到初始项目的目录中,它包含三个文件:class_labels.txt,deploy.prototxt和oxford102.caffemodel。
cd  <directory>

terminal里输入python进入python编码

python编码

第一步是import Core ML tools

import  coremltools

下一步

coreml_model  =  coremltools.converters.caffe.convert(('oxford102.caffemodel',  'deploy.prototxt'),  image_input_names='data',  class_labels='class_labels.txt')

现在这是很短的一行程式码,但其中包含很多事情,接下来让我解释一下这三个档案。

  • deploy.prototxt – 描述神经网络的结构。
  • oxford102.caffemodel – Caffe格式的数据训练模型。
  • class_labels.txt – 包含模型能够识别的全部花类列表。
    在上面的说明中,我们将定义一个名为coreml_model的模型,用来当做从Caffe转到Core ML的转换器,它是coremltools.converters.caffe.convert函式的运行结果,这行程式码的最后两个参数是:

image_input_names='data'
class_labels='class_labels.txt'
这两个参数定义了我们想要Core ML模型所接受的输入和输出,让我这样说:电脑只能理解数字。因此,如果不添加这两个参数,我们的Core ML模型将仅接受数字做为输入和输出,而不是图像和字符串做为输入和输出。

现在,你可以按下ENTER并且休息一下,根据你机器的计算能力,转换器运行需要一些时间,当转换器运行完成时,你将会看到一个简单的>>>

现在Caffe模型已经被转换,你需要将它保存下来,请输入下列所示的程式码

coreml_model.save('Flowers.mlmodel')

.mlmodel文件将保存在当前文件夹/目录中。

然后将.mlmodel拖入文件中, 可以直接使用

CoreML官方文档

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

推荐阅读更多精彩内容