Dopo aver addestrato il tuo modello utilizzando AutoML Vision Edge , puoi utilizzarlo nella tua app per rilevare oggetti nelle immagini.
Esistono due modi per integrare i modelli addestrati da AutoML Vision Edge. Puoi raggruppare il modello copiando i file del modello nel tuo progetto Xcode oppure scaricarlo dinamicamente da Firebase.
Opzioni di raggruppamento dei modelli | |
---|---|
In bundle nella tua app |
|
Ospitato con Firebase |
|
Prima di iniziare
Se desideri scaricare un modello , assicurati di aggiungere Firebase al tuo progetto Apple , se non l'hai già fatto. Ciò non è necessario quando si raggruppa il modello.
Includi le librerie TensorFlow e Firebase nel tuo Podfile:
Per raggruppare un modello con la tua app:
Veloce
pod 'TensorFlowLiteSwift'
Obiettivo-C
pod 'TensorFlowLiteObjC'
Per scaricare dinamicamente un modello da Firebase, aggiungi la dipendenza
Firebase/MLModelInterpreter
:Veloce
pod 'TensorFlowLiteSwift' pod 'Firebase/MLModelInterpreter'
Obiettivo-C
pod 'TensorFlowLiteObjC' pod 'Firebase/MLModelInterpreter'
Dopo aver installato o aggiornato i pod del tuo progetto, apri il progetto Xcode utilizzando il relativo
.xcworkspace
.
1. Caricare il modello
Configurare un'origine del modello locale
Per raggruppare il modello con la tua app, copia il file del modello e delle etichette nel tuo progetto Xcode, avendo cura di selezionare Crea riferimenti alla cartella quando lo fai. Il file del modello e le etichette verranno inclusi nel pacchetto dell'app.
Inoltre, guarda il file tflite_metadata.json
che è stato creato insieme al modello. Hai bisogno di due valori:
- Le dimensioni di input del modello. Per impostazione predefinita è 320x320.
- Rilevazioni massime del modello. Questo è 40 per impostazione predefinita.
Configura un'origine del modello ospitato da Firebase
Per utilizzare il modello ospitato in remoto, crea un oggetto CustomRemoteModel
, specificando il nome che hai assegnato al modello quando lo hai pubblicato:
Veloce
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Google Cloud console.
)
Obiettivo-C
FIRCustomRemoteModel *remoteModel = [[FIRCustomRemoteModel alloc]
initWithName:@"your_remote_model"];
Avvia quindi l'attività di download del modello, specificando le condizioni alle quali desideri consentire il download. Se il modello non è presente sul dispositivo o se è disponibile una versione più recente del modello, l'attività scaricherà in modo asincrono il modello da Firebase:
Veloce
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
)
Obiettivo-C
FIRModelDownloadConditions *conditions =
[[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
allowsBackgroundDownloading:YES];
NSProgress *progress = [[FIRModelManager modelManager] downloadModel:remoteModel
conditions:conditions];
Molte app avviano l'attività di download nel codice di inizializzazione, ma puoi farlo in qualsiasi momento prima di dover utilizzare il modello.
Crea un rilevatore di oggetti dal tuo modello
Dopo aver configurato le origini del modello, crea un oggetto Interpreter
TensorFlow Lite da uno di essi.
Se disponi solo di un modello raggruppato localmente, crea semplicemente un interprete dal file del modello:
Veloce
guard let modelPath = Bundle.main.path(
forResource: "model",
ofType: "tflite"
) else {
print("Failed to load the model file.")
return true
}
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()
Obiettivo-C
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
ofType:@"tflite"];
NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
error:&error];
if (error != NULL) { return; }
[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }
Se disponi di un modello ospitato in remoto, dovrai verificare che sia stato scaricato prima di eseguirlo. È possibile verificare lo stato dell'attività di download del modello utilizzando il metodo isModelDownloaded(remoteModel:)
del gestore modelli.
Anche se devi solo confermarlo prima di eseguire l'interprete, se disponi sia di un modello ospitato in remoto che di un modello raggruppato localmente, potrebbe avere senso eseguire questo controllo quando istanzia l' Interpreter
: crea un interprete dal modello remoto se è stato scaricato e altrimenti dal modello locale.
Veloce
var modelPath: String?
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
ModelManager.modelManager().getLatestModelFilePath(remoteModel) { path, error in
guard error == nil else { return }
guard let path = path else { return }
modelPath = path
}
} else {
modelPath = Bundle.main.path(
forResource: "model",
ofType: "tflite"
)
}
guard modelPath != nil else { return }
let interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()
Obiettivo-C
__block NSString *modelPath;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
[[FIRModelManager modelManager] getLatestModelFilePath:remoteModel
completion:^(NSString * _Nullable filePath,
NSError * _Nullable error) {
if (error != NULL) { return; }
if (filePath == NULL) { return; }
modelPath = filePath;
}];
} else {
modelPath = [[NSBundle mainBundle] pathForResource:@"model"
ofType:@"tflite"];
}
NSError *error;
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
error:&error];
if (error != NULL) { return; }
[interpreter allocateTensorsWithError:&error];
if (error != NULL) { return; }
Se disponi solo di un modello ospitato in remoto, dovresti disabilitare le funzionalità relative al modello, ad esempio disattivare o nascondere parte della tua interfaccia utente, finché non confermi che il modello è stato scaricato.
È possibile ottenere lo stato di download del modello collegando gli osservatori al Centro notifiche predefinito. Assicurati di utilizzare un riferimento debole a self
nel blocco dell'osservatore, poiché i download possono richiedere del tempo e l'oggetto di origine può essere liberato al termine del download. Per esempio:
Veloce
NotificationCenter.default.addObserver(
forName: .firebaseMLModelDownloadDidSucceed,
object: nil,
queue: nil
) { [weak self] notification in
guard let strongSelf = self,
let userInfo = notification.userInfo,
let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
as? RemoteModel,
model.name == "your_remote_model"
else { return }
// The model was downloaded and is available on the device
}
NotificationCenter.default.addObserver(
forName: .firebaseMLModelDownloadDidFail,
object: nil,
queue: nil
) { [weak self] notification in
guard let strongSelf = self,
let userInfo = notification.userInfo,
let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
as? RemoteModel
else { return }
let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
// ...
}
Obiettivo-C
__weak typeof(self) weakSelf = self;
[NSNotificationCenter.defaultCenter
addObserverForName:FIRModelDownloadDidSucceedNotification
object:nil
queue:nil
usingBlock:^(NSNotification *_Nonnull note) {
if (weakSelf == nil | note.userInfo == nil) {
return;
}
__strong typeof(self) strongSelf = weakSelf;
FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
if ([model.name isEqualToString:@"your_remote_model"]) {
// The model was downloaded and is available on the device
}
}];
[NSNotificationCenter.defaultCenter
addObserverForName:FIRModelDownloadDidFailNotification
object:nil
queue:nil
usingBlock:^(NSNotification *_Nonnull note) {
if (weakSelf == nil | note.userInfo == nil) {
return;
}
__strong typeof(self) strongSelf = weakSelf;
NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
}];
2. Preparare l'immagine di input
Successivamente, devi preparare le tue immagini per l'interprete TensorFlow Lite.
Ritaglia e ridimensiona l'immagine in base alle dimensioni di input del modello, come specificato nel file
tflite_metadata.json
(320x320 pixel per impostazione predefinita). Puoi farlo con Core Image o una libreria di terze partiCopia i dati dell'immagine in un
Data
(oggettoNSData
):Veloce
guard let image: CGImage = // Your input image guard let context = CGContext( data: nil, width: image.width, height: image.height, bitsPerComponent: 8, bytesPerRow: image.width * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue ) else { return nil } context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) guard let imageData = context.data else { return nil } var inputData = Data() for row in 0 ..< 320 { // Model takes 320x320 pixel images as input for col in 0 ..< 320 { let offset = 4 * (col * context.width + row) // (Ignore offset 0, the unused alpha channel) var red = imageData.load(fromByteOffset: offset+1, as: UInt8.self) var green = imageData.load(fromByteOffset: offset+2, as: UInt8.self) var blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self) inputData.append(&red, count: 1) inputData.append(&green, count: 1) inputData.append(&blue, count: 1) } }
Obiettivo-C
CGImageRef image = // Your input image long imageWidth = CGImageGetWidth(image); long imageHeight = CGImageGetHeight(image); CGContextRef context = CGBitmapContextCreate(nil, imageWidth, imageHeight, 8, imageWidth * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst); CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image); UInt8 *imageData = CGBitmapContextGetData(context); NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0]; for (int row = 0; row < 300; row++) { for (int col = 0; col < 300; col++) { long offset = 4 * (row * imageWidth + col); // (Ignore offset 0, the unused alpha channel) UInt8 red = imageData[offset+1]; UInt8 green = imageData[offset+2]; UInt8 blue = imageData[offset+3]; [inputData appendBytes:&red length:1]; [inputData appendBytes:&green length:1]; [inputData appendBytes:&blue length:1]; } }
3. Eseguire il rilevatore di oggetti
Successivamente, passa l'input preparato all'interprete:
Veloce
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()
Obiettivo-C
TFLTensor *input = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { return; }
[input copyData:inputData error:&error];
if (error != nil) { return; }
[interpreter invokeWithError:&error];
if (error != nil) { return; }
4. Ottieni informazioni sugli oggetti rilevati
Se il rilevamento dell'oggetto ha esito positivo, il modello produce come output tre array di 40 elementi (o qualsiasi cosa sia stata specificata nel file tflite_metadata.json
) ciascuno. Ogni elemento corrisponde a un oggetto potenziale. Il primo array è un array di riquadri di delimitazione; il secondo, una serie di etichette; e il terzo, una serie di valori di confidenza. Per ottenere gli output del modello:
Veloce
var output = try interpreter.output(at: 0)
let boundingBoxes =
UnsafeMutableBufferPointer<Float32>.allocate(capacity: 4 * 40)
output.data.copyBytes(to: boundingBoxes)
output = try interpreter.output(at: 1)
let labels =
UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: labels)
output = try interpreter.output(at: 2)
let probabilities =
UnsafeMutableBufferPointer<Float32>.allocate(capacity: 40)
output.data.copyBytes(to: probabilities)
Obiettivo-C
TFLTensor *output = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { return; }
NSData *boundingBoxes = [output dataWithError:&error];
if (error != nil) { return; }
output = [interpreter outputTensorAtIndex:1 error:&error];
if (error != nil) { return; }
NSData *labels = [output dataWithError:&error];
if (error != nil) { return; }
output = [interpreter outputTensorAtIndex:2 error:&error];
if (error != nil) { return; }
NSData *probabilities = [output dataWithError:&error];
if (error != nil) { return; }
Quindi, puoi combinare gli output delle etichette con il dizionario delle etichette:
Veloce
guard let labelPath = Bundle.main.path(
forResource: "dict",
ofType: "txt"
) else { return true }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labelText = fileContents?.components(separatedBy: "\n") else { return true }
for i in 0 ..< 40 {
let top = boundingBoxes[0 * i]
let left = boundingBoxes[1 * i]
let bottom = boundingBoxes[2 * i]
let right = boundingBoxes[3 * i]
let labelIdx = Int(labels[i])
let label = labelText[labelIdx]
let confidence = probabilities[i]
if confidence > 0.66 {
print("Object found: \(label) (confidence: \(confidence))")
print(" Top-left: (\(left),\(top))")
print(" Bottom-right: (\(right),\(bottom))")
}
}
Obiettivo-C
NSString *labelPath = [NSBundle.mainBundle pathForResource:@"dict"
ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
encoding:NSUTF8StringEncoding
error:&error];
if (error != nil || fileContents == NULL) { return; }
NSArray<NSString*> *labelText = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < 40; i++) {
Float32 top, right, bottom, left;
Float32 labelIdx;
Float32 confidence;
[boundingBoxes getBytes:&top range:NSMakeRange(16 * i + 0, 4)];
[boundingBoxes getBytes:&left range:NSMakeRange(16 * i + 4, 4)];
[boundingBoxes getBytes:&bottom range:NSMakeRange(16 * i + 8, 4)];
[boundingBoxes getBytes:&right range:NSMakeRange(16 * i + 12, 4)];
[labels getBytes:&labelIdx range:NSMakeRange(4 * i, 4)];
[probabilities getBytes:&confidence range:NSMakeRange(4 * i, 4)];
if (confidence > 0.5f) {
NSString *label = labelText[(int)labelIdx];
NSLog(@"Object detected: %@", label);
NSLog(@" Confidence: %f", confidence);
NSLog(@" Top-left: (%f,%f)", left, top);
NSLog(@" Bottom-right: (%f,%f)", right, bottom);
}
}
Suggerimenti per migliorare le prestazioni in tempo reale
Se desideri etichettare le immagini in un'applicazione in tempo reale, segui queste linee guida per ottenere i migliori framerate:
- Limita le chiamate al rilevatore. Se un nuovo fotogramma video diventa disponibile mentre il rilevatore è in funzione, rilasciare il fotogramma.
- Se si utilizza l'output del rilevatore per sovrapporre la grafica all'immagine in input, ottenere prima il risultato, quindi eseguire il rendering dell'immagine e sovrapporre in un unico passaggio. In questo modo, viene eseguito il rendering sulla superficie di visualizzazione solo una volta per ciascun fotogramma di input. Per un esempio, vedi le classi PreviewOverlayView e FIRDetectionOverlayView nell'app di esempio vetrina.