ARKit(SceneKitを使用)を使用して仮想オブジェクト(ボールなど)を追加しています。私は、Visionフレームワークを使用して、現実世界のオブジェクト(足など)を追跡し、Visionリクエスト完了ハンドラーメソッドでその更新された位置を受け取ります。
let request = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: self.handleVisionRequestUpdate)
追跡された現実世界のオブジェクトを仮想に置き換えたいのですが(たとえば、足を立方体に置き換えます)、座標系が異なるため、(リクエストリクエストの完了時に受け取る)boundingBox rectをシーンキットノードに置き換える方法がわかりません。
以下は、ビジョン要求完了ハンドラーのコードです。
private func handleVisionRequestUpdate(_ request: VNRequest, error: Error?) {
// Dispatch to the main queue because we are touching non-atomic, non-thread safe properties of the view controller
DispatchQueue.main.async {
// make sure we have an actual result
guard let newObservation = request.results?.first as? VNDetectedObjectObservation else { return }
// prepare for next loop
self.lastObservation = newObservation
// check the confidence level before updating the UI
guard newObservation.confidence >= 0.3 else {
return
}
// calculate view rect
var transformedRect = newObservation.boundingBox
//How to convert transformedRect into AR Coordinate
self.node.position = SCNVector3Make(?.worldTransform.columns.3.x,
?.worldTransform.columns.3.y,
}
}
座標系を転送するように案内してください。
長方形が水平面上にあると仮定すると、4つのコーナーすべてでシーンに対してヒットテストを実行し、そのうちの3つのコーナーを使用して長方形の幅、高さ、中心、および方向を計算できます。
私はそれを正確に行うGitHubで利用可能なデモアプリを持っています: https://github.com/mludowise/ARKitRectangleDetection
VNRectangleObservation
からの長方形の角の座標は、画像のサイズに相対的であり、電話の回転に応じて異なる座標になります。あなたはそれらにビューサイズを掛けて、電話の回転に基づいてそれらを反転させる必要があります:
func convertFromCamera(_ point: CGPoint, view sceneView: ARSCNView) -> CGPoint {
let orientation = UIApplication.shared.statusBarOrientation
switch orientation {
case .portrait, .unknown:
return CGPoint(x: point.y * sceneView.frame.width, y: point.x * sceneView.frame.height)
case .landscapeLeft:
return CGPoint(x: (1 - point.x) * sceneView.frame.width, y: point.y * sceneView.frame.height)
case .landscapeRight:
return CGPoint(x: point.x * sceneView.frame.width, y: (1 - point.y) * sceneView.frame.height)
case .portraitUpsideDown:
return CGPoint(x: (1 - point.y) * sceneView.frame.width, y: (1 - point.x) * sceneView.frame.height)
}
}
次に、4つのコーナーすべてでヒットテストを実行できます。 ARKitが水平面のヒットを返すように、ヒットテストを実行するときは.existingPlaneUsingExtent
タイプを使用することが重要です。
let tl = sceneView.hitTest(convertFromCamera(rectangle.topLeft, view: sceneView), types: .existingPlaneUsingExtent)
let tr = sceneView.hitTest(convertFromCamera(rectangle.topRight, view: sceneView), types: .existingPlaneUsingExtent)
let bl = sceneView.hitTest(convertFromCamera(rectangle.bottomLeft, view: sceneView), types: .existingPlaneUsingExtent)
let br = sceneView.hitTest(convertFromCamera(rectangle.bottomRight, view: sceneView), types: .existingPlaneUsingExtent)
その後、少し複雑になります...
各ヒットテストは0からnの結果で返される可能性があるため、別のプレーンに含まれるヒットテストをフィルターで除外する必要があります。これを行うには、各ARHitTestResult
のアンカーを比較します。
hit1.anchor == hit2.anchor
また、長方形の寸法、位置、方向を特定するために必要なのは4つのコーナーのうち3つだけなので、1つのコーナーがヒットテスト結果を返さなくても問題ありません。私がそれをどのようにしたかについては、 ここ を見てください。
長方形の幅は、左隅と右隅の間の距離(上または下)から計算できます。同様に、(左または右の)上隅と下隅の間の距離から長方形の高さを計算できます。
func distance(_ a: SCNVector3, from b: SCNVector3) -> CGFloat {
let deltaX = a.x - b.x
let deltaY = a.y - b.y
let deltaZ = a.z - b.z
return CGFloat(sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ))
}
let width = distance(right, from: left)
let height = distance(top, from: bottom)
長方形の反対側のコーナー(topLeft&bottomRightまたはtopRight&bottomLeft)から中点を取得することにより、その位置を計算できます。
let midX = (c1.x + c2.x) / 2
let midY = (c1.y + c2.y) / 2
let midZ = (c1.z + c2.z) / 2
let center = SCNVector3Make(midX, midY, midZ)
長方形の向き(y軸に沿った回転)を左右の隅(上または下)から計算することもできます。
let distX = right.x - left.x
let distZ = right.z - left.z
let orientation = -atan(distZ / distX)
それをすべて組み合わせて、長方形にオーバーレイされた何かをARに表示します。次に、SCNNode
をサブクラス化して仮想長方形を表示する例を示します。
class RectangleNode: SCNNode {
init(center: SCNVector3, width: CGFloat, height: CGFloat, orientation: Float) {
super.init()
// Create the 3D plane geometry with the dimensions calculated from corners
let planeGeometry = SCNPlane(width: width, height: height)
let rectNode = SCNNode(geometry: planeGeometry)
// Planes in SceneKit are vertical by default so we need to rotate
// 90 degrees to match planes in ARKit
var transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
// Set rotation to the corner of the rectangle
transform = SCNMatrix4Rotate(transform, orientation, 0, 1, 0)
rectNode.transform = transform
// We add the new node to ourself since we inherited from SCNNode
self.addChildNode(rectNode)
// Set position to the center of rectangle
self.position = center
}
}
考慮すべき主なことは、ARKitのシーンが3Dであるのに対して、外接する四角形が2D画像にあることです。これは、奥行きを選択するまで、3Dの境界矩形がどこにあるかは定義されないことを意味します。
2D座標から3Dに取得するために、シーンに対してヒットテストを実行する必要があります。
let box = newObservation.boundingBox
let rectCenter = CGPoint(x: box.midX, y: box.midY)
let hitTestResults = sceneView.hitTest(rectCenter, types: [.existingPlaneUsingExtent, .featurePoint])
// Pick the hitTestResult you need (nearest?), get position via worldTransform