Edited at

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

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もあります