如何将SCNNode正确放置在ARKit识别出的二维码顶部?
问题排查及修复方案

偏移问题由4个核心错误导致,逐一修复即可解决:
- 坐标系转换错误
Vision框架返回的boundingBox是左下角为原点的归一化坐标,而ARKit的hitTest接口使用的是左上角为原点的视图坐标系,Y轴方向完全相反,直接用原始坐标做hitTest必然偏移。 - 未指定图像方向
创建VNImageRequestHandler时没有传入当前摄像头的朝向参数,会导致Vision识别结果的坐标与实际摄像头采集画面的方向不匹配,竖屏使用时偏移尤其明显。 - 未开启竖直平面检测
当前的ARKit配置没有开启竖直平面识别,hitTest时使用的.featurePoint类型是对环境随机特征点的采样,位置本身就存在漂移,无法稳定锚定在竖直的二维码平面上。 - 未做结果防抖
每次检测到二维码就直接更新节点位置,频繁的识别误差会导致节点持续偏移抖动。
修复后的完整代码
class ViewController: UIViewController, ARSCNViewDelegate,ARSessionDelegate{ @IBOutlet var sceneView: ARSCNView! var qrRequests = [VNRequest]() var detectedDataAnchor: ARAnchor? var processing = false let configuration = ARWorldTrackingConfiguration() override func viewDidLoad() { super.viewDidLoad() self.sceneView.delegate = self self.sceneView.session.delegate = self // 开启竖直平面检测 configuration.planeDetection = [.vertical] self.sceneView.session.run(configuration) startQrCodeDetection() } func startQrCodeDetection() { let request = VNDetectBarcodesRequest(completionHandler: self.requestHandler) self.qrRequests = [request] } public func session(_ session: ARSession, didUpdate frame: ARFrame) { DispatchQueue.global(qos: .userInitiated).async { do { if self.processing { return } self.processing = true // 传入摄像头朝向,修正图像方向 let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage, orientation: frame.camera.imageOrientation, options: [:]) try imageRequestHandler.perform(self.qrRequests) } catch { self.processing = false } } } func requestHandler(request: VNRequest, error: Error?) { if let results = request.results, let result = results.first as? VNBarcodeObservation { guard let payload = result.payloadStringValue else { self.processing = false return } let rect = result.boundingBox // 翻转Y轴,适配ARKit坐标系 let convertedCenter = CGPoint(x: rect.midX, y: 1 - rect.midY) DispatchQueue.main.async { self.hitTestQrCode(center: convertedCenter) self.processing = false } } else { self.processing = false } } func hitTestQrCode(center: CGPoint) { // 仅匹配已识别的平面,去掉随机特征点采样 guard let hitTestResults = self.sceneView?.hitTest(center, types: [.existingPlaneUsingExtent]), let hitTestResult = hitTestResults.first else { self.processing = false return } // 加防抖:已存在锚点时,仅当位置偏差超过1cm再更新 if let detectedDataAnchor = self.detectedDataAnchor, let node = self.sceneView.node(for: detectedDataAnchor) { let currentPosition = SCNVector3FromMatrix4(hitTestResult.worldTransform) let oldPosition = node.position let distance = sqrt(pow(currentPosition.x - oldPosition.x, 2) + pow(currentPosition.y - oldPosition.y, 2) + pow(currentPosition.z - oldPosition.z, 2)) if distance > 0.01 { node.transform = SCNMatrix4(hitTestResult.worldTransform) // 节点放在二维码上方2cm处 node.position.y += 0.02 } } else { self.detectedDataAnchor = ARAnchor(transform: hitTestResult.worldTransform) self.sceneView.session.add(anchor: self.detectedDataAnchor!) } } func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { if self.detectedDataAnchor?.identifier == anchor.identifier { let sphere = SCNSphere(radius: 0.02) sphere.firstMaterial?.diffuse.contents = UIColor.red let sphereNode = SCNNode(geometry: sphere) // 自动上浮2cm,ARKit会自动同步锚点变换无需手动设置 sphereNode.position.y += 0.02 return sphereNode } return nil } // 辅助工具方法:从变换矩阵提取位置向量 func SCNVector3FromMatrix4(_ matrix: matrix_float4x4) -> SCNVector3 { return SCNVector3(matrix.columns.3.x, matrix.columns.3.y, matrix.columns.3.z) } }
内容的提问来源于stack exchange,提问作者Hashan Kahandugoda
相关产品推荐
相关产品推荐

