iOS
画像処理
機械学習
Swift
coreML

Visionなしで、Core ML単体で使う

Appleのサンプルを含め、Web上で見つかる記事のほとんどがCore MLのモデルを利用する際、Visionを併用した実装となっています。純正フレームワークとはいえ、不要な依存は少ないに越したことはありません。Visionを使わず、Core MLだけで実装するとしたらどんな感じになるんだろう、Visionはどのあたりを楽にしてくれているのか?ということに興味があり、やってみました。

比較として、以下の記事に書いたVisionを用いた実装と併記してみます。使用モデルはInception v3です。

モデル読み込み

Visionあり
private let model = try! VNCoreMLModel(for: Inceptionv3().model)
Visionなし
private let model = Inceptionv3()

Visionありの場合は.mlmodelから自動生成されたクラスからMLModelオブジェクトを取り出し、VNCoreMLModelを生成しましたが、Core ML単体のときは自動生成クラスをそのまま使います。(そのクラスに自動で生やされたメソッドを使いたいので)

画像入力

Visionあり
let handler = VNImageRequestHandler(cgImage: cgImage)
Visionなし
let input = Inceptionv3Input(image: pixelBuffer)

Visionありの場合は、リクエストハンドラを生成する際に入力画像を渡します。引数に渡せる入力画像の型はCVPixelBuffer, CGImage, CIImage, URL, Data等。

Visionなしの場合は、自動生成された入力型を生成します。初期化時に引数に渡せる入力画像の型はCVPixelBufferのみ。

これだけ見ると大して違いはなさそうですが、実は、Visionを使わない場合には自分でモデルの入力に合わせてリサイズを行う必要があります。

たとえば、Inceptionv3.mlmodelから自動生成された入力型Inceptionv3Inputには次のようなコメントがあります。

Inceptionv3.swift
/// Input image to be classified as color (kCVPixelFormatType_32BGRA) image buffer, 299 pixels wide by 299 pixels high

つまり、UIImageなりCGImageなりの手元にある画像データを、299x299にリサイズし、CVPixelBufferに格納する必要があります。(このあたりからVisionのありがたみがわかってきます)

このあたりの面倒さを助けてくれるヘルパークラスの集合として、

というものを見つけました。

こんな感じでUIImageのリサイズ 〜 CVPixelBufferへの変換を簡単に書けるようになります。

guard let pixelBuffer = image.pixelBuffer(width: 299, height: 299) else {fatalError()}

ちなみにこのヘルパーライブラリのリサイズ機能、いまのところは単純にスケールされてしまうので、アスペクトを維持したい場合には多少自分でコードを書く必要があります。

一方VisionのVNCoreMLRequestを使う場合はimageCropAndScaleOptionという便利オプションがあります。

推論処理の実行

Visionあり
try! handler.perform([request])
Visionなし
let output = try! model.prediction(input: input)

こんな感じで、これだけ見るとあまり変わらなそうですが、Visionを使う場合は、

  • VNCoreMLRequestにはpreferBackgroundProcessingでバックグラウンド処理を指定することもできる
  • perform()は複数のVNCoreMLRequestを引数に取ることができる

等々、並列処理を楽にさせてくれる機能があります。

結果の取得

Visionあり
guard let results = request.results as? [VNClassificationObservation] else {return}

results.forEach({ (result) in
    print("\(result.identifier) \(result.confidence * 100)")
})
Visionなし
print(output.classLabel)      // 1位候補のラベル
print(output.classLabelProbs) // 各クラスのラベルと確率

Visionなしの方のプロパティは、モデルから自動生成される出力型Inceptionv3Outputに生えているものなので、モデル依存です。

さらに、classLabelProbsの方は、結果がソートされていません。

Visionを使うメリット

というわけで、Core MLモデルを使った推論を行う際に、Core ML単体での実装と、Visionを併用する場合の実装の比較を行ってみました。

ここから見えてきたVisionを使うことのメリットとしては、

  • モデルに(比較的)依存しない処理が書ける
  • 入力画像のサイズやチャンネル数を気にしなくてよい
  • 並列処理とかが簡単にできる

といったあたりでしょうか。