深度学习图像识别项目(下):如何将训练好的Kreas模型布置到手机中

2018-07-27 11:40:45 浏览数 (1)

AiTechYun

编辑:yxy

在上篇的博文中,你学到了如何用Keras训练卷积神经网络

今天,我们将这种经过训练的Keras模型部署到iPhone手机或者说iOS的APP中,在这里我们使用CoreML,这 是一种易于使用的Apple应用程序机器的学习框架。

回顾一下,在这个由三部分组成的系列中,我们学习了:

  1. 如何快速构建图像数据集
  2. 训练Keras和卷积神经网络
  3. 使用CoreML将我们的Keras模型部署到手机应用程序中

我今天的目标是向你展示使用CoreML将Keras模型部署到iphone手机中是多么简单。

有多简单呢?坦白的说,我不是一个的移动开发者,如果我能做到,我相信你也能做到。

你可以随意使用今天发布的代码作为你应用程序的起点。

但就我个人而言,我将继续这个系列的主题,并建立一个神奇宝贝图鉴(Pokedex)。它是一款存在于宠物小精灵世界中的设备(我一直是个口袋妖怪迷)。

使用Pokedex,你可以拍摄一个神奇宝贝的图片,Pokedex会自动识别你的宠物,提供有用的信息和资料,例如神奇宝贝的身高,体重,以及它可能拥有的特殊能力。

你也可以自由地将Keras模型替换为你自己的,过程非常简单明了。

使用CoreML在iOS上运行Keras模型

本文分为四个部分。

首先,我将介绍关于CoreML的背景,包括它是什么以及为什么我们使用它。

从那里开始,我们将编写一个脚本将我们训练 好的Keras模型从HDF5文件转换为序列化的CoreML模型 – 这是一个非常简单的过程。

接下来,我们将在Xcode中创建一个Swift项目。对于熟悉Xcode的人来说,这一步没有什么困难,但对于我来说,我必须使用在线资源学习(我不是移动专家,而且我已经很久没有使用Xcode了) 。

我认为我已经讲的足够细致了,除非你需要修改代码,否则没有必要去网上搜索。

有时,你可能想要注册Apple开发人员计划,我会在测试iPhone上的应用程序之前简略讲讲。

最后,我们将编译应用程序并将Keras模型部署到我们的iPhone和iOS上。

什么是CoreML,它的用途是什么?

在iPhone上制作CoreML深度学习计算机视觉应用程序,请遵循以下步骤:(1)收集图像,(2)使用Keras训练和保存模型,(3)转换模型文件coremltools,(4)导入将模型放入Xcode Swift应用程序中,(5)编写Swift代码以对摄像头进行帧的推断,(6)部署到iPhone!

CoreML是苹果公司开发的一个机器学习框架,其目标是让任何想要为iOS/iPhone开发一个机器学习移动应用程序的人都能轻松地集成机器学习应用程序。

CoreML支持Caffe,Keras,scikit-learn等等。

现在,你需要一个经过训练的,序列化的Keras模型文件来转换成CoreML(Xcode兼容)文件。这可能是。

如果你选择使用自己的自定义模型,则需要检查CoreML文档以确保支持你在网络内使用的层。

文档:https://developer.apple.com/documentation/coreml/converting_trained_models_to_core_ml

在那里,你需要的只是加载模型和运行推断的几行代码。

苹果公司的CoreML开发团队确实无法让它变得特别容易(值得给个五星好评)。

我是一名计算机视觉 深度学习专家,并不是应用程序开发人员

坦白的说:我不是一名移动应用程序开发人员(而且我也不会声称自己是)。

当然,我以前也开发过像ID My Pill和Chic Engine之类的应用程序 ,但移动开发并不是我的特长和爱好。实际上,这些应用程序是由PhoneGap/Cordova使用HTML、JavaScript和CSS创建的,没有任何Objective-C或Swift知识。

相反,我是一个通过并且通过计算机视觉的人。当涉及到移动应用程序时,我主要依赖易于使用的框架,例如PhoneGap / Cordova和(现在的)CoreML。

为了学习这篇博文的CoreML基础知识,我从网上其他专家开发人员那里收集了这个项目所需的知识。特别是下面这边文章:

