iOS
機械学習
Swift

iOS11のVision.frameworkを使ってみる

iOS11から追加された、Vision.frameworkを使ってみた時に調べた内容のメモです。
機械学習の簡単な概要にも触れつつ、Visionを用いたカメラ画像を判別するサンプルアプリを作成します。

サンプルアプリ

Visionの「機械学習による画像分析」機能を利用して、以下のサンプルアプリをつくります。
このサンプルアプリは、カメラで映した画像を判別し、モノの名前を表示します。

デモ動画
VisionDemo.gif

Vision.frameworkとは

  • iOS11から追加された画像認識APIを提供するフレームワーク
  • 同じくiOS11から追加された機械学習フレームワークのCore MLを抽象化

機械学習スタック

iOSの機械学習フレームワークを利用したアプリケーションスタックは以下のような図になります。

スクリーンショット 2017-08-27 22.03.26.png

補足:ニューラルネットワークとは

  • 機械学習手法の一種
  • 人間の脳の神経回路網を 数式モデルで表したもの
  • NNと略される (DNN1、RNN2、CNN3など)

20161006215219.png

補足:機械学習による画像認識の流れ
機械学習を利用したアプリケーションを開発するには、以下のように機械学習モデルを構築する必要があります。

  1. 学習のため画像データを収集(教材を集める)
  2. 学習用データから、機械学習アルゴリズムによりモデルを作成
  3. 学習済みモデルを用いて未知の画像を判別(実践)

Visionで認識できるもの

Visionでの画像認識は、Appleが既に画像認識モデルを用意したもの(1)と、
開発者がモデルを用意する必要のあるもの(2)があります。
今回はモデルを用意し、「機械学習による画像分析」機能を利用します。

(1)機械学習モデルの用意が不要なもの

  • 顔検出 / Face Detection and Recognition
  • バーコード検出 / Barcode Detection
  • 画像の位置合わせ / Image Alignment Analysis
  • テキスト検出 / Text Detection
  • 水平線検出 / Horizon Detection

(2)機械学習モデルの用意が必要なもの

  • オブジェクト検出とトラッキング / Object Detection and Tracking
  • 機械学習による画像分析 / Machine Learning Image Analysis

画像認識モデルの用意

今回は簡単のため、Appleのサイトで配布されている学習済みモデルを利用します。
https://developer.apple.com/machine-learning/

配布モデル一覧
モデルによって得意な画像の種類や容量が異なる
(5MB〜553.5MB)

  • MobileNets
  • SqueezeNet
  • Places205-GoogLeNet
  • ResNet50
  • Inception v3
  • VGG16

特に理由はありませんが、ResNet50を利用してみました。

ResNet50
- 樹木、動物、食物、乗り物、人などの1000種類のカテゴリ
- サイズは102.6 MB
- MITライセンス

モデルをXcodeプロジェクトに組み込む

  • モデルとして用意した.mlmodelファイルをXcodeにドラッグ&ドロップするだけ
  • 自動でモデル名.swiftという名前でモデルクラスが作成される

スクリーンショット 2017-08-27 22.39.08.png

Resnet50.swift(一部抜粋)
スクリーンショット 2017-08-27 22.40.58.png

カメラ画像をキャプチャする処理を実装

AVFoundationを利用し、カメラ画像をキャプチャ
    // カメラキャプチャの開始
    private func startCapture() {
        let captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo

        // 入力の指定
        guard let captureDevice = AVCaptureDevice.default(for: .video),
            let input = try? AVCaptureDeviceInput(device: captureDevice),
            captureSession.canAddInput(input) else {
                assertionFailure("Error: 入力デバイスを追加できませんでした")
                return
        }

        captureSession.addInput(input)

        // 出力の指定
        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "VideoQueue"))

        guard captureSession.canAddOutput(output) else {
            assertionFailure("Error: 出力デバイスを追加できませんでした")
            return
        }
        captureSession.addOutput(output)

        // プレビューの指定
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.frame = view.bounds
        view.layer.insertSublayer(previewLayer, at: 0)

        // キャプチャ開始
        captureSession.startRunning()
    }
