Dispatch Queue with CoreML

I am a newcomer to Swift and iOS development, so apologies upfront for this. I would like to ask for help around queuing and the UI (at least I think this is my problem!)

I am trying to enable the camera, via AVFoundation, in order to get a near real-time stream of images that I can pass on for CoreML analysis.


Currently, I am at the debugging / data learning stage. Essentially, I would like to take sample footage and save it all to an on-device CoreData [SQLite] database, that I can then analyse further offline. I can then learn from the results to fine tune / filter images / etc.


If I supply an individual image, the image model is able to provide descending order of “confidence” with up to 1000 rows. I recorded all this as part of the analysis by adding an extra counter integer to the [VNClassificationObservation] array and adding this to the CoreData database.


This doesn’t work with the stream from the camera. The counter doesnt seem to work, with everything having a counter value of 1000…


The tap gesture in viewDidLoad() function has also stopped working with these changes. I wonder if its related to the queuing stuff, as the text label doesnt properly reset either though I can see it updating for a millisecond or so...



func setupCameraSession() {
  <<bunch of stuff to initialise devices, etc>>

  cameraLayer = AVCaptureVideoPreviewLayer(session: cameraSession)
  cameraLayer.frame = layer.frame
  layer.addSublayer(cameraLayer)
  cameraSession.startRunning()
  cameraOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {     
  processCameraBuffer(sampleBuffer: sampleBuffer)
}

func processCameraBuffer(sampleBuffer: CMSampleBuffer) {
      
        let coreMLModel = Inceptionv3()
      
        if let model = try? VNCoreMLModel(for: coreMLModel.model) {
            let request = VNCoreMLRequest(model: model, completionHandler: { (request, error) in
                if let results = request.results as? [VNClassificationObservation] {
                    var counter = 1
                    for classification in results {
                        DispatchQueue.main.async(execute: {
                            let timestamp = NSDate().timeIntervalSince1970
                          
                            let appDelegate = UIApplication.shared.delegate as! AppDelegate
                            let context = appDelegate.persistentContainer.viewContext
                          
                            let newItem = NSEntityDescription.insertNewObject(forEntityName: "Predictions", into: context)
                            newItem.setValue(classification.confidence, forKey: "confidence")
                            newItem.setValue(classification.identifier, forKey: "identifier")
                            newItem.setValue(counter, forKey: "index")
                            newItem.setValue(timestamp, forKey: "timestamp")
                            let confidence = String(format: "%.2f", classification.confidence * 100)
                            self.labelPrediction.text = "\(confidence)%"
                          
                            do {
                                try context.save()
                            } catch {
                                print("CoreData Save error")
                            }
                        })
                      
                        counter += 1
                    }
                }
            })
                if let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
                    let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
                    try? handler.perform([request])
                }
        }
    }

override func viewDidLoad() {
        super.viewDidLoad()
      
        imagePicker.delegate = self
      
        if let imageIcon = UIImage(named: "icon_512") {
            layer.contentsGravity = kCAGravityResizeAspect
            layer.contents = imageIcon.cgImage
        }
      
        let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.releaseCamera))
        viewCamera.addGestureRecognizer(tap)
    }


I would like to understand more about the queuing process in order to achieve this, so that I can record every "confidence" row from the CoreML model and to always have the camera disable when the "tap" function is pressed (i.e. the screen is touched).


Thanks

Replies

The captureOutput(...) method is called by AVFoundation for every frame separatly. This means your processCameraBuffer is also called for one frame at a time. Since your counter variable is local to that method, it gets initalized again with 1. It gets captured by the async block, but only that particular instance. You would need to define that counter in another scope, for exampel as member property of the view controller.


By the way, your MLModel gets recreated for every single frame for the same reason. I highly advice you to initialize it once and re-use it for every frame.