Objekte in Bildern mit einem von AutoML trainierten Modell auf Apple-Plattformen erkennen

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
  • Das Modell ist Teil des Sets
  • Das Modell ist sofort verfügbar, auch wenn das Apple-Gerät offline ist.
  • Kein Firebase-Projekt erforderlich
Mit Firebase gehostet
  • Hosten Sie das Modell, indem Sie es in Firebase Machine Learning hochladen.
  • Verringert die Größe des App-Bundles
  • Das Modell wird auf Anfrage heruntergeladen.
  • Modellupdates per Push senden, ohne die App noch einmal zu veröffentlichen
  • Einfache A/B-Tests mit Firebase Remote Config
  • Erfordert ein Firebase-Projekt

Hinweis

  1. Wenn Sie ein Modell herunterladen möchten, müssen Sie Firebase zu Ihrem Apple-Projekt hinzufügen, falls Sie dies noch nicht getan haben. Dies ist beim Bündeln des Modells nicht erforderlich.

  2. 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'
    

    Fügen Sie die Abhängigkeit Firebase/MLModelInterpreter hinzu, um ein Modell dynamisch aus Firebase herunterzuladen:

    Swift

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Objective-C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. 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. Die Standardeinstellung ist 320 x 320.
  • Die maximalen Erkennungen des Modells. Standardmäßig ist dies 40.

Von 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 den Modelldownload und geben Sie die Bedingungen an, unter denen Sie das Herunterladen erlauben möchten. 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 die Modellquellen konfiguriert haben, erstellen Sie aus einer der Quellen 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.

  1. 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.

  2. 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, erstellt das Modell als Ausgabe drei Arrays mit jeweils 40 Elementen (bzw. dem in der Datei tflite_metadata.json angegebenen Wert). 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 ein neuer Videoframe verfügbar wird, während der Detektor ausgeführt wird, lassen Sie den Frame fallen.
  • Wenn Sie die Ausgabe des Detektors verwenden, um Grafiken über das Eingabebild einzublenden, rufen Sie zuerst das Ergebnis ab und rendern Sie dann das Bild und das Overlay in einem Schritt. So wird für jeden Eingabeframe nur einmal auf die Displayoberfläche gerendert. Ein Beispiel finden Sie in den Klassen previewOverlayView und FIRDetectionOverlayView in der Showcase-Beispiel-App.