撮影フレーム枚に呼び出されるデリゲートメソッド
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // CMSampleBufferをCVPixelBufferに変換
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            assertionFailure("Error: バッファの変換に失敗しました")
            return
        }

    // この中にVision.frameworkの処理を書いていく
    }

Vision.frameworkで利用する主なクラス

概要をまとめると以下のようになります。

  • VNCoreMLModel(組み込んだモデル)
  • VNCoreMLRequest(画像認識のリクエスト)
  • VNImageRequestHandler(リクエストの実行)
  • VNObservation(認識結果)

VNCoreMLModel

  • CoreMLのモデルをVisionで扱うためのコンテナクラス

VNCoreMLRequest

  • CoreMLに画像認識を要求するためのクラス
  • 認識結果はモデルの出力形式により決まる
    • 画像→クラス(分類結果)
    • 画像→特徴量
    • 画像→画像

VNImageRequestHandler

  • 一つの画像に対し、一つ以上の画像認識処理(VNCoreMLRequest)を実行するためのクラス
  • 初期化時に認識対象の画像形式を指定する
    • CVPixelBuffer
    • CIImage
    • CGImage

VNObservation

  • 画像認識結果の抽象クラス
  • 結果としてこのクラスのサブクラスのいずれかが返される
  • 認識の確信度を表すconfidenceプロパティを持つ(VNConfidence=Floatのエイリアス)

VNObservationサブクラス

  • VNClassificationObservation
    分類名としてidentifierプロパティを持つ

  • VNCoreMLFeatureValueObservation
    特徴量データとしてfeatureValueプロパティを持つ

  • VNPixelBufferObservation
    画像データとしてpixelBufferプロパティを持つ

具体的な実装コード

モデルクラスの初期化
        // CoreMLのモデルクラスの初期化
        guard let model = try? VNCoreMLModel(for: Resnet50().model) else {
            assertionFailure("Error: CoreMLモデルの初期化に失敗しました")
            return
        }
画像認識リクエストを作成
        // 画像認識リクエストを作成(引数はモデルとハンドラ)
        let request = VNCoreMLRequest(model: model) { [weak self] (request: VNRequest, error: Error?) in
            guard let results = request.results as? [VNClassificationObservation] else { return }

            // 判別結果とその確信度を上位3件まで表示
            // identifierは類義語がカンマ区切りで複数書かれていることがあるので、最初の単語のみ取得する
            let displayText = results.prefix(3).compactMap { "\(Int($0.confidence * 100))% \($0.identifier.components(separatedBy: ", ")[0])" }.joined(separator: "\n")

            DispatchQueue.main.async {
                self?.textView.text = displayText
            }
        }
画像認識リクエストを実行
        // CVPixelBufferに対し、画像認識リクエストを実行
        try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])

画像認識部分の完成形

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // CMSampleBufferをCVPixelBufferに変換
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            assertionFailure("Error: バッファの変換に失敗しました")
            return
        }

        // CoreMLのモデルクラスの初期化
        guard let model = try? VNCoreMLModel(for: Resnet50().model) else {
            assertionFailure("Error: CoreMLモデルの初期化に失敗しました")
            return
        }

        // 画像認識リクエストを作成(引数はモデルとハンドラ)
        let request = VNCoreMLRequest(model: model) { [weak self] (request: VNRequest, error: Error?) in
            guard let results = request.results as? [VNClassificationObservation] else { return }

            // 判別結果とその確信度を上位3件まで表示
            // identifierは類義語がカンマ区切りで複数書かれていることがあるので、最初の単語のみ取得する
            let displayText = results.prefix(3).compactMap { "\(Int($0.confidence * 100))% \($0.identifier.components(separatedBy: ", ")[0])" }.joined(separator: "\n")

            DispatchQueue.main.async {
                self?.textView.text = displayText
            }
        }

        // CVPixelBufferに対し、画像認識リクエストを実行
        try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
    }

サンプルコード

今回ご紹介したサンプルコードはこちらに置いてあります。
https://github.com/shtnkgm/VisionFrameworkSample

参考


  1. Deep Neural Network(ディープニューラルネットワーク) 

  2. Recurrent Neural Network(再帰型ニューラルネットワーク) 

  3. Convolutional Neural Network(畳み込みニューラルネットワーク)