Search code examples
swiftmachine-learningaugmented-realityarkitapple-vision

CVPixelBuffer – How to capture every THIRD frame out of 60 fps?


I need only 20 frames out of 60 frames per second for processing (CVPixelBuffer).

How to capture every third ARFrame in ARKit session? I need approximately 20 fps for capture (I understand there may be a drop frame).

Here's a code's snippet:

func updateCoreML() {

    let pixelBuffer: CVPixelBuffer? = (sceneView.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)
    }
}

Solution

  • The simplest way known to me is to use RxARKit and apply .throttle() operator to session.rx.didUpdateFrame. I believe it is not prudent to skip every so many events because frame rate is not guaranteed to be 60fps at all times, so it is best to use .throttle() to get a frame at most every so many milliseconds, no matter what the actual framerate is. You can plug the result of it into RxVision, which will take care that that very frame will be used by CoreML.

    import RxSwift
    import RxARKit
    import RxVision
    
    
    let disposeBag = DisposeBag()
    let mlRequest: RxVNCoreMLRequest<CVPixelBuffer> = VNCoreMLRequest.rx.request(model: model, imageCropAndScaleOption: .scaleFit)
    
    mlRequest
        .observable
        .subscribe { [unowned self] (event) in
            switch event {
                case .next(let completion):       
                    let cvPixelBuffer = completion.value
                    if let result = completion.request.results?[0] as? VNClassificationObservation {
                        os_log("results: %@", type: .debug, result.identifier)
                    }
                default:
                    break
            }
        }
        .disposed(by: disposeBag)
    
    session
        .rx
        .didUpdateFrame
        .throttle(1/20)
        .subscribe { event in
            switch event {
            case .next(let didUpdateFrame):
            let frame: ARFrame = didUpdateFrame.frame        
            let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage, orientation: .up, options: requestOptions)
            do {
                try imageRequestHandler.rx.perform([mlRequest], with: frame.capturedImage) } catch {
                print(error)
            }            
            break
            default:
                break
            }
        }
        .disposed(by: disposeBag)