【问题标题】:Transforming ARFrame#capturedImage to view size将 ARFrame#capturedImage 转换为视图大小
【发布时间】:2020-04-03 03:23:34
【问题描述】:

在ARKit中使用ARSessionDelegate处理原始相机图像时...

func session(_ session: ARSession, didUpdate frame: ARFrame) {

    guard let currentFrame = session.currentFrame else { return }
    let capturedImage = currentFrame.capturedImage

    debugPrint("Display size", UIScreen.main.bounds.size)
    debugPrint("Camera frame resolution", CVPixelBufferGetWidth(capturedImage), CVPixelBufferGetHeight(capturedImage))

    // ...

}

...如文档所述,相机图像数据与屏幕尺寸不匹配,例如,在 iPhone X 上我得到:

  • 显示尺寸:375x812pt
  • 相机分辨率:1920x1440px

现在有displayTransform(for:viewportSize:) API 可以将相机坐标转换为视图坐标。像这样使用 API 时:

let ciimage = CIImage(cvImageBuffer: capturedImage)
let transform = currentFrame.displayTransform(for: .portrait, viewportSize: UIScreen.main.bounds.size)
var transformedImage = ciimage.transformed(by: transform)
debugPrint("Transformed size", transformedImage.extent.size)

我得到的尺寸为 2340x1920,这似乎不正确,结果的纵横比应为 375:812 (~0.46)。我在这里错过了什么/使用此 API 将相机图像转换为“由 ARSCNView 显示”的图像的正确方法是什么?

(示例项目:ARKitCameraImage

【问题讨论】:

    标签: ios arkit


    【解决方案1】:

    事实证明这相当复杂,因为displayTransform(for:viewportSize) 需要标准化的图像坐标,看来您只能在纵向模式下翻转坐标,并且图像不仅需要转换,还需要裁剪。下面的代码对我有用。建议如何改进这一点将不胜感激。

    guard let frame = session.currentFrame else { return }
    let imageBuffer = frame.capturedImage
    
    let imageSize = CGSize(width: CVPixelBufferGetWidth(imageBuffer), height: CVPixelBufferGetHeight(imageBuffer))
    let viewPort = sceneView.bounds
    let viewPortSize = sceneView.bounds.size
    
    let interfaceOrientation : UIInterfaceOrientation
    if #available(iOS 13.0, *) {
        interfaceOrientation = self.sceneView.window!.windowScene!.interfaceOrientation
    } else {
        interfaceOrientation = UIApplication.shared.statusBarOrientation
    }
    
    let image = CIImage(cvImageBuffer: imageBuffer)
    
    // The camera image doesn't match the view rotation and aspect ratio
    // Transform the image:
    
    // 1) Convert to "normalized image coordinates"
    let normalizeTransform = CGAffineTransform(scaleX: 1.0/imageSize.width, y: 1.0/imageSize.height)
    
    // 2) Flip the Y axis (for some mysterious reason this is only necessary in portrait mode)
    let flipTransform = (interfaceOrientation.isPortrait) ? CGAffineTransform(scaleX: -1, y: -1).translatedBy(x: -1, y: -1) : .identity
    
    // 3) Apply the transformation provided by ARFrame
    // This transformation converts:
    // - From Normalized image coordinates (Normalized image coordinates range from (0,0) in the upper left corner of the image to (1,1) in the lower right corner)
    // - To view coordinates ("a coordinate space appropriate for rendering the camera image onscreen")
    // See also: https://developer.apple.com/documentation/arkit/arframe/2923543-displaytransform
    
    let displayTransform = frame.displayTransform(for: interfaceOrientation, viewportSize: viewPortSize)
    
    // 4) Convert to view size
    let toViewPortTransform = CGAffineTransform(scaleX: viewPortSize.width, y: viewPortSize.height)
    
    // Transform the image and crop it to the viewport
    let transformedImage = image.transformed(by: normalizeTransform.concatenating(flipTransform).concatenating(displayTransform).concatenating(toViewPortTransform)).cropped(to: viewPort)
    

    【讨论】:

    • 这是一个独立的示例项目,该项目使用此代码用于使用所显示的相机图像的金属着色器:github.com/ralfebert/ARSCNViewShaderExample
    • 我尝试了其他几种方法,这是唯一一种使 ARFrame 的大小和方向与 snapshot() 返回的图像相同的方法,因此它们可以被过度播放或比较。
    【解决方案2】:

    非常感谢您的回答!我为此工作了一周。

    这是另一种不弄乱方向的方法。除了使用 captureImage 属性,您还可以使用屏幕快照。

    func session(_ session: ARSession, didUpdate frame: ARFrame) {
      guard let image = CIImage(image: sceneView.snapshot()) else { return }
    
      let imageSize = image.extent.size
    
      // Convert to "normalized image coordinates"
      let resize = CGAffineTransform(scaleX: 1.0 / imageSize.width, y: 1.0 / imageSize.height)
    
      // Convert to view size
      let viewSize = CGAffineTransform(scaleX: sceneView.bounds.size.width, y: sceneView.bounds.size.height)
    
      // Transform image
      let editedImage = image.transformed(by: resize.concatenating(viewSize)).cropped(to: sceneView.bounds)
    
      sceneView.scene.background.contents = context.createCGImage(editedImage, from: editedImage.extent)
     }
    

    【讨论】:

    • 问题不在于选择复制图像像素的方法。但是要使用 captureImage 位,因为最后一种方法不会复制字节并在用于覆盖事物时达到 60fps。
    猜你喜欢
    • 2021-05-17
    • 2020-08-16
    • 2018-07-05
    • 2013-07-17
    • 2019-12-03
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 2013-10-17
    相关资源
    最近更新 更多