Help us understand the problem. What is going on with this article?

iOS11のVision.frameworkを使ってみる

More than 1 year has passed since last update.

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした