Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Wykrywaj obiekty na obrazach za pomocą modelu wyszkolonego w AutoML na platformach Apple

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Po wytrenowaniu własnego modelu za pomocą AutoML Vision Edge możesz używać go w swojej aplikacji do wykrywania obiektów na obrazach.

Istnieją dwa sposoby integracji modeli przeszkolonych z AutoML Vision Edge. Możesz spakować model, kopiując pliki modelu do swojego projektu Xcode lub możesz go dynamicznie pobrać z Firebase.

Opcje łączenia modeli
W pakiecie w Twojej aplikacji
  • Model jest częścią zestawu
  • Model jest dostępny od ręki, nawet gdy urządzenie Apple jest offline
  • Nie ma potrzeby posiadania projektu Firebase
Hostowane z Firebase
  • Hostuj model, przesyłając go do Firebase Machine Learning
  • Zmniejsza rozmiar pakietu aplikacji
  • Model jest pobierany na żądanie
  • Wypychaj aktualizacje modelu bez ponownego publikowania aplikacji
  • Łatwe testowanie A/B dzięki zdalnej konfiguracji Firebase
  • Wymaga projektu Firebase

Zanim zaczniesz

  1. Jeśli chcesz pobrać model , pamiętaj o dodaniu Firebase do swojego projektu Apple , jeśli jeszcze tego nie zrobiłeś. Nie jest to wymagane podczas łączenia modelu.

  2. Dołącz biblioteki TensorFlow i Firebase do swojego Podfile:

    Aby połączyć model z aplikacją:

    Szybki

    pod 'TensorFlowLiteSwift'
    

    Cel C

    pod 'TensorFlowLiteObjC'
    

    Aby dynamicznie pobierać model z Firebase, dodaj zależność Firebase/MLModelInterpreter :

    Szybki

    pod 'TensorFlowLiteSwift'
    pod 'Firebase/MLModelInterpreter'
    

    Cel C

    pod 'TensorFlowLiteObjC'
    pod 'Firebase/MLModelInterpreter'
    
  3. Po zainstalowaniu lub zaktualizowaniu podów projektu otwórz projekt Xcode, używając jego .xcworkspace .

1. Załaduj model

Skonfiguruj lokalne źródło modelu

Aby połączyć model z aplikacją, skopiuj plik modelu i etykiet do projektu Xcode, pamiętając o wybraniu opcji Utwórz odniesienia do folderów , gdy to zrobisz. Plik modelu i etykiety zostaną uwzględnione w pakiecie aplikacji.

Spójrz też na plik tflite_metadata.json , który został utworzony wraz z modelem. Potrzebujesz dwóch wartości:

  • Wymiary wejściowe modelu. Domyślnie jest to 320x320.
  • Maksymalne wykrywanie modelu. Domyślnie jest to 40.

Skonfiguruj źródło modelu hostowane przez Firebase

Aby użyć zdalnie hostowanego modelu, utwórz obiekt CustomRemoteModel , określając nazwę, którą przypisałeś modelowi podczas jego publikowania:

Szybki

let remoteModel = CustomRemoteModel(
    name: "your_remote_model"  // The name you assigned in the Google Cloud Console.
)

Cel C

FIRCustomRemoteModel *remoteModel = [[FIRCustomRemoteModel alloc]
                                     initWithName:@"your_remote_model"];

Następnie uruchom zadanie pobierania modelu, określając warunki, na jakich chcesz zezwolić na pobieranie. Jeśli modelu nie ma na urządzeniu lub dostępna jest nowsza wersja modelu, zadanie asynchronicznie pobierze model z Firebase:

Szybki

let downloadProgress = ModelManager.modelManager().download(
    remoteModel,
    conditions: ModelDownloadConditions(
        allowsCellularAccess: true,
        allowsBackgroundDownloading: true
    )
)

Cel C

FIRModelDownloadConditions *conditions =
        [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                             allowsBackgroundDownloading:YES];
