ARKitとデプス
ARKitにおけるデプス取得方法
ARセッション中に毎フレーム得られるARFrame
に、capturedDepthData
というプロパティがあり、ここからAVDepthData
オブジェクトが得られます。
var capturedDepthData: AVDepthData? { get }
AVDepthData
はデプスデータを表すクラスで、ARKitに限らずAVFoundationやImage I/O等々、iOSにおいてどういうフレームワークでデプスを取得するにせよ、(ほとんどの場合は)最終的にデプスデータをこの型で得ることになります。
というわけでこのプロパティにアクセスすればデプスが得られます。
guard let frame = sceneView.session.currentFrame else { return }
let depthData = frame.capturedDepthData
以上!非常にシンプルですね。
AVDepthData
上述したとおり、AVDepthData
はデプスデータを表すクラスで、iOS 11以降で利用可能です。ARKitのクラスではなく、AVFoundationフレームワークに属します。
AVDepthData
は多くのプロパティやメソッドを持ちますが、
最も重要なのはdepthDataMap
プロパティです。
var depthDataMap: CVPixelBuffer { get }
このプロパティはデプスマップのピクセルデータをCVPixelBuffer
型で保持します。
CVPixelBuffer
はiOS 4の頃から存在し、多くの画像を扱うフレームワークがこの型をサポートしているので、デプスデータをAVDepthData
として取得してしまえば、
あとはそのデプスマップをCore ImageでもMetalでも、従来の画像処理方法で好きなように処理可能です。
制約
フェイストラッキング時のみ利用可能
現状ではARFaceTrackingConfiguration
を利用してフェイストラッキングを行っている場合のみこのデプスデータが取得できます。
他のコンフィギュレーション(たとえば平面検出等を行うARWorldTrackingConfiguration
)利用時にはARFrame
のcapturedDepthData
プロパティは常にnil
になります。
毎フレーム更新されるわけではない
デプスカメラのフレームレートはカラーカメラのフレームレートよりも遅いので、毎フレーム更新されるわけではありません。当該フレームにおいてデプスデータが得られない場合にも同プロパティはnil
になる可能性があります。
デプスを描画してみる
ARKitで取得したデプスをMTKView
に描画してみます。
上述したとおりAVDepthData
はCVPixelBuffer
型でデプスマップのピクセルデータを保持しているのでいかようにも処理できるのですが、今回はCIImage
としてMetalでレンダリングすることにします。
というわけでレンダラにこんな感じのCIImage
オブジェクトを引数に取るメソッドを実装します。
func update(with ciImage: CIImage) {
let _ = inFlightSemaphore.wait(timeout: .distantFuture)
guard
let commandBuffer = commandQueue.makeCommandBuffer(),
let currentDrawable = renderDestination.currentDrawable
else {
inFlightSemaphore.signal()
return
}
commandBuffer.addCompletedHandler{ [weak self] commandBuffer in
if let strongSelf = self {
strongSelf.inFlightSemaphore.signal()
}
}
ciContext.render(ciImage, to: currentDrawable.texture, commandBuffer: commandBuffer, bounds: ciImage.extent, colorSpace: colorSpace)
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
CIContext
のrender(_:to:commandBuffer:bounds:colorSpace:)
メソッドで引数に渡されたCIImage
をレンダリングするコマンドをMetalのコマンドバッファにエンコードしているところがポイント。
AVDepthData
が持つCVPixelBuffer
型のデプスマップをCIImage
型にするために、次のようなextensionを用意しておきます。
extension CVPixelBuffer {
func transformedImage(targetSize: CGSize, rotationAngle: CGFloat) -> CIImage? {
let image = CIImage(cvPixelBuffer: self, options: [:])
let scaleFactor = Float(targetSize.width) / Float(image.extent.width)
return image.transformed(by: CGAffineTransform(rotationAngle: rotationAngle)).applyingFilter("CIBicubicScaleTransform", parameters: ["inputScale": scaleFactor])
}
}
上のメソッドを使って、CVPixelBuffer
からCIImage
オブジェクトを作成しつつ、描画サイズにリサイズしつつ、回転を補正します。
extension ARFrame {
func transformedDepthImage(targetSize: CGSize) -> CIImage? {
guard let depthData = capturedDepthData else { return nil }
return depthData.depthDataMap.transformedImage(targetSize: CGSize(width: targetSize.height, height: targetSize.width), rotationAngle: -CGFloat.pi/2)
}
}
なお、重要な点として、CIImage
のリサイズや回転処理はまだこの時点では行われず、Metalのコマンドバッファにレンダリングコマンドをエンコードし、コミットした後にGPU側で処理される、という点です。というわけでCPUとGPUを行ったり来たりせずリサイズ等の処理〜描画の一連の処理がGPU側でまとめて処理されることになります。(このあたりの話は拙著「Metal入門」の第12章に書いてあります)
ARFrame
からcapturedDepthData
が取得できたら上のメソッドを使用してCIImage
に変換しておき、
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let frame = sceneView.session.currentFrame else { return }
if let depthImage = frame.transformedDepthImage(targetSize: currentDrawableSize) {
self.depthImage = depthImage
}
}
あとはMTKView
の描画のタイミングで、CIImage
オブジェクトをレンダリングメソッドに渡します。
func draw(in view: MTKView) {
if let image = depthImage {
renderer.update(with: image)
}
}
このサンプルは「iOS-Depth-Sampler」としてオープンソースにしているのでそちらもご参照ください。
ここではシンプルに可視化しただけですが、このデプスをオクルージョンに使うもよし、エフェクトに使うもよしで、色々と活用してみてください。 1
フェイストラッキングではデプスデータを使用しているのか?
ARKitのフェイストラッキングは赤外線カメラを塞いでも動作します。なので、赤外線カメラ(すなわちそこから得られるデプス)を使用してないのでは、という説があります。
それだとフェイストラッキング時にデプスデータを取得できることと矛盾する気がしたので、実際に試してみました。挙動としては
- 赤外線カメラを塞ぐとデプスの供給が止まる
- 塞いでいてもフェイストラッキングは動作する
という感じになりました。顔のトラッキング自体はデプスなしで動作するようになっていて、開発者がオクルージョン等で使用できるようにデプスデータも提供してくれてるのでしょうか。でもARFaceTrackingConfiguration
をTrueDepthカメラあり端末に限定してるからにはやはり内部でも何かしらの用途で使用しているのでしょうか。WWDCに参加できたらラボで聞いてみたいものです。
で、拙作iOS-Depth-Samplerでデプスデータを可視化しつつ赤外線カメラを指で塞いだり指を離したりしてわかったのは、
— Shuichi Tsutsumi (@shu223) 2018年9月25日
・赤外線カメラを塞ぐとデプスの供給が止まる
・塞いでいてもフェイストラッキングは動作する
デプスが取れれば使用し、取れなくてもそれなりに動作するようになってるっぽい
iOSにおけるデプスについての解説がある書籍
宣伝になってしまいますが、「iOS 12 Programming」という書籍で、iOSにおけるデプスの取り扱いについて30ページに渡って解説しています。
- 5.1 はじめに
- 5.2 デプスとは?
- 5.3 Disparity(視差)とDepth(深度)
- 5.4 AVDepthData
- 5.5 iOSにおけるデプス取得方法
- 5.6 デプス取得方法1:撮影済み写真から取得
- 5.6.1 CGImageSourceオブジェクトを作成する
- デプスデータをもつ PHAsset だけを取得する
- 5.6.2 CGImageSource から Auxiliary データを取得する
- 5.6.3 Auxiliary データから AVDepthData を初期化する
- デプスマップをCIImageとして取得する
- 5.7 デプス取得方法2:カメラからリアルタイムに取得
- 5.7.1 デプスが取れるタイプの AVCaptureDevice を使用する
- 5.7.2 デプスが取れるフォーマットを指定する
- 5.7.3 セッションの出力に AVCaptureDepthDataOutput を追加する
- 5.7.4 AVCaptureDepthDataOutputDelegate を実装し、AVDepthData を取得する
- 5.7.5 AVCaptureDataOutputSynchronizer で出力を同期させる
- 5.8 デプス取得方法3:ARKitから取得
- 5.9 デプス応用1:背景合成
- 5.9.1 背景合成とは
- 5.9.2 CIBlendWithMask
- 5.9.3 デプスデータの加工
- 5.10 デプス応用2:2D写真を3D空間に描画する
- 5.11 PortraitEffectsMatte
- 5.11.1 AVPortraitEffectsMatte
- 5.11.2 PortraitEffectsMatteの取得方法
- 5.11.3 PortraitEffectsMatteの取得条件/制約
ARKitだけではなく、AVFoundationを用いてリアルタイムにカメラからデプスを取得する方法、撮影済み写真から取得する方法、とフレームワークを横断してiOSにおけるデプスの取り扱いについて網羅的に解説しています。"iOS 12"とタイトルにありますが、iOS 13が出ても14が出ても役立つ内容になっているので、デプスに興味のある方はぜひぜひご検討ください。Amazonや書店では販売しておらず、以下のPEAKS社のサイトより購入できます。
-
ARKitで取得したデプスをMetalシェーダに食わせてエフェクトをつくる、という案件は実際にやったことがあるのですが残念ながら非公開 ↩
Author And Source
この問題について(ARKitとデプス), 我々は、より多くの情報をここで見つけました https://qiita.com/shu223/items/cd7b157ffee4ed363f1e著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .