美文网首页
CoreML 与Vision

CoreML 与Vision

作者: Roct | 来源:发表于2018-07-03 15:40 被阅读60次

    本文环境为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官方文档

    相关文章

      网友评论

          本文标题:CoreML 与Vision

          本文链接:https://www.haomeiwen.com/subject/gwaduftx.html