链接:https://medium.freecodecamp.org/ios-coreml-vision-image-recognition-3619cf319d0b

这里的代码大部分都是基于此,只需要一两个小小的修改。感谢它,使这个项目成为可能!

通过CoreML和Python使Keras模型与iOS兼容

在本节中,我们使用pip安装coremltools包。

要安装 coremltools ,请确保您处于带有相关库(我们正在使用Keras)的Python虚拟环境中,然后输入以下命令:

代码语言:javascript复制
pip install coremltools

从那里,通过滚动到本博客文章的“下载”部分并下载代码,获取我的转换器脚本和相关文件。

安装完成后,打开 coremlconverter .py 并按照下列步骤操作:

代码语言:javascript复制
# import necessary packages
代码语言:javascript复制
from keras.modelsimport load_model
代码语言:javascript复制
import coremltools
代码语言:javascript复制
import argparse
代码语言:javascript复制
import pickle
代码语言:javascript复制
代码语言:javascript复制
# construct the argument parser and parse the arguments
代码语言:javascript复制
ap= argparse.ArgumentParser()
代码语言:javascript复制
ap.add_argument("-m","--model", required=True,
代码语言:javascript复制
    help="path to trained model model")
代码语言:javascript复制
ap.add_argument("-l","--labelbin", required=True,
代码语言:javascript复制
    help="path to label binarizer")
代码语言:javascript复制
args= vars(ap.parse_args())

第2-5行导入我们所需的软件包。

然后我们解析我们的命令行参数。我们有两个参数:

  • –model:在磁盘上的预训练、序列化Keras模型的路径。
  • –labelbin:我们的类标签binarizer的路径。这个文件是我们之前发布的训练CNN的文章中的scikit-learn的LabelBinarizer对象。如果你没有 LabelBinarizer对象,则需要修改代码以硬编码 class_labels集合 。

加载类标签和我们的Keras模型:

在 第17-19行,我们加载我们的类标签pickle文件,并将class_labels 作为列表存储 。

接下来,我们将训练好的Keras模型加载到一行(第23行)。

然后,我们从coremltools调用converter并将生成的模型保存到磁盘:

第27行 ,我们调用coremltools.converters.keras.convert函数。一定要参考文档中的关键参数说明。我们今天正在使用以下参数:

  • model:我们正在转换的Keras模型。实际上,你可以在这里放置一个路径 文件名,但我选择输入模型对象(API支持两种方法)。
  • input_names = “image” :引用自文档:“可以赋予Keras模型输入的可选名称。这些名称将在Core ML模型的界面中用于引用Keras模型的输入。如果未提供,Keras输入在Core ML模型中命名为[input1,input2,…,inputN]。当存在多个输入时,输入特征名称与Keras输入的顺序相同。”
  • image_input_names = “image” :从文档引用:“将名称输入可以被Core ML处理为图像Keras模型(input_names参数的子集)。所有其他输入都被处理为MultiArrays(N-D数组)。“
  • image_scale = 1 / 255.0 :这个参数非常重要。在训练网络之前,通常会将图像的像素强度缩放到[0,1]。如果你执行了此类缩放,请务必将 image_scale 参数设置为scale factor。在训练期间你可能已经完成了双重和三重检查、缩放以及预处理,并确保你在转换过程中反映了这些预处理步骤。
  • class_labels = class_labels :在这里,我们提供了我们模型所训练的类标签集。我们从LabelBinarizer对象中获得了class_label。 如果你愿意,也可以硬编码class_labels。
  • is_bgr = True :这个参数很容易忽略 。如果你的模型是使用BGR颜色通道排序进行训练的,那么将此值设置为True非常重要, 以便CoreML按预期运行。如果模型是使用RGB图像进行训练的,则可以放心地忽略此参数。如果你的图像不是BGR或RGB,请参阅文档。

我还想指出,如果您在iPhone应用程序中对查询图像执行均值减法,则可以通过参数添加红/绿/蓝/灰的偏差。例如,这对许多ImageNet模型都是必需的。 如果你需要执行此步骤,请务必参阅文档。均值减法是Python深度学习计算机视觉中常见的预处理步骤 。

