Appleのサンプルを含め、Web上で見つかる記事のほとんどがCore MLのモデルを利用する際、Visionを併用した実装となっています。純正フレームワークとはいえ、不要な依存は少ないに越したことはありません。Visionを使わず、Core MLだけで実装するとしたらどんな感じになるんだろう、Visionはどのあたりを楽にしてくれているのか?ということに興味があり、やってみました。
比較として、以下の記事に書いたVisionを用いた実装と併記してみます。使用モデルはInception v3です。
モデル読み込み
private let model = try! VNCoreMLModel(for: Inceptionv3().model)
private let model = Inceptionv3()
Visionありの場合は.mlmodel
から自動生成されたクラスからMLModel
オブジェクトを取り出し、VNCoreMLModel
を生成しましたが、Core ML単体のときは自動生成クラスをそのまま使います。(そのクラスに自動で生やされたメソッドを使いたいので)
画像入力
let handler = VNImageRequestHandler(cgImage: cgImage)
let input = Inceptionv3Input(image: pixelBuffer)
Visionありの場合は、リクエストハンドラを生成する際に入力画像を渡します。引数に渡せる入力画像の型はCVPixelBuffer
, CGImage
, CIImage
, URL
, Data
等。
Visionなしの場合は、自動生成された入力型を生成します。初期化時に引数に渡せる入力画像の型はCVPixelBuffer
のみ。
これだけ見ると大して違いはなさそうですが、実は、Visionを使わない場合には自分でモデルの入力に合わせてリサイズを行う必要があります。
たとえば、Inceptionv3.mlmodelから自動生成された入力型Inceptionv3Input
には次のようなコメントがあります。
/// 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
という便利オプションがあります。
BGR
や RGB
といった color_layout
の処理
モデルによってはRGBではなくBGR画像を入力として想定しているものもあります。
そういった色チャンネルの並び順はCore MLモデルフォーマットでは color_layout
として指定できるようになっています。
Vision の VNImageRequestHandler
を通してCore MLモデルに画像を渡す場合は、この color_layout
を踏まえてよしなにやってくれます 1。
推論処理の実行
try! handler.perform([request])
let output = try! model.prediction(input: input)
こんな感じで、これだけ見るとあまり変わらなそうですが、Visionを使う場合は、
-
VNCoreMLRequest
にはpreferBackgroundProcessing
でバックグラウンド処理を指定することもできる -
perform()
は複数のVNCoreMLRequest
を引数に取ることができる
等々、並列処理を楽にさせてくれる機能があります。
結果の取得
guard let results = request.results as? [VNClassificationObservation] else {return}
results.forEach({ (result) in
print("\(result.identifier) \(result.confidence * 100)")
})
print(output.classLabel) // 1位候補のラベル
print(output.classLabelProbs) // 各クラスのラベルと確率
Visionなしの方のプロパティは、モデルから自動生成される出力型Inceptionv3Output
に生えているものなので、モデル依存です。
さらに、classLabelProbs
の方は、結果がソートされていません。
Visionを使うメリット
というわけで、Core MLモデルを使った推論を行う際に、Core ML単体での実装と、Visionを併用する場合の実装の比較を行ってみました。
ここから見えてきたVisionを使うことのメリットとしては、
- モデルに(比較的)依存しない処理が書ける
- 入力画像のサイズやチャンネル数やチャンネル並び順(BGR等)を気にしなくてよい
- 並列処理とかが簡単にできる
といったあたりでしょうか。
追記
MLModel
オブジェクトを利用すれば、Visionを使わない場合でもモデルに依存せず処理をかけます。というわけで上述のいくつかの部分は修正が必要なので、時間ができたときにアップデートします。
-
ただし、画像が
CVPixelBuffer
のようにピクセルフォーマットの情報を持つ場合。UIImageおよびCGImageは、アルファチャンネルの位置やエンディアンの情報は保持できるものの、BGRかRGBかの情報は型自体には保持していないので、明示的に入れ替えたりする必要がありそう。要確認。 ↩