Dopo aver addestrato il tuo modello utilizzando AutoML Vision Edge, puoi utilizzarlo nella tua app per rilevare gli oggetti nelle immagini.
Esistono due modi per integrare i modelli addestrati da AutoML Vision Edge. Puoi aggregare il modello copiando i relativi file nel progetto Xcode oppure scaricarlo dinamicamente da Firebase.
Opzioni di raggruppamento dei modelli | |
---|---|
Raggruppato nell'app |
|
Ospitato con Firebase |
|
Prima di iniziare
Se vuoi scaricare un modello, assicurati di aggiungere Firebase al tuo progetto Apple, se non l'hai già fatto. Questa operazione non è obbligatoria quando raggruppi il modello.
Includi le librerie TensorFlow e Firebase nel tuo Podfile:
Per raggruppare un modello con la tua app:
Swift
pod 'TensorFlowLiteSwift'
Objective-C
pod 'TensorFlowLiteObjC'
Per scaricare dinamicamente un modello da Firebase, aggiungi la dipendenza
Firebase/MLModelInterpreter
:Swift
pod 'TensorFlowLiteSwift' pod 'Firebase/MLModelInterpreter'
Objective-C
pod 'TensorFlowLiteObjC' pod 'Firebase/MLModelInterpreter'
Dopo aver installato o aggiornato i pod del progetto, apri il progetto Xcode utilizzando il relativo
.xcworkspace
.
1. Carica il modello
Configurare un'origine modello locale
Per includere il modello nell'app, copia il file del modello e delle etichette nel progetto Xcode, selezionando Crea riferimenti alle cartelle. Il file del modello e le etichette verranno inclusi nel bundle dell'app.
Inoltre, controlla il file tflite_metadata.json
creato insieme al
modello. Sono necessari due valori:
- Le dimensioni di input del modello. Per impostazione predefinita, è 320 x 320.
- I rilevamenti massimi del modello. Il valore predefinito è 40.
Configurare un'origine modello ospitata su Firebase
Per utilizzare il modello ospitato in remoto, crea un oggetto CustomRemoteModel
specificando il nome che hai assegnato al modello quando lo hai pubblicato:
Swift
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Google Cloud console.
)
Objective-C
FIRCustomRemoteModel *remoteModel = [[FIRCustomRemoteModel alloc]
initWithName:@"your_remote_model"];
Quindi, avvia l'attività di download del modello, specificando le condizioni in cui vuoi consentire il download. Se il modello non è sul dispositivo o se è disponibile una versione più recente, l'attività lo scarica in modo asincrono da Firebase:
Swift
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
)
Objective-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.
Creare un rilevatore di oggetti dal modello
Dopo aver configurato le origini del modello, crea un oggetto Interpreter
di TensorFlow Lite da una di queste.
Se hai solo un modello in bundle locale, crea un interprete dal file del modello:
Swift
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()
Objective-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 hai un modello ospitato in remoto, dovrai verificare che sia stato scaricato prima di eseguirlo. Puoi controllare lo stato del compito di download del modello utilizzando il metodo isModelDownloaded(remoteModel:)
del gestore dei modelli.
Anche se devi confermarlo solo prima di eseguire l'interprete, se hai sia un modello ospitato in remoto sia un modello in bundle locale, potrebbe essere sensato eseguire questo controllo durante l'inizializzazione di Interpreter
: crea un interprete dal modello remoto se è stato scaricato e dal modello locale in caso contrario.
Swift
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()
Objective-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 hai solo un modello ospitato in remoto, devi disattivare le funzionalità correlate al modello, ad esempio disattivare o nascondere parte dell'interfaccia utente, finché non confermi che il modello è stato scaricato.
Puoi ottenere lo stato del download del modello collegando gli osservatori al Centro notifiche predefinito. Assicurati di utilizzare un riferimento debole a self
nel blocco osservatore, poiché i download possono richiedere del tempo e l'oggetto di origine può essere liberato al termine del download. Ad esempio:
Swift
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]
// ...
}
Objective-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. Prepara l'immagine di input
A questo punto, devi preparare le 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
(320 x 320 pixel per impostazione predefinita). Puoi farlo con Core Image o una libreria di terze partiCopia i dati dell'immagine in un
Data
(oggettoNSData
):Swift
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) } }
Objective-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. Esegui il rilevatore di oggetti
Quindi, passa l'input preparato all'interprete:
Swift
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()
Objective-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. Ricevere informazioni sugli oggetti rilevati
Se il rilevamento degli oggetti va a buon fine, il modello produce come output tre array di 40 elementi ciascuno (o quelli specificati nel file tflite_metadata.json
).
Ogni elemento corrisponde a un potenziale oggetto. Il primo array è un array di riquadri di delimitazione, il secondo è un array di etichette e il terzo è un array di valori di confidenza. Per ottenere gli output del modello:
Swift
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)
Objective-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; }
Poi, puoi combinare gli output delle etichette con il tuo dizionario di etichette:
Swift
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))")
}
}
Objective-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 il rendimento in tempo reale
Se vuoi etichettare le immagini in un'applicazione in tempo reale, segui queste linee guida per ottenere le frequenze frame migliori:
- Regola le chiamate al rilevatore. Se un nuovo frame video diventa disponibile mentre il rilevatore è in esecuzione, inseriscilo.
- Se utilizzi l'output del rilevatore per sovrapporre la grafica all'immagine di input, ottieni prima il risultato, poi esegui il rendering dell'immagine e la sovrapposizione in un unico passaggio. In questo modo, esegui il rendering sulla superficie di visualizzazione solo una volta per ogni frame di input. Per un esempio, consulta le classi previewOverlayView e FIRDetectionOverlayView nell'app di esempio della vetrina.