Apple公式のデプス推定Core MLモデル「FCRN-DepthPrediction」
WWDC19のタイミングで、Appleが公式に配布するCore MLモデル[1]に「FCRN-DepthPrediction」と呼ばれるデプス推定モデルが追加されました。

名前の通り、デプスをディープラーニングで推定するモデルです。これを用いることでデプスをもたない画像からもデプスデータ [2] を取得できるようになります。
複眼カメラやLiDARを搭載したスマホもずいぶん普及しましたが、世の中に出回っている大半の静止画や動画にはデプスは含まれていません。そういった画像に対してデプスデータが必要な場合に本モデルが役立ちます。
FCRN-DepthPredictionモデル
「FCRN-DepthPrediction」が、今回追加されたデプス推定のCore MLモデルの名称です。2D画像を入力すると、推定デプスマップを出力します。FCRNは"Fully Convolutional Residual Networks"の略で、本モデルのソースコードはこちらにあります。

実際のデプスと本モデルで推定したデプスの比較(FCRNのREADMEより)
Appleの公式ページで配布されている.mlmodelファイルは32ビット浮動小数点数でパラメータ(重み)を保持するFCRN.mlmodelと、16ビット浮動小数点数版のFCRNFP16.mlmodelの2種類があります。
デプス推定モデルを使用する
実際にFCRN.mlmodelを使用して、カメラから得られる画像からリアルタイムにデプス推定を行ってみましょう。
基本実装(推論処理実行まで)
FCRN.mlmodelに入力画像を渡し、推論処理を行うところまではこのモデル特有の実装はほとんどなく[3]、「いつもの」Visionを使ったCore MLモデルの推論処理の実装です。簡単に流れを紹介します。
1. VNCoreMLModelの初期化
.mlmodelから自動生成されるFCRNクラスはMLModel型のmodelプロパティを持つので、それをイニシャライザに渡してVNCoreMLModelオブジェクトを生成します。
let depthModel = try! VNCoreMLModel(for: FCRN().model)
2. VNCoreMLRequestの初期化、設定
VNCoreMLRequestのイニシャライザに手順1で生成したVNCoreMLModelオブジェクトと、推論処理完了後の処理ブロックVNRequestCompletionHandlerを渡し、初期化します。
let request = VNCoreMLRequest(
    model: depthModel, completionHandler: { request, error in
    // 推論完了後の処理
    ...
})
request.preferBackgroundProcessing = true
request.imageCropAndScaleOption = .centerCrop
3. VNImageRequestHandlerを初期化し、処理を開始する
カメラから毎フレーム取得できるCVPixelBufferオブジェクトをイニシャライザに渡してVNImageRequestHandlerオブジェクトを生成し、perform(_:)メソッドにVNRequestオブジェクト(ここでは手順2で生成したVNCoreMLRequestオブジェクト)を渡して推論処理を開始します。
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try! handler.perform([request])
以上で推論処理が実行できます。
推論結果(MLMultiArray)の処理
上述の手順で推論処理は実行されますが、完了後の処理をまだ実装していません。Core MLの推論結果は、その出力の形式に応じて処理方法が変わってきます。
1. モデルの入出力仕様を確認する
FCRN.mlmodelをプロジェクトに追加し、Xcodeで見てみると、次のように入出力仕様を確認できます。

入力として304 x 228のカラー画像(RGB画像)を受け取り、シェイプが(1 x 12 x 160)、値をDoubleで持つMLMultiArray型のデプスマップを出力することがわかります。
2. 推論結果のMLMultiArrayオブジェクトを取得する
VNCoreMLRequestのイニシャライザには処理完了後に実行するVNRequestCompletionHandlerブロックを渡すと書きました。この第1引数にはVNRequestオブジェクトが入ってきます。そして、そのresultsプロパティからは、(出力の方がMLMultiArrayの場合は)VNCoreMLFeatureValueObservationの配列が得られます。
let request = VNCoreMLRequest(
    model: depthModel, completionHandler: { request, error in    
    let results = request.results as! [VNCoreMLFeatureValueObservation]
そして、このVNCoreMLFeatureValueObservationはfeatureValueというMLFeatureValue型のプロパティを持っており、
var featureValue: MLFeatureValue { get }
このMLFeatureValueのmultiArrayValueプロパティより、MLMultiArrayオブジェクトを取り出せます。
var multiArrayValue: MLMultiArray? { get }
したがって、推論処理完了時に渡されてくるVNRequestオブジェクトから推論結果であるMLMultiArrayオブジェクトを取り出すまでの一連の処理は、次のようになります。
guard let result = 
    request.results?.first as? VNCoreMLFeatureValueObservation 
    else { return }
guard result.featureValue.type == .multiArray else { fatalError() }
let multiArray = result.featureValue.multiArrayValue!
3. MLMultiArrayをCGImageに変換する
MLMultiArrayという型は多次元を扱う型で、機械学習の文脈では便利なのですが、ここに格納されたピクセル情報をCGImageのようなiOSで一般的に用いる画像の型に変換するのは少々複雑な実装が必要になります。
そこで、ここでは「CoreMLHelpers」[4]というオープンソースのヘルパーライブラリを使用することにします。このライブラリはMLMultiArrayを画像に変換するextension等、Core MLを使う際に便利な実装が揃っています。
このヘルパーライブラリを利用して、次のようにMLMultiArrayをCGImageに変換できます。
let cgImage = multiArray.cgImage(min: min, max: max)!
あとはこれをCIImageにしてマスクとして背景合成に使用するなり、そのままMetalで描画するなりできます。

FCRN.mlmodelを用いてリアルタイムにデプス推定を行い可視化
モデルの情報・更新履歴
DeeplabV3のApple公式のCore MLモデルは2019年が初出ですが、以降もたびたび(ひっそりと)更新されています [5]。
- 
FCRN.mlmodel - 254.7MB
 
- 
FCRNFP16.mlmodel - 127.3MB
 
モデルの種類
種類・ファイルサイズはバージョンに関わらず共通。
- 
モデルバージョン: 1.1 - coremltoolsバージョン: 4.0a6
 
- 
モデルバージョン: 1.0 
モデルのバージョン
- 
iOSにおけるデプスについては拙著「Depth in Depth - iOSデプス詳解」をご参照ください。 ↩︎ 
- 
実際は入力仕様に合わせて画像をリサイズしたりクロップしたりといったことも必要なのですが、Visionを使用する場合はそのあたりをよしなにやってくれるので、基本的には入力仕様を意識する必要がありません。 ↩︎ 
- 
AppleのCore MLモデル配布サイトを見ても更新情報はまったく書かれていないので、毎年WWDC期間中やcoremltoolsのメジャーアップデートがリリースされるタイミングでダウンロードして確認しています。 ↩︎ 





Discussion