I have a CoreML Image Classification task, that takes the "live stream" from the iOS device's [video] camera and occurs in the background. Once objects have been been identified, and other app logic has occurred, I would like to update the UI's label with some of the data.
Can someone explain how the callout to DispatchQueue.main.asyc(execute: { })
is able to access the variable(s) I have been working with? I think this is essentially a scoping issue?
The code I am currently using:
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 = 0
var otherVar = 0
for item in results[0...9] {
if item.identifier.contains("something") {
print("some app logic goes on here")
otherVar += 10 - counter
}
counter += 1
}
switch otherVar {
case _ where otherVar >= 10:
DispatchQueue.main.async(execute: {
let displayVarFormatted = String(format: "%.2f", otherVar / 65 * 100)
self.labelPrediction.text = "\(counter): \(displayVarFormatted)%"
})
default:
DispatchQueue.main.async(execute: {
self.labelPrediction.text = "No result!"
})
}
}
})
if let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
do {
try handler.perform([request])
} catch {
print(error.localizedDescription)
}
}
}
}
Its the self.labelPrediction.text = ""
lines inside the switch statement that is causing the issue. This var is always 0 currently.
It's not the matter of DispatchQueue
. From processCameraBuffer(sampleBuffer:)
, your code update your UI before it get result.
To solve this, you need to use escaping closure
. Your function should look like this.
func processCameraBuffer(sampleBuffer: CMSampleBuffer, completion: @escaping (Int, String) -> Void) {
// 2.
let request = VNCoreMLRequest(model: model, completionHandler: { (request, error) in
DispatchQueue.main.async(execute: {
// 3.
let displayVarFormatted = String(format: "%.2f", otherVar / 65 * 100)
completion(counter, displayVarFormatted)
})
}
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// 1.
processCameraBuffer(sampleBuffer) { counter, displayVarFormatted in
/*
This Closure will be executed from
completion(counter, displayVarFormatted)
*/
// 4.
self.labelPrediction.text = "\(counter): \(displayVarFormatted)%"
}
}
Scope of variable is not a problem from here. You need to handle asynchronous task.
VNCoreMLRequest
executed.processCameraBuffer
's completion block, by completion()
.