如何将视觉框架坐标系转化为ARKit?
How to transform vision framework coordinate system into ARKit?
我正在使用 ARKit(与 SceneKit)添加虚拟对象(例如球)。我正在使用 Vision 框架跟踪真实世界的对象(例如脚),并在视觉请求完成处理程序方法中接收其更新的位置。
let request = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: self.handleVisionRequestUpdate)
我想用虚拟替换跟踪的真实世界对象(例如用立方体替换脚)但我不确定如何将 boundingBox 矩形(我们在视觉请求完成中收到)替换为场景工具包节点作为坐标系统不同。
下面是视觉请求完成处理程序的代码:
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,
}
}
请指导我转移坐标系。
主要考虑的是边界矩形在2D图像中,而ARKit的场景是3D。这意味着在您选择深度之前,它不会在 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
假设矩形在水平面上,您可以对场景的所有 4 个角执行命中测试,并使用其中的 3 个角来计算矩形的宽度、高度、中心和方向。
我在 GitHub 上有一个演示应用程序可以做到这一点:
https://github.com/mludowise/ARKitRectangleDetection
VNRectangleObservation
中矩形角的坐标将与图像的大小相关,并且在不同的坐标中取决于 phone 的旋转。您需要将它们乘以视图大小并根据 phone 的旋转将它们反转:
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 个角进行命中测试。在执行命中测试时使用类型 .existingPlaneUsingExtent
很重要,以便 ARKit returns 命中水平面。
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)
然后就有点复杂了...
因为每个命中测试可能 return 有 0 到 n 个结果,所以您需要过滤掉包含在不同平面上的任何命中测试。您可以通过比较每个 ARHitTestResult
:
的锚点来做到这一点
hit1.anchor == hit2.anchor
此外,您只需要 4 个角中的 3 个角来识别矩形的尺寸、位置和方向,因此如果一个角没有 return 任何命中测试结果也没关系。看看 here 我是怎么做到的。
您可以根据左右角(顶部或底部)之间的距离计算矩形的宽度。同样,您可以根据顶角和底角之间的距离(左或右)计算矩形的高度。
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(与 SceneKit)添加虚拟对象(例如球)。我正在使用 Vision 框架跟踪真实世界的对象(例如脚),并在视觉请求完成处理程序方法中接收其更新的位置。
let request = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: self.handleVisionRequestUpdate)
我想用虚拟替换跟踪的真实世界对象(例如用立方体替换脚)但我不确定如何将 boundingBox 矩形(我们在视觉请求完成中收到)替换为场景工具包节点作为坐标系统不同。
下面是视觉请求完成处理程序的代码:
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,
}
}
请指导我转移坐标系。
主要考虑的是边界矩形在2D图像中,而ARKit的场景是3D。这意味着在您选择深度之前,它不会在 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
假设矩形在水平面上,您可以对场景的所有 4 个角执行命中测试,并使用其中的 3 个角来计算矩形的宽度、高度、中心和方向。
我在 GitHub 上有一个演示应用程序可以做到这一点: https://github.com/mludowise/ARKitRectangleDetection
VNRectangleObservation
中矩形角的坐标将与图像的大小相关,并且在不同的坐标中取决于 phone 的旋转。您需要将它们乘以视图大小并根据 phone 的旋转将它们反转:
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 个角进行命中测试。在执行命中测试时使用类型 .existingPlaneUsingExtent
很重要,以便 ARKit returns 命中水平面。
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)
然后就有点复杂了...
因为每个命中测试可能 return 有 0 到 n 个结果,所以您需要过滤掉包含在不同平面上的任何命中测试。您可以通过比较每个 ARHitTestResult
:
hit1.anchor == hit2.anchor
此外,您只需要 4 个角中的 3 个角来识别矩形的尺寸、位置和方向,因此如果一个角没有 return 任何命中测试结果也没关系。看看 here 我是怎么做到的。
您可以根据左右角(顶部或底部)之间的距离计算矩形的宽度。同样,您可以根据顶角和底角之间的距离(左或右)计算矩形的高度。
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
}
}