我们脚本的最后一步是保存输出的CoreML protobuf模型:

Xcode预期的文件扩展名为.mlmodel 。因此,我选择使用代码而不是命令行参数来处理它,以避免可能出现的问题。

第35行将.model扩展从输入路径/文件名中删除,并将其替换为.mlmodel,将结果存储为输出。

第37行使用正确的文件名将文件保存到磁盘。

这就是这个脚本的全部内容。感谢Apple CoreML开发人员!

运行Keras到CoreML的转换脚本

我们的脚本可以通过传递两个命令行参数来执行:

  1. 模型的路径
  2. 标签binarizer的路径

准备好后,在终端中输入以下命令并根据需要查看输出:

代码语言:javascript复制
$ python coremlconverter.py--model pokedex.model--labelbin lb.pickle
代码语言:javascript复制
Using TensorFlow backend.
代码语言:javascript复制
[INFO] loadingclass labelsfrom label binarizer
代码语言:javascript复制
[INFO]class labels: ['background','bulbasaur','charmander','mewtwo','pikachu','squirtle']
代码语言:javascript复制
[INFO] loading model...
代码语言:javascript复制
[INFO] converting model
代码语言:javascript复制
0 : conv2d_1_input, <keras.engine.topology.InputLayerobject at0x11889dfd0>
代码语言:javascript复制
1 : conv2d_1, <keras.layers.convolutional.Conv2Dobject at0x1188a8048>
代码语言:javascript复制
2 : activation_1, <keras.layers.core.Activationobject at0x1188a8198>
代码语言:javascript复制
...
代码语言:javascript复制
22 : batch_normalization_6, <keras.layers.normalization.BatchNormalizationobjectat0x118b0d390>
代码语言:javascript复制
23 : dense_2, <keras.layers.core.Denseobject at0x118bac198>
代码语言:javascript复制
24 : activation_7, <keras.layers.core.Activationobject at0x118c08f28>
代码语言:javascript复制
[INFO] saving model as pokedex.mlmodel
代码语言:javascript复制
Input name(s)and shape(s):
代码语言:javascript复制
image : (C,H,W)= (3,96,96)
代码语言:javascript复制
Neural Network compiler0:100 , name= conv2d_1, output shape : (C,H,W)= (32,96,96)
代码语言:javascript复制
Neural Network compiler1:130 , name= activation_1, output shape : (C,H,W)= (32,96,96)
代码语言:javascript复制
Neural Network compiler2:160 , name= batch_normalization_1, output shape : (C,H,W)= (32,96,96)
代码语言:javascript复制
...
代码语言:javascript复制
Neural Network compiler21:160 , name= batch_normalization_6, output shape : (C,H,W)= (1024,1,1)
代码语言:javascript复制
Neural Network compiler22:140 , name= dense_2, output shape : (C,H,W)= (5,1,1)
代码语言:javascript复制
Neural Network compiler23:175 , name= activation_7, output shape : (C,H,W)= (5,1,1)

然后,列出你的目录的内容:

代码语言:javascript复制
$ ls-al
代码语言:javascript复制
total299240
代码语言:javascript复制
drwxr-xr-x@6 adrian  staff       192 Apr11 15:07 .
代码语言:javascript复制
drwxr-xr-x@5 adrian  staff       160 Apr11 15:06 ..
代码语言:javascript复制
-rw-r--r--@1 adrian  staff      1222 Apr11 11:06 coremlconverter.py
代码语言:javascript复制
-rw-r--r--@1 adrian  staff  34715389 Apr11 11:07 pokedex.mlmodel
代码语言:javascript复制
-rw-r--r--@1 adrian  staff 104214208 Mar28 06:45 pokedex.model
代码语言:javascript复制
drwxr-xr-x@4 adrian  staff       128 Apr10 08:36 xcode

…你会看到 pokedex .mlmodel,它可以直接导入到Xcode中(我们将在下一节的第4步中继续这样做)。有趣的是,你可以看到文件比原始的Keras模型小,这可能意味着CoreML在转换过程中删除了了任何优化器状态。

