You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vision SwiftUI:忽略安全区域/iPad下地标连接绘制异常求助

Vision人体体态地标绘制偏差问题排查与解决

问题概述

基于Apple开发者网站《利用Vision检测动物体态》示例修改为人体检测后,仅在iPhone不忽略安全区域时地标及线条绘制正常;忽略安全区域或在iPad运行时,绘制位置出现明显偏差。原示例使用GeometryReader获取视图尺寸,核心绘制依赖Line Shape坐标变换,尝试修改videoGravity为.resizeAspect及调整updateUIViewController方法未解决问题。

核心原因

Vision返回的地标坐标基于视频原始帧尺寸,而UI绘制坐标系受安全区域、设备屏幕比例、视频预览层的videoGravity影响,坐标转换逻辑未适配这些变量的变化,导致映射关系错位。

具体排查与修复方案

1. 修正视频帧与UI视图的坐标映射逻辑

需根据videoGravity类型、安全区域状态、设备屏幕尺寸,重新计算视频预览在视图内的实际显示区域,再将Vision的归一化坐标映射到该区域:

// 计算视频预览层在视图内的实际显示区域
func videoPreviewDisplayFrame(in view: UIView) -> CGRect {
    guard let previewLayer = cameraView.previewLayer else { return .zero }
    let videoNativeSize = previewLayer.videoRect.size
    let viewBounds = view.bounds
    
    // 根据videoGravity计算缩放比例
    let scaleX = viewBounds.width / videoNativeSize.width
    let scaleY = viewBounds.height / videoNativeSize.height
    let scale: CGFloat
    
    switch previewLayer.videoGravity {
    case .resizeAspect:
        scale = min(scaleX, scaleY)
    case .resizeAspectFill:
        scale = max(scaleX, scaleY)
    default:
        scale = 1.0
    }
    
    let displaySize = CGSize(width: videoNativeSize.width * scale, height: videoNativeSize.height * scale)
    var xOffset = (viewBounds.width - displaySize.width) / 2
    var yOffset = (viewBounds.height - displaySize.height) / 2
    
    // 适配安全区域忽略状态
    if view.ignoresSafeArea {
        let safeInsets = view.window?.safeAreaInsets ?? .zero
        // 移除安全区域的偏移补偿,让预览层占满整个视图
        yOffset -= safeInsets.top
        xOffset -= safeInsets.left
    }
    
    return CGRect(x: xOffset, y: yOffset, width: displaySize.width, height: displaySize.height)
}

// 将Vision地标坐标转换为UI绘制坐标
func convertLandmarkPoint(_ visionPoint: CGPoint, to view: UIView) -> CGPoint {
    let displayFrame = videoPreviewDisplayFrame(in: view)
    guard let videoSize = cameraView.previewLayer?.videoRect.size else { return .zero }
    
    // Vision坐标是基于视频帧的归一化坐标(0-1范围)
    let normalizedX = visionPoint.x / videoSize.width
    let normalizedY = visionPoint.y / videoSize.height
    
    return CGPoint(
        x: displayFrame.origin.x + normalizedX * displayFrame.width,
        y: displayFrame.origin.y + normalizedY * displayFrame.height
    )
}

2. 监听布局变化,实时更新坐标映射

在CameraViewController中添加布局变化监听,确保视图尺寸、安全区域、设备旋转时重新计算映射参数:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // 触发坐标映射参数更新,比如重新计算displayFrame
    updateLandmarkDrawing()
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass {
        updateLandmarkDrawing()
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { _ in
        self.updateLandmarkDrawing()
    })
}

3. 调整Line Shape的坐标计算逻辑

原示例的Line Shape可能直接使用视图bounds进行变换,需改为基于上述计算的displayFrame来绘制线条,确保线条的起点和终点都映射到正确的显示区域内。

4. 验证视频预览层的布局约束

确保cameraView的约束设置正确:若忽略安全区域,需将cameraView的顶部/底部约束绑定到视图的边缘而非safeAreaLayoutGuide;若保留安全区域,则绑定到safeAreaLayoutGuide。

内容的提问来源于stack exchange,提问作者Stefano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:53:11