iOS
Swift
ARKit
ARCore
CloudAnchors

ARCoreのCloud AnchorsをSwift, iOSで試してみた

ARKitを使ったGraffity - ARビデオ通話 -を作っている筋肉エンジニアKBOYです。

先日のGoogle I/O 2018で発表された Cloud Anchors|ARCoreはiOSでも使えるということで、ARKitを使ったアプリを作っている身として早速触ってみました。

公式サンプルがobjcだったので、今回Swiftでオリジナルサンプルを作ってみました。以下にアップしたので参考にしてみてください。

https://github.com/kboy-silvergym/CloudAnchorsSwiftSampler

本記事では、Cloud Anchorsの概要とサンプルの解説をしたいと思います。

Cloud Anchorsとは

alt
(by Experience augmented reality together with new updates to ARCore)

現在、ARKit, ARCoreを使うと、カメラに写した世界の平面を認識し、空間を座標で表すことができます。しかし、他の端末で同じ場所を見た時に、そこが同じ場所かどうかということはわかりません。空間を座標で表し、平面を認識しただけだからです。

しかし、様々な企業が研究開発しているARCloudがあると、違う端末どうしが同じ場所を写した時に、そこが同じ場所であると認識できるようになります。

Cloud AnchorsはそのARCloudの簡易版と言えるでしょう。

空間から認識した特徴点を元にアンカーを生成し、その情報をGoogleのCloudを使って共有することができるフレームワークです。

公式で公開されているサンプルは、ドロイドくんを平面上に配置すると、同じAPIKeyを参照するアプリで、同じ場所にドロイドくんを現すことができるというものです。

Original Sample

僕が簡単に作ったオリジナルサンプルを紹介します。

飛行機をおく 同じ場所に飛行機が出現
GIF image-B938C504D902-1.gif GIF image-8D03C122CEC6-1.gif

ちょっと向き変わっちゃってますけど(笑)、こんな感じで同じ場所に物体を出現させることができます。

手順はこんな感じです。

  • 前提

    • アンカーが追加されたら飛行機出すようにする
  • 送る側

    • アンカーを追加する(飛行機を置く)
    • アンカー情報をGoogleのCloudに送る
    • GoogleのCloudから帰ってきたcloudIdentifierをdatabaseに入れる
  • 表示する側

    • databaseに入ったcloudIdentifierを受け取る
    • cloudIdentifierを使ってGoogleのCloudを検索する
    • アンカー情報を受け取ってアンカーを追加
    • アンカーが追加された場所に飛行機出てくる

解説していきます。

PlaneAnchor以外のアンカーだったら飛行機出すようにしとく

// MARK: - <#ARSCNViewDelegate#>
extension ViewController: ARSCNViewDelegate {

    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        // Basicaly, you can get only ARPlaneAnchor,
        // but this case, you can also get another anchor which is created by ARCore.
        if !(anchor is ARPlaneAnchor) {
            let scene = SCNScene(named: "art.scnassets/ship.scn")!
            return scene.rootNode.childNode(withName: "ship", recursively: false)
        }
        return nil
    }
}

基本的にARAnchor って現状ARPlaneAnchorしか配置されないので、ARPlaneAnchor じゃなかったら飛行機を配置するようにしちゃいます。1

https://developer.apple.com/documentation/arkit/aranchor

アンカー情報を送る

1.タップした平面にアンカーを追加

ViewController.swift
    // Tap Event
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else {
            return
        }
        let touchLocation = touch.location(in: sceneView)
        let hitTestResults = sceneView.hitTest(touchLocation, types: [.existingPlane, .existingPlaneUsingExtent, .estimatedHorizontalPlane])

        if let result = hitTestResults.first {
            addAnchor(transform: result.worldTransform)
        }
    }

    // add an anchor to AR Space and share the anchor data to Google Cloud
    private func addAnchor(transform: matrix_float4x4) {
        let arAnchor = ARAnchor(transform: transform)
        sceneView.session.add(anchor: arAnchor)

        do {
            _ = try gSession.hostCloudAnchor(arAnchor)
        } catch {
            print(error)
        }
    }

タップした平面にアンカーを追加し、その情報を try gSession.hostCloudAnchor(arAnchor) でGoogleのCloudに共有します。

2.session:didHostAnchorが呼ばれるので、databaseにcloudIdentifierを共有

GARSessionDelegate
    func session(_ session: GARSession, didHostAnchor anchor: GARAnchor) {
        let id = anchor.cloudIdentifier
        firebaseReference.childByAutoId().child("hosted_anchor_id").setValue(id)
    }

anchorのhostが終わると、GARSessionDelegateのsession:didHostAnchor が呼ばれて、cloudIdentifierを得ることができます。

ここではFirebaseRealtimeDatabaseでcloudIdentifierを共有していますが、何を使ってもいいです。idが別の端末と共有できればいいので。

記録されたアンカー情報を受け取って表示

1.cloudIdentifierを受け取る

ViewController.swift
    // fetch anchors from Firebase Database
    private func observeAnchors() {
        firebaseReference.observe(.value) { (snapshot) in
            guard let value = snapshot.value as? [String : Any] else {
                return
            }
            let list = value.values
            list.forEach { value in
                if let dic = value as? [String: Any],
                    let anchorId = dic["hosted_anchor_id"] as? String {
                    self.fetchedAnchorIds.append(anchorId)
                }
            }
        }
    }

サンプルでは、firebaseから受け取った情報を1回fetchedAnchorIdscloudIdentifierに入れています。。この後ボタンを押した時にそのidentifierを使って同期することを試みます。

2.cloudIdentifierからGoogle Cloudを検索

    private func resolveAnchors(){
        fetchedAnchorIds.forEach { id in
            _ = try! gSession?.resolveCloudAnchor(withIdentifier: id)
        }
        fetchedAnchorIds.removeAll()
    }

ボタンのタップをトリガーに、resolveAnchors()メソッドを呼び、try! gSession?.resolveCloudAnchor(withIdentifier: id)fetchedAnchorIds を一個ずつGoogleのCloudに投げています。

3.発見するとdidResolveが呼ばれるので、アンカーを配置する

GARSessionDelegate
    func session(_ session: GARSession, didResolve anchor: GARAnchor) {
        let arAnchor = ARAnchor(transform: anchor.transform)
        sceneView.session.add(anchor: arAnchor)
    }

空間を見回していると、発見できます。

これで、アンカーをクラウドで共有することができました。

飛行機をおく 同じ場所に飛行機が出現
GIF image-B938C504D902-1.gif GIF image-8D03C122CEC6-1.gif

注意点

ARSCNViewDelegate
// MARK: - <#ARSCNViewDelegate#>
extension ViewController: ARSessionDelegate {

    func session(_ session: ARSession, didUpdate frame: ARFrame) {
        do {
            try gSession.update(frame)
        } catch {
            print(error)
        }
    }
}

上のように、フレーム情報を常にARCoreに共有してください。そうじゃないとARCoreが空間を把握できないので。

まとめ

コードは https://github.com/kboy-silvergym/CloudAnchorsSwiftSampler に公開しました。Cloud AnchorsをSwiftで使う方の手助けになれば幸いです。

本家のサンプルよりもより無駄を削ぎ落として、CloudAnchorsが何をしているのか分かりやすく整理したつもりです。

ってことで、Graffity - ARビデオ通話 -もよろしくです。

では!

参考


  1. ...ARImageAnchorARFaceAnchorもあります