注意: 为了让我的Pokedex应用程序能够识别相机是面对的是“日常物品”还是神奇宝贝,我添加了一个名为“background”的类 (这样做的目的是消除误报)。然后,我使用上篇文章的代码重新训练模型。background类由从我的系统上的UKBench数据集中随机抽取的250个图像组成。

在Xcode中创建一个Swift CoreML深度学习项目

第0步: 准备开发环境

本节的第0步是在Macintosh电脑上下载并安装Xcode。如果你的Xcode版本不是至少9.0版,那么就需要升级。在某些时候,我的Xcode要升级到9.3版本来支持我的iPhone iOS 11.3。

警告:升级Xcode可能破坏计算机上的其他开发软件或环境(比如安装了OpenCV的Python虚拟环境)。请小心使用MacInCloud之类的服务,以免破坏本地开发环境。

一旦你安装并检查了正确版本的XCode,你就可以继续下去了。

第1步:创建项目

为了规整,我在我的主目录中创建一个名为xcode的文件夹,用于存放所有的xcode项目。我创建了以下目录: 〜/ adrian / xcode 。

然后,启动Xcode并创建一个“Single View App”,如图所示。

接下来,你可以随意命名项目,我将它命名为“pokedex”,如下所示。

第2步:删除storyboard

storyboard是一个视图控制器(可视化模型/视图/控制器架构)。我们将脱离简单应用程序的视图控制器。以编程方式创建视图。

继续并从左边的文件管理器中删除Main.storyboard。

在Xcode中删除Main.storyboard,我们不需要它来完成这个深度学习计算机视觉iOS应用程序。

然后,单击树中的高级应用程序名称(在我的案例中为“pokedex ”)并滚动到 “Deployment info”。擦除标有“Main Interface”的文本框的内容

第3步向info.plist添加一个元素

我们的应用程序访问相机,所以我们需要准备授权信息。这可以很容易地在info.plist中完成。

点击如图所示的“ ”按钮, 并添加Key Value。该密钥必须完全符合“Privacy – Camera Usage Description”,但值可以自定义。

为我们的info.plist添加一个“Privacy – Camera Usage Description”,因为我们的CoreML应用程序必须使用iPhone摄像头。

第4步:创建应用程序窗口和根视图控制器

尽管我们删除了storyboard,也需要一个视图。在这一步,你需要将以下代码复制并粘贴到 AppDelegate .swift中 。函数已经定义好了,你只需要粘贴即可:

代码语言:javascript复制
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey:Any]?)->Bool {
代码语言:javascript复制
    // Override pointfor customization after application launch.
代码语言:javascript复制
代码语言:javascript复制
    // create the user interface windowand make it visible
代码语言:javascript复制
    window= UIWindow()
代码语言:javascript复制
    window?.makeKeyAndVisible()
代码语言:javascript复制
代码语言:javascript复制
    // create the view controllerand root view controller
代码语言:javascript复制
    let vc= ViewController()
代码语言:javascript复制
    window?.rootViewController= vc
代码语言:javascript复制
代码语言:javascript复制
    // return true upon success
代码语言:javascript复制
    return true
代码语言:javascript复制
}

第5步:将CoreML模型文件拖放到Xcode中

在Mac上使用Finder,点击上面创建的CoreML .mlmodel文件。

然后,将其拖放到项目树中。它会自动导入并创建相关的Swift类:

第6步: 构建ViewController

打开ViewController .swift 并导入我们需要的包或框架:

第10-12行为这个项目导入了三个必需的包。

UIKit包是开发iOS应用程序视图的通用框架,容许文本,按钮,表格视图,和导航。

AVFoundation框架是iOS上的影声媒介,我们用它从相机中捕捉。

我们使用 Vision框架为我们自定义的CoreML模型分类,但这个框架容许的远不止这些。借助Vision框架,可以执行人脸检测,面部标志检测,条形码识别,特征跟踪等。

现在我们已经导入了相关的框架,下一步创建 ViewController 类(从一个文本标签开始):

在第14行, ViewController 类是在继承UIViewController和 AVCaptureVideoDataOutputSampleBufferDelegate时定义的 。真长,让人回想起我在Java编程的日子!

