ARKitで姿勢検出
モーションキャプチャ
ARKit 3(iOS 13)から、モーションキャプチャ機能が追加され、現実の人物の全身の動きを取得できるようになりました。これにより、人体の特定の部位に仮想オブジェクトやエフェクトを表示したり、現実の人物の動きと同じように3Dモデルをアニメーションさせたりといったことが可能になります。
他にもモーション認識に使用したり、スポーツにおける動きの分析、バーチャルオブジェクトとのインタラクションといった多様なユースケースが考えられます。
本機能はA12以降(iPhone XS・XR移行)のデバイスで利用可能です。
2D Body Detection
ARKitにおけるモーションキャプチャの実現方法のひとつが、本項で解説する「2D Body Detection」です。人体を2次元的に検出します。
2D Body Detectionの実装方法
2D Body Detectionの実装は非常に簡単です。ARConfiguration
クラスにframeSemantics
というARConfiguration.FrameSemantics
型のプロパティがiOS 13で追加されたので、
var frameSemantics: ARConfiguration.FrameSemantics
ここに、bodyDetection
を指定します。
configuration.frameSemantics = .bodyDetection
これだけの追加実装で、人体を検出できるようになります。
detectedBody, ARBody2D
検出した人体の情報は、ARFrame
のdetectedBody
プロパティよりARBody2D
型で取得できます。
var detectedBody: ARBody2D? { get }
ARBody2D
は、2Dで人体を表すためのクラスです。ARSkeleton2D
型のskeleton
プロパティを持ちます。
var skeleton: ARSkeleton2D { get }
ARSkeleton2D
ARSkeleton2D
は、2Dで人体の骨格(スケルトン)を表すクラスです。ARSkeleton
クラス[1]を継承しています。
人体の骨格を形成する17個のジョイントのスクリーン空間における位置を表す2次元座標を、jointLandmarks
プロパティにsimd_float2
型の配列で保持しています。座標は原点を左上として、(0.0, 0.0)
〜(1.0, 1.0)
に正規化されています。
@nonobjc var jointLandmarks: [simd_float2] { get }
ARSkeleton2Dのジョイントランドマーク
また、landmark(for:)
メソッドにARSkeleton.JointName
型(次項で解説します)でジョイント名を指定することで、そのジョイントの2次元座標を取得することもできます。
@nonobjc func landmark(for jointName: ARSkeleton.JointName) -> simd_float2?
ARSkeleton.JointName
ARSkeleton.JointName
は骨格のジョイント名を表す構造体です。本構造体はARSkeleton2D
だけでなく、後述するARSkeleton3D
でも共通で使用します。
以下の型プロパティが定義されています。
static let head: ARSkeleton.JointName
static let leftFoot: ARSkeleton.JointName
static let leftHand: ARSkeleton.JointName
static let leftShoulder: ARSkeleton.JointName
static let rightFoot: ARSkeleton.JointName
static let rightHand: ARSkeleton.JointName
static let rightShoulder: ARSkeleton.JointName
static let root: ARSkeleton.JointName
2Dの場合は合計17個のジョイントが検出されますが、全てのジョイント名がARSkeleton.JointName
で定義されているわけではないことがわかります。
ARSkeleton2Dにおいて各JointNameが示すジョイントの位置
ARSkeletonDefinition
人体の骨格を可視化する際に、ジョイントの接続関係を知りたい場合があります。ジョイントの接続関係を調べるには、ARSkeleton
クラス(ARSkeleton2D
,ARSkeleton3D
の親クラス)に定義されているdefinition
プロパティから得られるARSkeletonDefinition
オブジェクトを使用します。
var definition: ARSkeletonDefinition { get }
ARSkeletonDefinition
クラスは次のように各ジョイントの「親」となるジョイントのインデックスを格納したparentIndices
プロパティを持っており、
@nonobjc public var parentIndices: [Int] { get }
次のように、ジョイントの接続関係をたどることができます。
let definition = skeleton.definition
let joints = skeleton.jointLandmarks
joints.enumerated().forEach { jointIndex, jointPosition in
// 親ジョイントのインデックスを取得
let parentIndex = definition.parentIndices[jointIndex]
// Rootの親ジョイントは存在しないため、インデックスとして-1が返ってくる
guard parentIndex != -1 else { return }
// 親ジョイントの位置を取得
let parentJoint = joints[parentIndex]
}
本クラスも、2Dだけではなく、後述する3Dボディトラッキングでも使用します。
利用可能なコンフィギュレーション
前述の通りbodyDetection
はARConfiguration
のframeSemantics
プロパティにセットするわけですが、どのコンフィギュレーションクラスに対しても適用できるわけではありません。サポートしていないARConfiguration.FrameSemantics
の型プロパティをセットすると、実行時エラーとなりアプリが停止します。
2D Body Detectionを利用可能なコンフィギュレーションは、以下の4つです[2]。
ARWorldTrackingConfiguration
AROrientationTrackingConfiguration
ARImageTrackingConfiguration
ARBodyTrackingConfiguration
bodyDetection と personSegmentation は併用可能か?
personSegmentation
あるいは〜WithDepth
を使用するとカメラフレーム画像の各々のピクセルが人物を表す一部であるかそうでないか、という情報は得ることができますが、画像の中で頭はどこにあるか、手はどこにあるか、といった情報は得られません。そこでbodyDetection
を併用したい、というケースがあるかもしれません。幸いARConfiguration.FrameSemantics
はOptionSet
に準拠しているので、次のように両方を指定してもビルドは通ります。
configuration.frameSemantics = [.personSegmentation, .bodyDetection]
しかし、残念ながら実行時に次のようなエラーで停止してしまいます。結論として、bodyDetection
とpersonSegmentation
の併用はできません。
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'This set of frame semantics is not supported on this configuration'
3D Body Tracking
ARKitにおいてモーションキャプチャの実現するもうひとつの方法が、本項で解説する「3D Body Tracking」です。3次元的に人体を検出し、トラッキングします。
2D Body Detectionではワールドトラッキング等のコンフィギュレーションにframe semanticとして機能を付加する形で利用しましたが、こちらはARBodyTrackingConfiguration
という専用のコンフィギュレーションを使用します。
ARBodyTrackingConfiguration
ARBodyTrackingConfiguration
は、3D空間で人体の動きをトラッキングするコンフィギュレーションです。
初期化し、ARセッションに渡してrun
すればボディトラッキングが開始します。
let configuration = ARBodyTrackingConfiguration()
sceneView.session.run(configuration)
なお、frameSemantics
プロパティにはデフォルトでbodyDetection
がセットされています。
平面検出と画像検出の利用
ARBodyTrackingConfiguration
では平面検出と画像検出も利用可能です。トラッキング中の人体の動きに合わせて仮想キャラクターを動かす場合に、検出した平面や画像の上にキャラクターを設置するといった使い方が可能です。
平面検出にはplaneDetection
プロパティを利用します。使用方法はARWorldTrackingConfiguration
の同名プロパティと同様[3]です。
var planeDetection: ARWorldTrackingConfiguration.PlaneDetection
また画像検出/トラッキングには以下のプロパティが用意されています。こちらも使い方はARWorldTrackingConfiguration
の同名プロパティと同様[4]です。
var detectionImages: Set<ARReferenceImage>
var maximumNumberOfTrackedImages: Int
ボディトラッキング時にでPeople Occlusionは利用可能か?
人体の3D骨格情報を利用して何か仮想オブジェクトを表示する際に、人体の輪郭に合わせてオクルージョンしたいケースがあるかもしれません。しかしこちらで示した通り、personSegmentation
及びpersonSegmentationWithDepth
はARBodyTrackingConfiguration
では使用できません。
ARBodyAnchor
ボディトラッキングを開始し、人体が検出されると、ARBodyAnchor
オブジェクトがアンカーのリストに追加されます。
このARBodyAnchor
は、他のアンカークラスと同様にARAnchor
のサブクラスですので、検出されたアンカーオブジェクトにアクセスする方法も同様です。ボディトラッキングが動作することをシンプルに確認するために、検出したボディアンカーの位置に球体ノードを設置してみましょう。
func renderer(_ renderer: SCNSceneRenderer,
didAdd node: SCNNode, for anchor: ARAnchor) {
if let bodyAnchor = anchor as? ARBodyAnchor {
let sphereNode = SCNNode.sphereNode(color: UIColor.blue)
node.addChildNode(sphereNode)
}
}
ボディアンカーの位置にブルーの球体オブジェクトを設置
ARSkeleton3D
ARBodyAnchor
はARSkeleton3D
型のskeleton
プロパティを持ち、ここから、トラッキング中の人体の3次元骨格情報が取得できます。
var skeleton: ARSkeleton3D { get }
ARSkeleton3D
は、3Dで人体の骨格(スケルトン)を表すクラスです。先に解説したARSkeleton2D
と同様にARSkeleton
クラスを継承しています。
ARSkeleton2D
のジョイント数は17個でしたが、ARSkeleton3D
はなんと91個ものジョイントから構成されます。
ARSkeleton3Dの91個のジョイント
ARSkeleton3D
はjointModelTransforms
プロパティを持ち、各ジョイントのモデル空間[5]におけるTransform[6]をsimd_float4x4
型の配列で保持しています。
@nonobjc public var jointModelTransforms: [simd_float4x4] { get }
またjointLocalTransforms
プロパティには、各ジョイントのローカル空間[7]のTransformをsimd_float4x4
型の配列で保持しています。
@nonobjc public var jointLocalTransforms: [simd_float4x4] { get }
modelTransform(for:)
メソッド、あるいはlocalTransform(for:)
メソッドにARSkeleton.JointName
型(2Dの項で解説済み)でジョイント名を指定することで、そのジョイントのTransformを表す4x4行列を取得することもできます。
@nonobjc public func modelTransform(for jointName: ARSkeleton.JointName)
-> simd_float4x4?
@nonobjc public func localTransform(for jointName: ARSkeleton.JointName)
-> simd_float4x4?
ARSkeleton3Dにおいて各JointNameが示すジョイントの位置
ジョイントの接続関係も、2Dの場合と同様にskeleton
プロパティから取得できるARSkeletonDefinition
オブジェクト(2Dの項で解説済み)を使用し、親子関係を利用してたどることができます。
Root以外のジョイントは親ジョイントを持つ
let definition = skeleton.definition
let joints = skeleton.jointModelTransforms
joints.enumerated().forEach { jointIndex, jointTransform in
// 親ジョイントのインデックスを取得
let parentIndex = definition.parentIndices[jointIndex]
// Rootの親ジョイントは存在しないため、インデックスとして-1が返ってくる
guard parentIndex != -1 else { return }
// 親ジョイントの位置を取得
let parentJoint = joints[parentIndex]
}
ARSkeleton3Dを可視化
「つくりながら学ぶ」ARKitの入門書
ARKitの本を書いて個人で出版しました。
はじめの一歩として3行で書ける最小実装のARから始めて、平面を検出する方法、その平面に仮想オブジェクトを設置する方法、そしてその仮想オブジェクトとインタラクションできるようにする方法・・・と、読み進めるにつれて「作りながら」引き出しが増えていき、最終的にはARKitを用いたメジャーや、空間に絵や文字を描くといった、ARKitならではのアプリケーションの実装ができるよう構成しています。
全146ページ。サンプルコードはGitHubよりダウンロード可能です。BOOTHにて販売中。
-
ARSkeleton
クラスは、後述するARSkeleton3D
の親クラスでもあります。 ↩︎ -
どのコンフィギュレーションでどのframe semanticをサポートしているかを調べるには、
ARConfiguration
のsupportsFrameSemantics(_:)
メソッドを利用できます。本メソッドについては「ARKitのPeople Occlusion」で解説しています。 ↩︎ -
「実践ARKit」参照。 ↩︎
-
「実践ARKit」参照。 ↩︎
-
このモデル空間はRootジョイント(お尻)を原点とします。 ↩︎
-
位置・回転・スケール情報を保持する4x4行列。 ↩︎
-
このローカル空間は親ジョイントを原点とします。 ↩︎
Discussion