[iOS 11][ARKit] 距離の計測について #WWDC2017

2017.08.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

iOS 11で追加されたARKitを利用した、AR巻尺の距離測定アプリ「AR Measure」がYoutubeに公開されています。

今回は、このようなアプリを作る方法について確認してみました。

本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。

2 現実世界の座標取得

下の写真は、クラスメソッド札幌オフィスのエントランスですが、スマフォののカメラで覗いて、画面上でタッチすると、そのポイントは、現実の世界では、赤い点を指していることになります。

001 003

ARKitを使用すると、カメラ画像に写っているスマフォ上の位置を指定すると、それに対応する現実世界の位置情報が取得できます。

この機能を利用すると、画面上で地点を指定し、その距離を計算することが可能になるということです。

3 hitTest

ARSCNViewhitTest(_:types:)は、カメラ画像内の現実世界のオブジェクト、又はARアンカーを検索する メソッドです。

https://developer.apple.com/documentation/arkit/arscnview/2875544-hittest

func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]

下記のコードは、スマフォ画面の中央に対応する実世界の座標を取得しているコードです。

types:featurePointが指定されており、アンカーに依存せず座標が取得できます。 結果は、ジャイロの初期化などが、完了していないと、取得に失敗することがありますので、isEmptyで取得できているかどうかを確認しています。

func getCenter() -> SCNVector3? {
    // スマフォ画面の中央座標
    let touchLocation = sceneView.center 
    // hitTestによる判定
    let hitResults = sceneView.hitTest(touchLocation, types: [.featurePoint])
    // 結果取得に成功しているかどうか
    if !hitResults.isEmpty {
        if let hitTResult = hitResults.first {
            // 実世界の座標をSCNVector3で返す
            return SCNVector3(hitTResult.worldTransform.columns.3.x, hitTResult.worldTransform.columns.3.y, hitTResult.worldTransform.columns.3.z)
        }
    }
    return nil
}

取得できる現実世界の位置は、カメラを原点とし、スマフォの向き及び、カメラ投影によって決定される方向に延びる線に沿った任意の点です。hitTestでは、その線に沿って交差するすべてのオブジェクトを返します。 データは、カメラからの距離が近い順にソートされた配列となっているため、最初のデータを利用しています。

4 距離の計算

次のコードは、2つのSCNVector3から、距離を求めているものです。 startPositionendPositionが、その2点の情報です。

let position = SCNVector3Make(endPosition.x - startPosition.x, endPosition.y - startPosition.y, endPosition.z - startPosition.z)
let distance = sqrt(position.x * position.x + position.y * position.y + position.z * position.z)
print(String.init(format: "%.2fm", arguments: [distance]))


002
参考:二点間の距離を求める公式(2次元、3次元)より

5 補助表示

距離を計測するアプリを作るとなると、取得したポイント(点)や、その間を表現するため、幾つかのノードを表示することになると思います。

ここでは一例として、SCNVector3(位置情報)に基づいて、球、線、円柱などのノードを生成するコードを紹介させて頂きます。

球体(始点、終点を表現する)

func createSphereNode(position: SCNVector3, radius: CGFloat, color: UIColor) -> SCNNode {
    let sphere = SCNSphere(radius: radius)
    let material = SCNMaterial()
    material.diffuse.contents = color
    sphere.materials = [material]
    let sphereNode = SCNNode(geometry: sphere)
    sphereNode.position = position
    return sphereNode
}

線(始点と終点を結ぶ線を表現する)

func createLineNode(startPosition: SCNVector3, endPosition: SCNVector3, color: UIColor) -> SCNNode {
    let indices: [Int32] = [0, 1]
    let source = SCNGeometrySource(vertices: [startPosition, endPosition])
    let element = SCNGeometryElement(indices: indices, primitiveType: .line)
    let line = SCNGeometry(sources: , elements: [element])
    line.firstMaterial?.lightingModel = SCNMaterial.LightingModel.blinn
    let lineNode = SCNNode(geometry: line)
    lineNode.geometry?.firstMaterial?.diffuse.contents = color
    return lineNode
}

円柱(始点と終点を結ぶ線を表現する) 上記の「線」では、細すぎる場合に利用可能

func createCylinderNode(startPosition: SCNVector3, endPosition: SCNVector3, radius: CGFloat , color: UIColor, transparency: CGFloat) -> SCNNode {

    let height = CGFloat(GLKVector3Distance(SCNVector3ToGLKVector3(startPosition), SCNVector3ToGLKVector3(endPosition)))

    let cylinderNode = SCNNode()
    cylinderNode.eulerAngles.x = Float(Double.pi / 2)

    let cylinderGeometry = SCNCylinder(radius: radius, height: height)
    cylinderGeometry.firstMaterial?.diffuse.contents = color
    let cylinder = SCNNode(geometry: cylinderGeometry)

    cylinder.position.y = Float(-height/2)
    cylinderNode.addChildNode(cylinder)

    let node = SCNNode()
    let targetNode = SCNNode()

    if (startPosition.z < 0.0 && endPosition.z > 0.0) {
        node.position = endPosition
        targetNode.position = startPosition
    } else {
        node.position = startPosition
        targetNode.position = endPosition
    }
    node.addChildNode(cylinderNode)
    node.constraints = [ SCNLookAtConstraint(target: targetNode) ]
    return node
}

6 最後に

今回は、ARを利用した、計測アプリを作る方法について確認して見ました。

実際に作成して見ると、地点指定が、ある程度正確だと、非常に精度が高い距離測定が可能でした。 正確に位置指定するというのは、物体にスマフォを近づけて指定するという感じです。

ARKitを使用するとARオブジェクトを表示するだけでなく、実世界の地点を正確に把握できる為、色々面白そうなものが作れる予感がしました。

7 参考リンク


Introducing ARKit
Apple Developer > Documentation > ARKit
WWDC2017 Session 602 Introducing ARKit: Augmented Reality for iOS
[iOS 11] はじめてのARKit #WWDC2017