LoginSignup
7
12

More than 3 years have passed since last update.

Kerasで作成したモデルを変換してiOSに組み込み

Posted at

やること

前回記事(【Tensorflow・VGG16】転移学習による画像分類)で作成したモデルを使ってiPhone上で写真を撮影し、それを推定してみる。

手順概要

  1. Kerasで作成したモデル(.h5)を変換
  2. Xcodeでプロジェクトを作成
  3. 画面のパーツ作成 & コードの関連付け
  4. カメラへのアクセス許可 & カメラを起動するコードを追加
  5. ボタン(写真撮影)押下時の挙動
  6. モデルを読み込んで推定

動作環境

  • macOS Catalina 10.15 beta
  • Python 3.6.8
  • keras 2.2.4
  • coremltools 2.1.0
  • Xcode 10.2.1

1. Kerasで作成したモデル(.h5)を変換

  • 前回記事で作成したモデル(vgg16_transfer.h5)を使い、iOSで利用可能なモデルに変換する
モデルの変換
import coremltools

coreml_model = coremltools.converters.keras.convert(
    'vgg16_transfer.h5',
    input_names='image',
    image_input_names='image',
    output_names='Prediction',
    class_labels=['apple', 'tomato', 'strawberry'],
    )

coreml_model.save('./vgg16_transfer.mlmodel')

2. Xcodeでプロジェクトを作成

  • Xcodeを起動し、「Create a new Xcode project」を選択 1.png
  • 「Single View App」を選択 2.png
  • 'Product Name'は任意の名称を付ける。Languageは「Swift」、チェックボックス(Use XX, include XX)は全て外し、Nextボタンを押下する

3. 画面のパーツ作成 & コードの関連付け

  • Xcode上で下記3つの画面のパーツを追加する

    • Image View(写真を表示する領域)
    • Text View(推定結果を表示する領域)
    • Button(カメラを起動するボタン)
  • こんな感じのレイアウトにしておく
    3.png

  • 各パーツの関連付けを実施する

  • Image ViewとText Viewは「class ViewController」の下に追加(パーツを選択し、controlキーを押下しながら追加)

  • Buttonは「override func viewDidLoad()」の下に追加

  • Name(とConnection)はそれぞれ下記で設定

    • Image View : imageDisplay(Connection : Outlet)
    • Text View : predictionDisplay(Connection : Outlet)
    • Button : takePhoto(Connection : Action)
各パーツの関連付け
    # Image View
    @IBOutlet weak var imageDisplay: UIImageView!
    # Text View
    @IBOutlet weak var predictionDisplay: UITextView!
    # Button
    @IBAction func takePhoto(_ sender: Any) {
    }

4. カメラへのアクセス許可 & カメラを起動するコードを追加

  • info.plistに「Privacy - Camera Usage Description」を追加し、カメラへのアクセスを許可する
    4.png

  • 下記2つの「delegate」(イベントを検知・処理)を追加する。(カメラを表示する画面、写真を保存した時に元の画面に戻る操作を行うことが可能)

    • UIImagePickerControllerDelegate 
    • UINavigationControllerDelegate 
  • カメラの撮影画面を表示するために「imagePicker」という変数を追加

  • アプリが起動した直後に「imagePicker」を初期化するコードを「viewDidLoad()」内に追加

カメラを起動(before)
class ViewController: UIViewController {

    @IBOutlet weak var imageDisplay: UIImageView!
    @IBOutlet weak var predictionDisplay: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    # ~~~ 省略 ~~~
}
カメラを起動(after)
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var imageDisplay: UIImageView!
    @IBOutlet weak var predictionDisplay: UITextView!
    var imagePicker: UIImagePickerController!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        # 初期化
        imagePicker = UIImagePickerController()
        # delegate:イベントを受け渡しする変数
        imagePicker.delegate = self
        # sourceType : カメラからデータを撮るか、アルバムから読込か -> 今回はカメラ
        imagePicker.sourceType = .camera
    }

    # ~~~ 省略 ~~~
}

5. ボタン(写真撮影)押下時の挙動

  • 4.で作成しておいたimagePickerを表示するため、present関数を「takePhoto」で呼び出す
before
    @IBAction func takePhoto(_ sender: Any) {
    }
after
    @IBAction func takePhoto(_ sender: Any) {
        present(imagePicker, animated: true, completion: nil)
    }
  • 写真を撮り終わった後の処理を記載(imagePickerController)
  • imageDisplayのimage属性を更新する
  • infoに撮影した画像が入っているため、それを取り出してimageDisplayのimage属性にセットする
  • infoのUIImagePickerController.InfoKeyに各種属性が入っているため、「originalImage」というプロパティを設定する('as? UIImage'でタイプを指定する)
  • 画像を設定したら、先程のImagePickerを閉じる(dismiss)
