运行 并在指定时间段内暂停 ARSession

Run and Pause an ARSession in a specified period of time

我正在开发具有手势识别功能的 ARKit/Vision iOS 应用程序。我的应用程序有一个简单的 UI,其中包含单个 UIView。根本没有 ARSCNView/ARSKView。我将捕获的 ARFrames 序列放入 CVPixelBuffer 中,然后用于 VNRecognizedObjectObservation.

我不需要会话的任何跟踪数据。 CVPixelBuffer 我只需要 currentFrame.capturedImage。我需要以 30 fps 的速度捕获 ARFrames。 60 fps 是过高的帧速率。

preferredFramesPerSecond instance property is absolutely useless in my case, because it controls frame rate for rendering an ARSCNView/ARSKView. I have no ARViews. And it doesn't affect session's frame rate.

所以,我决定使用 run()pause() 方法来降低会话的帧速率。

问题

我想知道如何在指定的时间段内自动runpause一个ARSession? runpause 方法的持续时间必须是 16 ms(或 0.016 秒)。我想这可能是通过 DispatchQueue 实现的。但是不知道怎么实现。

怎么做?

这是一个伪代码:

session.run(configuration)

    /*  run lasts 16 ms  */

session.pause()

    /*  pause lasts 16 ms  */

session.run(session.configuration!)

    /*  etc...  */

P.S。 我的应用程序中既不能使用 CocoaPod 也不能使用 Carthage

Update:是关于ARSession的currentFrame.capturedImage如何获取和使用的

let session = ARSession()

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    session.delegate = self
    let configuration = ARImageTrackingConfiguration() // 6DOF
    configuration.providesAudioData = false
    configuration.isAutoFocusEnabled = true            
    configuration.isLightEstimationEnabled = false
    configuration.maximumNumberOfTrackedImages = 0
    session.run(configuration)  

    spawnCoreMLUpdate()
}

func spawnCoreMLUpdate() {    // Spawning new async tasks

    dispatchQueue.async {
        self.spawnCoreMLUpdate()
        self.updateCoreML()
    }
}

func updateCoreML() {

    let pixelBuffer: CVPixelBuffer? = (session.currentFrame?.capturedImage)
    if pixelBuffer == nil { return }
    let ciImage = CIImage(cvPixelBuffer: pixelBuffer!)
    let imageRequestHandler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    do {
        try imageRequestHandler.perform(self.visionRequests)
    } catch {
        print(error)
    }
}

如果您想要将帧率从 60 降低到 30,您应该使用 SCNViewpreferredFramesPerSecond 属性。我假设您使用的是 ARSCNView,它是 SCNView.

的子类

Property documentation.

如果我没理解错的话,你可以通过DispatchQueue来实现。如果你 运行 下面的代码,它首先打印 HHH 然后等待 1 秒然后打印 ABC。您可以放置​​自己的函数以使其为您工作。当然,将时间间隔从 1 更改为您想要的值。

let syncConc = DispatchQueue(label:"con",attributes:.concurrent)

DispatchQueue.global(qos: .utility).async {
syncConc.async {
    for _ in 0...10{
        print("HHH - \(Thread.current)")
        Thread.sleep(forTimeInterval: 1)
        print("ABC - \(Thread.current)")

    }
}

PS:我仍然不确定 Thread.sleep 是否会阻止您的进程,如果是,我会编辑我的答案。

我认为 run()pause() 策略不是可行的方法,因为 DispatchQueue API 不是为实时准确性而设计的。这意味着不能保证每次暂停都是 16ms。最重要的是,重新启动会话可能不会立即发生,并且可能会增加更多延迟。

此外,您共享的代码最多只能捕获一张图像,并且由于 session.run(configuration) 是异步的,因此可能不会捕获任何帧。

因为您没有使用 ARSCNView/ARSKView,所以唯一的方法是实现 ARSession 委托,以便在每个捕获的帧时收到通知。

当然,委托很可能每 16 毫秒被调用一次,因为这就是相机的工作方式。但是您可以决定要处理哪些帧。通过使用帧的时间戳,您可以每 32 毫秒处理一帧并丢弃其他帧。这相当于 30 fps 处理。

这里有一些代码可以帮助您入门,确保 dispatchQueue 不是并发的以顺序处理您的缓冲区:

var lastProcessedFrame: ARFrame?

func session(_ session: ARSession, didUpdate frame: ARFrame) {
  dispatchQueue.async {
    self.updateCoreML(with: frame)
  }
}

private func shouldProcessFrame(_ frame: ARFrame) -> Bool {
  guard let lastProcessedFrame = lastProcessedFrame else {
    // Always process the first frame
    return true
  }
  return frame.timestamp - lastProcessedFrame.timestamp >= 0.032 // 32ms for 30fps
}

func updateCoreML(with frame: ARFrame) {

  guard shouldProcessFrame(frame) else {
    // Less than 32ms with the previous frame
    return
  }
  lastProcessedFrame = frame
  let pixelBuffer = frame.capturedImage
  let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
  do {
    try imageRequestHandler.perform(self.visionRequests)
  } catch {
    print(error)
  }
}