在这门课上,我们首先要定义一个 UILabel ,它将保存我们的类标签和相关性的概率百分比文本。 16-23行处理这一步骤。

接下来,我们将重写viewDidLoad函数:

viewDidLoad函数在视图加载之后调用。对于通过代码创建的视图控制器,这个过程是在loadView之后 。

在 第25行中,我们使用override 关键字,这样编译器就知道我们重写了继承的类函数。

由于我们重写了函数,因此我们需要调用第27行所示的父函数 。

从那里,我们建立捕获会话(第30行),然后将标签添加为子视图(第31行和第32行)。

我把下一个函数作为一个完整性的问题包括在内;然而,我们实际上不会对它做任何修改:

如果测试应用程序时遇到内存不足警告,你可以重写带有附加规则的didReceiveMemoryWarning函数。我们保持原样推出,继续进行下一步。

让我们试着使用iOS和Swift设置摄像头捕捉访问权限:

代码语言:javascript复制
func setupCaptureSession() {
代码语言:javascript复制
    // create a new capture session
代码语言:javascript复制
    let captureSession= AVCaptureSession()
代码语言:javascript复制
代码语言:javascript复制
    // find the available cameras
代码语言:javascript复制
    let availableDevices= AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices
代码语言:javascript复制
代码语言:javascript复制
    do {
代码语言:javascript复制
        // select a camera
代码语言:javascript复制
        if let captureDevice= availableDevices.first {
代码语言:javascript复制
            captureSession.addInput(try AVCaptureDeviceInput(device: captureDevice))
代码语言:javascript复制
        }
代码语言:javascript复制
    } catch {
代码语言:javascript复制
        // print an errorif the camerais not available
代码语言:javascript复制
        print(error.localizedDescription)
代码语言:javascript复制
    }
代码语言:javascript复制
代码语言:javascript复制
    // setup the video output to the screenand add output to our capture session
代码语言:javascript复制
    let captureOutput= AVCaptureVideoDataOutput()
代码语言:javascript复制
    captureSession.addOutput(captureOutput)
代码语言:javascript复制
    let previewLayer= AVCaptureVideoPreviewLayer(session: captureSession)
代码语言:javascript复制
    previewLayer.frame= view.frame
代码语言:javascript复制
    view.layer.addSublayer(previewLayer)
代码语言:javascript复制
代码语言:javascript复制
    // buffer the videoand start the capture session
代码语言:javascript复制
    captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label:"videoQueue"))
代码语言:javascript复制
    captureSession.startRunning()
代码语言:javascript复制
}

请记住,我不是iOS开发专家,但上面的代码块还不难。

首先,我们需要创建一个捕捉会话(第44行)并查询相机并检查是否有错误(第47-57行)。

然后,我们将预览图输出到屏幕的previewLayer(第60-64行)并启动会话(第67和68行)。

让我们对框架进行分类,并在屏幕上绘制标签文本

代码语言:javascript复制
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer,from connection: AVCaptureConnection) {
代码语言:javascript复制
    // load our CoreML Pokedex model
代码语言:javascript复制
    guard let model= try? VNCoreMLModel(for: pokedex().model)else {return }
代码语言:javascript复制
代码语言:javascript复制
    // run an inference with CoreML
代码语言:javascript复制
    let request= VNCoreMLRequest(model: model) { (finishedRequest, error)in
代码语言:javascript复制
代码语言:javascript复制
        // grab the inference results
代码语言:javascript复制
        guard let results= finishedRequest.results as? [VNClassificationObservation]else {return }
代码语言:javascript复制
代码语言:javascript复制
        // grab the highest confidence result
代码语言:javascript复制
        guard let Observation= results.firstelse {return }
代码语言:javascript复制
代码语言:javascript复制
        // create the label text components
代码语言:javascript复制
        let predclass= "(Observation.identifier)"
代码语言:javascript复制
        let predconfidence= String(format:"%.02f%", Observation.confidence* 100)
代码语言:javascript复制
代码语言:javascript复制
        // set the label text
代码语言:javascript复制
        DispatchQueue.main.async(execute: {
代码语言:javascript复制
            self.label.text= "(predclass) (predconfidence)"
代码语言:javascript复制
        })
代码语言:javascript复制
    }
代码语言:javascript复制
代码语言:javascript复制
    // create a Core Video pixelbuffer whichis an imagebuffer that holds pixelsin main memory
代码语言:javascript复制
    // Applications generating frames, compressingor decompressing video,or using Core Image
代码语言:javascript复制
    // canall make use of Core Video pixel buffers
代码语言:javascript复制
    guard let pixelBuffer: CVPixelBuffer= CMSampleBufferGetImageBuffer(sampleBuffer)else {return }
代码语言:javascript复制
代码语言:javascript复制
    // execute the request
代码语言:javascript复制
    try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
代码语言:javascript复制
}