after
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
        imagePicker.dismiss(animated: true, completion: nil)
    }

6. モデルを読み込んで推定

  • mlmodelを読み込みして、そのモデルに画像ファイルを渡して推定する
  • 1.で変換したモデル(vgg16_transfer.mlmodel)をXcodeのプロジェクトにドラッグ・アンド・ドロップする

7.png

8.png

  • 下記ライブラリを追加する
    • CoreML : 機械学習のライブラリ
    • Vision : 画像ファイルを扱うライブラリ
ライブラリの追加
import CoreML
import Vision
  • 推定処理を加える
  • imagePickerControllerの後ろに推定処理(imagePrediction)を加える
  • 引数に画像ファイルを指定(画面上に表示した内容と同じ(imageDisplay.image))
before
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
        imagePicker.dismiss(animated: true, completion: nil)
    }
after
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
        imagePicker.dismiss(animated: true, completion: nil)
        imagePrediction(image: (info[UIImagePickerController.InfoKey.originalImage] as? UIImage)!)
    }
  • 推定処理(imagePrediction)の関数を作成
  • モデルの読み込み
モデルの読み込み
    func imagePrediction(image: UIImage) {
        guard let model = try? VNCoreMLModel(for: vgg16_transfer().model) else {
            fatalError("Model not found")
        }
  • VNCoreMLRequestのインスタンスを生成
  • 推定結果をresultsに格納する(推定結果は「VNClassificationObservation」という変数に入って返ってくる)
  • results.firstにスコアの高いデータが格納されている
  • predictionDiaplayに推定結果を表示する
    • firstResult.confidence : 確率(少数で入っているので、100倍して%表示)
    • firstResult.identifier : ラベル(推定結果)
モデルによる推定処理
        let request = VNCoreMLRequest(model: model) {
            [weak self] request, error in

            guard let results = request.results as? [VNClassificationObservation],
                let firstResult = results.first else {
                    fatalError("No results found")
            }

            DispatchQueue.main.async {
                self?.predictionDisplay.text = "Accuracy: = \(Int(firstResult.confidence * 100))% \n\nラベル: \((firstResult.identifier))"
            }
        }
  • requestに対する処理を記載
  • ciImageというデータ型に変換できないと推定処理が出来ないため、取得出来ない場合はエラー
  • Visionフレームワークで画像を使うためのimageHandlerという変数を宣言し、VNImageRequestHandlerを生成
  • imageHandlerにrequestを実行させる
        guard let ciImage = CIImage(image: image) else {
            fatalError("Can't convert image.")
        }

        let imageHandler = VNImageRequestHandler(ciImage: ciImage)

        DispatchQueue.global(qos: .userInteractive).async {
            do {
                try imageHandler.perform([request])
            } catch {
                print("Error")
            }
        }
  • コードの全体は以下の通り
ViewController.swift
import UIKit
import CoreML
import Vision

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var imageDisplay: UIImageView!
    @IBOutlet weak var predictionDisplay: UITextView!
    var imagePicker: UIImagePickerController!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        imagePicker = UIImagePickerController()
        imagePicker.delegate = self
        imagePicker.sourceType = .camera
    }
    @IBAction func takePhoto(_ sender: Any) {
        present(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
        imagePicker.dismiss(animated: true, completion: nil)
        imagePrediction(image: (info[UIImagePickerController.InfoKey.originalImage] as? UIImage)!)
    }

    func imagePrediction(image: UIImage) {
        guard let model = try? VNCoreMLModel(for: vgg16_transfer().model) else {
            fatalError("Model not found")
        }

        let request = VNCoreMLRequest(model: model) {
            [weak self] request, error in

            guard let results = request.results as? [VNClassificationObservation],
                let firstResult = results.first else {
                    fatalError("No results found")
            }

            DispatchQueue.main.async {
                self?.predictionDisplay.text = "Accuracy: = \(Int(firstResult.confidence * 100))% \n\nラベル: \((firstResult.identifier))"
            }
        }

        guard let ciImage = CIImage(image: image) else {
            fatalError("Can't convert image.")
        }

        let imageHandler = VNImageRequestHandler(ciImage: ciImage)

        DispatchQueue.global(qos: .userInteractive).async {
            do {
                try imageHandler.perform([request])
            } catch {
                print("Error")
            }
        }
    }
}

ソースコード

https://github.com/hiraku00/ios_camera
('vgg16_transfer.h5'と'vgg16_transfer.mlmodel'は除外)

参考文献

7
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
12