search
LoginSignup
100

More than 3 years have passed since last update.

posted at

updated at

Organization

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(畳み込みニューラルネットワーク) 

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
What you can do with signing up
100