見出し画像

第4世代 iPad Pro の LiDARスキャナーによる実世界のオブジェクトのメッシュ化

以下の記事を参考に書いてます。

Visualizing and Interacting with a Reconstructed Scene

1. はじめに

第4世代iPad Pro(iPad OS 13.4以降)では、ARKitLiDARスキャナー を使用して、実世界のオブジェクトをメッシュ化することができます。LiDARスキャナー は、ユーザーの周囲から深度情報をすばやく取得できるため、ユーザーが移動することなく、実世界の形状推定が可能になりました。ARKitは、深度情報を頂点に変換し、メッシュを作成します。そして、複数のアンカーを作成し、それぞれにメッシュを割り当てます。

このメッシュを使用すると、次のようなことが可能になります。

・実世界のオブジェクトの表面のポイントのより正確な検出。
・実世界のオブジェクトの分類。
・より正確なオクルージョン。
・仮想コンテンツと実世界のオブジェクトのリアルな相互作用。

このサンプルは、RealityKit を使用したARエクスペリエンスを提供します。下図は、RealityKit がARKitからの実世界の情報を活用し、端末を実世界の椅子に向けた時、デバッグビジュアライゼーションを作成する方法を示しています。

画像1

2. 実世界のオブジェクトのメッシュ化

実世界のオブジェクトのメッシュ化を有効にするには、ARWorldTrackingConfigurationsceneReconstructionmeshWithClassification を指定します。

arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification

このサンプルでは、RealityKit の ARView を使用してグラフィックをレンダリングしています。ARView には、実世界のオブジェクトのメッシュを可視化する sceneUnderstanding デバッグオプションが用意されています。

arView.debugOptions.insert(.showSceneUnderstanding)
【注意】
sceneUnderstandingは通常デバッグ目的でのみで利用します。

ARエクスペリエンスを開始するために、MainViewControllerの viewDidLoad()でセッションを開始します。

arView.session.run(configuration)

3. 平面検出

平面検出 を有効にすると、ARKitはメッシュを作成するときにその情報を考慮します。LiDARスキャナー が平面上にわずかに不均一なメッシュを生成する可能性がある場合、メッシュを滑らかにします。

平面検出 の有効によるメッシュの違いを示すために、このアプリでは、切り替えボタンを提供しています。

@IBAction func togglePlaneDetectionButtonPressed(_ button: UIButton) {
    guard let configuration = arView.session.configuration as? ARWorldTrackingConfiguration else {
        return
    }
    // 平面検出の有効化
    if configuration.planeDetection == [] {
        configuration.planeDetection = [.horizontal, .vertical]
        button.setTitle("Stop Plane Detection", for: [])
    } 
    // 平面検出の無効化
    else {
        configuration.planeDetection = []
        button.setTitle("Start Plane Detection", for: [])
    }
    arView.session.run(configuration)
}

4. 実世界のオブジェクトの表面のポイントの検出

メッシュによって、実世界のオブジェクトの表面のポイントを取得するアプリは、これまでにない精度を実現できます。メッシュを考慮することにより、レイキャストで非平面な表面や特徴のない表面(白い壁など)を検出できます。

このアプリでは、ユーザーが画面をタップした時にレイキャストします。実世界のオブジェクトの表面のポイントを取得するために、必要に応じて、ARRaycastQuery.Target.estimatedPlane (非平面および平面)および ARRaycastQuery.TargetAlignment.any(水平および垂直) を指定できます。

