Nachdem Sie Ihr eigenes Modell mit AutoML Vision Edge trainiert haben, können Sie es in Ihrer App verwenden, um Objekte in Bildern zu erkennen.
Es gibt zwei Möglichkeiten, mit AutoML Vision Edge trainierte Modelle zu integrieren. Sie können das Modell bündeln, indem Sie die Modelldateien in Ihr Xcode-Projekt kopieren, oder es dynamisch von Firebase herunterladen.
Optionen für die Modellkombination | |
---|---|
In Ihrer App gebündelt |
|
Mit Firebase gehostet |
|
Hinweis
Wenn Sie ein Modell herunterladen möchten, müssen Sie Ihrem Apple-Projekt Firebase hinzufügen, falls Sie das noch nicht getan haben. Das ist nicht erforderlich, wenn Sie das Modell bündeln.
Fügen Sie die TensorFlow- und Firebase-Bibliotheken in Ihre Podfile-Datei ein:
So bündeln Sie ein Modell mit Ihrer App:
Swift
pod 'TensorFlowLiteSwift'
Objective-C
pod 'TensorFlowLiteObjC'
Wenn Sie ein Modell dynamisch von Firebase herunterladen möchten, fügen Sie die Abhängigkeit
Firebase/MLModelInterpreter
hinzu:Swift
pod 'TensorFlowLiteSwift' pod 'Firebase/MLModelInterpreter'
Objective-C
pod 'TensorFlowLiteObjC' pod 'Firebase/MLModelInterpreter'
Nachdem Sie die Pods Ihres Projekts installiert oder aktualisiert haben, öffnen Sie Ihr Xcode-Projekt mit der
.xcworkspace
.
1. Modell laden
Lokale Modellquelle konfigurieren
Wenn Sie das Modell mit Ihrer App bündeln möchten, kopieren Sie die Modell- und Labelsdatei in Ihr Xcode-Projekt. Achten Sie dabei darauf, Create folder references (Ordnerverweise erstellen) auszuwählen. Die Modelldatei und die Labels werden dann in das App-Bundle aufgenommen.
Sehen Sie sich auch die tflite_metadata.json
-Datei an, die zusammen mit dem Modell erstellt wurde. Sie benötigen zwei Werte:
- Die Eingabedimensionen des Modells. Standardmäßig ist das 320 × 320 Pixel.
- Die maximale Anzahl der Erkennungen des Modells. Standardmäßig ist dies 40.
Firebase-gehostete Modellquelle konfigurieren
Wenn Sie das remote gehostete Modell verwenden möchten, erstellen Sie ein CustomRemoteModel
-Objekt und geben Sie den Namen an, den Sie dem Modell bei der Veröffentlichung zugewiesen haben:
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"];
Starten Sie dann die Aufgabe zum Herunterladen des Modells und geben Sie die Bedingungen an, unter denen der Download zulässig sein soll. Wenn das Modell nicht auf dem Gerät vorhanden ist oder eine neuere Version des Modells verfügbar ist, wird es von der Aufgabe asynchron von Firebase heruntergeladen:
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];
Viele Apps starten die Downloadaufgabe in ihrem Initialisierungscode, Sie können dies aber auch jederzeit tun, bevor Sie das Modell verwenden müssen.
Objekterkennung aus Ihrem Modell erstellen
Nachdem Sie Ihre Modellquellen konfiguriert haben, erstellen Sie aus einer davon ein TensorFlow Lite-Interpreter
-Objekt.
Wenn Sie nur ein lokal gebündeltes Modell haben, erstellen Sie einfach einen Interpreter aus der Modelldatei:
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; }
Wenn Sie ein extern gehostetes Modell haben, müssen Sie prüfen, ob es heruntergeladen wurde, bevor Sie es ausführen. Sie können den Status der Modelldownloadaufgabe mit der isModelDownloaded(remoteModel:)
-Methode des Modellmanagers prüfen.
Sie müssen dies zwar nur vor dem Ausführen des Interpreters bestätigen, wenn Sie jedoch sowohl ein remote gehostetes Modell als auch ein lokal gebündeltes Modell haben, kann es sinnvoll sein, diese Prüfung bei der Instanziierung von Interpreter
durchzuführen: Erstellen Sie einen Interpreter aus dem Remote-Modell, wenn es heruntergeladen wurde, andernfalls aus dem lokalen Modell.
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; }
Wenn Sie nur ein extern gehostetes Modell haben, sollten Sie modellverwandte Funktionen deaktivieren, z. B. einen Teil der Benutzeroberfläche grau ausblenden oder ausblenden, bis Sie bestätigen, dass das Modell heruntergeladen wurde.
Sie können den Downloadstatus des Modells abrufen, indem Sie Beobachter an das standardmäßige Benachrichtigungscenter anhängen. Verwenden Sie im Beobachterblock unbedingt einen schwachen Verweis auf self
, da Downloads einige Zeit in Anspruch nehmen können und das ursprüngliche Objekt bis zum Ende des Downloads freigegeben werden kann. Beispiel:
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. Eingabebild vorbereiten
Als Nächstes müssen Sie Ihre Bilder für den TensorFlow Lite-Interpreter vorbereiten.
Zuschneiden und skalieren Sie das Bild auf die Eingabeabmessungen des Modells, wie in der Datei
tflite_metadata.json
angegeben (standardmäßig 320 × 320 Pixel). Sie können dies mit Core Image oder einer Drittanbieterbibliothek tun.Kopieren Sie die Bilddaten in ein
Data
(NSData
-Objekt):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. Objektdetektor ausführen
Geben Sie die vorbereitete Eingabe dann an den Interpreter weiter:
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. Informationen zu erkannten Objekten abrufen
Wenn die Objekterkennung erfolgreich ist, gibt das Modell als Ausgabe drei Arrays mit jeweils 40 Elementen (oder wie in der Datei tflite_metadata.json
angegeben) aus.
Jedes Element entspricht einem potenziellen Objekt. Das erste Array ist ein Array von Begrenzungsrahmen, das zweite ein Array von Labels und das dritte ein Array von Konfidenzwerten. So rufen Sie die Modellausgaben ab:
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; }
Anschließend können Sie die Labelausgaben mit Ihrem Labelwörterbuch kombinieren:
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);
}
}
Tipps zur Verbesserung der Echtzeitleistung
Wenn Sie Bilder in einer Echtzeitanwendung taggen möchten, beachten Sie die folgenden Richtlinien, um die beste Framerate zu erzielen:
- Aufrufe an den Detektor drosseln Wenn während der Laufzeit des Detektors ein neuer Videoframe verfügbar wird, legen Sie ihn ab.
- Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken auf das Eingabebild zu legen, rufen Sie zuerst das Ergebnis ab und rendern Sie dann das Bild und das Overlay in einem einzigen Schritt. So wird für jeden Eingabeframe nur einmal auf die Displayoberfläche gerendert. Sehen Sie sich die Klassen previewOverlayView und FIRDetectionOverlayView in der Beispiel-App an.