虽然这对Python开发人员来说可能有点陌生,但这块没什么不可思议的。

我们在73行加载CoreML模型 。

然后,我们对给定的框架进行分类,并抓取76-79行的结果 。然后,我们可以从CoreML模型中获取第一个预测结果,并将其存储为名为Observation的对象 (第82行)。

预测的类标签可以通过Observation.identifier提取(第85行)。我们还规定confidence仅显示两位小数(第86行)。我们用这两个组件设置label 的文本(89-91行)。

最后,我们建立一个视频像素缓冲区并执行请求(97-100行)。

我们已经完成了最后的函数,然后在屏幕上指定标签的位置:

代码语言:javascript复制
    func setupLabel() {
代码语言:javascript复制
        // constrain the labelin the center
代码语言:javascript复制
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive= true
代码语言:javascript复制
代码语言:javascript复制
        // constrain the the label to50 pixelsfrom the bottom
代码语言:javascript复制
        label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant:-50).isActive= true
代码语言:javascript复制
    }
代码语言:javascript复制
}

setupLabel中的两个设置不言而喻 ,是指我们将标签设置在底部中心。

不要忘记最后标记ViewController类结束的括号!

注册Apple开发者计划

为了将项目部署到您的iPhone,要先注册Apple开发者计划。

注册后,在iPhone上接受证书。

注册过程很快,你只需等待Xcode和iPhone同步,然后再接受证书。我最终付了100美元,但你可以查看下面这篇博客文章创建免费的开发者帐户。

链接:https://9to5mac.com/2016/03/27/how-to-create-free-apple-developer-account-sideload-apps/

测试

现在我们准备编译并测试我们的深度学习应用程序!

我建议首先通过USB部署你的应用程序。如果你想与其他人分享,如果你想和别人分享,你可以利用TestFlight,然后在App Store上发布。

我们现在要使用USB。

首先,通过USB将你的iPhone插入Mac。你可能需要用你的识别码来解锁你的iPhone,当iTunes提示你信任该设备时,选择是。

然后,在Xcode菜单栏中,选择Product > Destination > Adrian’s iPhone。

然后,构建并运行,选择Product > Run 。

如果你成功了,应用程序将会在你的iPhone上自动安装和打开。此时,你可以去寻找神奇宝贝周边(卡牌,毛绒玩具或手办)。

下面是我的CoreML应用程序的实际操作:

这绝对是一个简单的应用程序,但我很自豪,手机上有这个功能,可以向朋友,口袋妖怪迷以我的读者炫耀展示。

视频内容

如果有更多时间,可以在UI上放置一个按钮,以便拍摄我在外面遇到的神奇宝贝。这个交给Swift和iOS专家吧!

兼容性说明: 此应用程序已在iPhone 6s,iPhone 7和iPhone X上用iOS 11.3进行了测试。我使用xCode 9.3构建应用程序。

总结

在今天的博客文章中,我们看到,利用CoreML框架获取训练好的Keras模型并将其部署到iPhone和iOS非常简单。

希望你看到苹果公司CoreML框架中的价值,它对苹果开发人员和机器学习工程师来说简直是福音书,因为它可以吸收深度神经网络,并输出一种基本与iPhone和iOS兼容的模型。

我们在今天的iPhone应用中使用了Swift。尽管Swift不像Python那样简单(在此,为防止个人偏见,我持保留态度),但鉴于CoreML非常简单,你可以很省力的仿造这个项目来构建你自己的应用程序。

0 人点赞