let tapLocation = sender.location(in: arView)
if let result = arView.raycast(from: tapLocation, allowing: .estimatedPlane, alignment: .any).first {
   // ...

画像3

ユーザーのレイキャストが結果を返すと、このアプリは交差点に小さな球を配置します。

let resultAnchor = AnchorEntity(world: result.worldTransform)
resultAnchor.addChild(sphere(radius: 0.01, color: .lightGray))
arView.scene.addAnchor(resultAnchor, removeAfter: 3)

画像3

5. 実世界のオブジェクトの分類

ARKitには、メッシュ化された実世界のオブジェクトを分類する機能があります。以下のクラスに分類されます。

・ ceiling: 天井
・ door: ドア
・ floor: 床
・ none: 分類不可
・ seat: 椅子
・ table: テーブル
・ wall: 壁
・ window: 窓

ユーザーが画面をタップし、レイキャストがメッシュ化された実世界のオブジェクトと交差する場合、このアプリは、分類結果のテキストを表示します。

画像4

ARViewの automaticallyConfigureSessiontrue の場合、RealityKitはデフォルトで分類を無効にします。サンプルでは、分類を有効にするために、sceneReconstruction に meshWithClassificationを設定しています。

arView.automaticallyConfigureSession = false
let configuration = ARWorldTrackingConfiguration()
configuration.sceneReconstruction = .meshWithClassification

このアプリは、メッシュから交差点の分類を取得しようとします。

nearbyFaceWithClassification(to: result.worldTransform.position) { (centerOfFace, classification) in
   // ...

メッシュ内の3つの頂点ごとに、面と呼ばれる三角形が形成されます。ARKitは各面に分類を割り当てるため、サンプルはメッシュを通して交差点の近くの面を検索します。面に分類がある場合、このアプリはそれを画面に表示します。

DispatchQueue.global().async {
    for anchor in meshAnchors {
        for index in 0..<anchor.geometry.faces.count {
            // 面の中心を取得して、指定された場所と比較できるようにする
            let geometricCenterOfFace = anchor.geometry.centerOf(faceWithIndex: index)

            // 面の中心を世界座標に変換
            var centerLocalTransform = matrix_identity_float4x4
            centerLocalTransform.columns.3 = SIMD4<Float>(geometricCenterOfFace.0, geometricCenterOfFace.1, geometricCenterOfFace.2, 1)
            let centerWorldPosition = (anchor.transform * centerLocalTransform).position

            // 指定された場所に5cm以内で近い分類
            let distanceToFace = distance(centerWorldPosition, location)
            if distanceToFace <= 0.05 {
                // 面の分類を取得し検索終了
                let classification: ARMeshClassification = anchor.geometry.classificationOf(faceWithIndex: index)
                completionBlock(centerWorldPosition, classification)
                return
            }
        }
    }

このサンプルは、3Dテキストを作成しています。

let textEntity = self.model(for: classification)

メッシュがテキストを隠すのを防ぐため、テキストをレイの負の方向に少しオフセットして読みやすくしています。

let rayDirection = normalize(result.worldTransform.position - self.arView.cameraTransform.translation)
let textPositionInWorldCoordinates = result.worldTransform.position - (rayDirection * 0.1)

さらに、テキストが常に画面上で同じサイズで表示されるように、カメラからのテキストの距離に基づいてスケールを適用しています。

let raycastDistance = distance(result.worldTransform.position, self.arView.cameraTransform.translation)
textEntity.scale = .one * raycastDistance

テキストを表示するために、調整された交差点のアンカーされたエンティティにテキストを配置します。これは、カメラに面するように向けられています。

var resultWithCameraOrientation = self.arView.cameraTransform
resultWithCameraOrientation.translation = textPositionInWorldCoordinates
let textAnchor = AnchorEntity(world: resultWithCameraOrientation.matrix)
textAnchor.addChild(textEntity)
self.arView.scene.addAnchor(textAnchor, removeAfter: 3)

分類が取得された面の頂点の位置を可視化するため、頂点の実際の位置に小さな球を作成します。

if let centerOfFace = centerOfFace {
    let faceAnchor = AnchorEntity(world: centerOfFace)
    faceAnchor.addChild(self.sphere(radius: 0.01, color: classification.color))
    self.arView.scene.addAnchor(faceAnchor, removeAfter: 3)
}

画像5

6. オクルージョン

オクルージョン は、カメラ視点から、仮想コンテンツの前に実世界のオブジェクトを配置する機能です。この錯覚を実現するために、RealityKitはユーザーが表示する仮想コンテンツの前にあるメッシュをチェックし、それらのメッシュによって隠されている仮想コンテンツの部分の描画を省略します。

このサンプルでは、environment.sceneUnderstandingocclusion オプションを追加することで、オクルージョンを有効にしています。

arView.environment.sceneUnderstanding.options.insert(.occlusion)

実行時に、メッシュ化された実世界の背後にある仮想テキストの描画部分を省略します。

画像6

7. 仮想コンテンツと実世界のオブジェクトの相互作用

ARKitは、実世界の正確なメッシュを提供するため、仮想コンテンツは実世界のオブジェクトとリアルに相互作用できます。 

このサンプルでは、environment.sceneUnderstandingphysics オプションを追加することで、物理シミュレーションを有効にしています。

arView.environment.sceneUnderstanding.options.insert(.physics)

仮想コンテンツがメッシュ化された実世界のオブジェクトと接触したことを検出するために、サンプルはaddAnchor(_:,removeAfter:)の衝突形状を使用して、テキストの比率を定義します。

if model.collision == nil {
    model.generateCollisionShapes(recursive: true)
    model.physicsBody = .init()
}

このアプリがオブジェクトを分類してテキストを表示すると、仮想テキストをドロップする前に3秒間待機します。 サンプルがテキストのphysicsBody.modedynamic に設定すると、テキストは落下して重力に反応します。

Timer.scheduledTimer(withTimeInterval: seconds, repeats: false) { (timer) in
    model.physicsBody?.mode = .dynamic
}

テキストが落ちると、床に着地するなど、メッシュ化された実世界のオブジェクトと衝突します。

画像7


この記事が気に入ったらサポートをしてみませんか?