NSProgress *progress = [[FIRModelManager modelManager] downloadModel:remoteModel
                                                          conditions:conditions];

Wiele aplikacji uruchamia zadanie pobierania w swoim kodzie inicjującym, ale możesz to zrobić w dowolnym momencie, zanim będzie trzeba użyć modelu.

Utwórz wykrywacz obiektów ze swojego modelu

Po skonfigurowaniu źródeł modelu utwórz na podstawie jednego z nich obiekt Interpreter TensorFlow Lite.

Jeśli masz tylko model pakowany lokalnie, po prostu utwórz interpreter z pliku modelu:

Szybki

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()

Cel 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; }

Jeśli masz model hostowany zdalnie, przed uruchomieniem musisz sprawdzić, czy został pobrany. Stan zadania pobierania modelu można sprawdzić za pomocą metody isModelDownloaded(remoteModel:) menedżera modelu.

Chociaż musisz to potwierdzić tylko przed uruchomieniem interpretera, jeśli masz zarówno model hostowany zdalnie, jak i model w pakiecie lokalnym, może być sensowne wykonanie tego sprawdzenia podczas tworzenia instancji Interpreter : utwórz tłumacza na podstawie modelu zdalnego, jeśli jest zostały pobrane, aw innym przypadku z modelu lokalnego.

Szybki

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()

Cel 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; }

Jeśli masz tylko zdalnie hostowany model, powinieneś wyłączyć funkcje związane z modelem — na przykład wyszarzyć lub ukryć część interfejsu użytkownika — do czasu potwierdzenia, że ​​model został pobrany.

Możesz uzyskać status pobierania modelu, dołączając obserwatorów do domyślnego Centrum powiadomień. Pamiętaj, aby użyć słabego odniesienia do self w bloku obserwatora, ponieważ pobieranie może zająć trochę czasu, a źródłowy obiekt może zostać uwolniony do czasu zakończenia pobierania. Na przykład:

Szybki

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]
    // ...
}

Cel 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. Przygotuj obraz wejściowy

Następnie musisz przygotować swoje obrazy do interpretera TensorFlow Lite.

  1. Przytnij i przeskaluj obraz do wymiarów wejściowych modelu, jak określono w pliku tflite_metadata.json (domyślnie 320 x 320 pikseli). Możesz to zrobić za pomocą Core Image lub biblioteki innej firmy

  2. Skopiuj dane obrazu do obiektu Data ( NSData ):

    Szybki

    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)
      }
    }
    

    Cel 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. Uruchom wykrywacz obiektów

Następnie przekaż przygotowane wejście tłumaczowi:

Szybki

try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

Cel 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. Uzyskaj informacje o wykrytych obiektach

Jeśli wykrywanie obiektów powiedzie się, model wygeneruje jako dane wyjściowe trzy tablice po 40 elementów (lub cokolwiek, co zostało określone w pliku tflite_metadata.json ), każda. Każdy element odpowiada jednemu potencjalnemu obiektowi. Pierwsza tablica to tablica obwiedni; druga, tablica etykiet; a trzecia tablica wartości ufności. Aby uzyskać dane wyjściowe modelu:

Szybki

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)

Cel 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; }

Następnie możesz połączyć dane wyjściowe etykiet ze słownikiem etykiet:

Szybki

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))")
    }
}

Cel 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);
    }
}

Wskazówki dotyczące poprawy wydajności w czasie rzeczywistym

Jeśli chcesz oznaczać obrazy etykietami w aplikacji działającej w czasie rzeczywistym, postępuj zgodnie z poniższymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Ogranicz połączenia z detektorem. Jeśli podczas działania detektora pojawi się nowa klatka wideo, usuń tę klatkę.
  • Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik, a następnie wyrenderuj obraz i nakładkę w jednym kroku. W ten sposób renderujesz na powierzchni wyświetlania tylko raz dla każdej klatki wejściowej. Przykład można znaleźć w klasach PreviewOverlayView i FIRDetectionOverlayView w przykładowej aplikacji do prezentacji.