Oznaczanie obrazów etykietami za pomocą modelu wytrenowanego przez AutoML na platformach Apple

Po wytrenowaniu własnego modelu z użyciem AutoML Vision Edge możesz go używać w aplikacji do oznaczania obrazów etykietami.

Modele wytrenowane za pomocą AutoML Vision Edge można integrować na 2 sposoby. Możesz zgrupować model, kopiując jego pliki do projektu Xcode, lub pobrać go dynamicznie z Firebase.

Opcje grupowania modeli
W pakiecie w aplikacji
  • Model jest częścią pakietu
  • Model jest dostępny od razu, nawet gdy urządzenie Apple jest offline.
  • Nie musisz tworzyć projektu Firebase
Hostowany w Firebase

Zanim zaczniesz

  1. Umieść biblioteki ML Kit w pliku Podfile:

    Aby połączyć model z aplikacją:

    pod 'GoogleMLKit/ImageLabelingCustom'
    

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

    pod 'GoogleMLKit/ImageLabelingCustom'
    pod 'GoogleMLKit/LinkFirebase'
    
  2. Po zainstalowaniu lub zaktualizowaniu pakietów projektu otwórz projekt Xcode za pomocą .xcworkspace. ML Kit jest obsługiwany w Xcode w wersji 12.2 lub nowszej.

  3. Jeśli chcesz pobrać model, pamiętaj, aby dodać Firebase do swojego projektu na Androida, chyba że masz to już za sobą. Nie jest to wymagane przy pakowaniu modelu.

1. Wczytaj model

Konfigurowanie źródła lokalnego modelu

Aby połączyć model z aplikacją:

  1. Wyodrębnij model i jego metadane z archiwum ZIP pobranego z konsoli Firebase do folderu:

    your_model_directory
      |____dict.txt
      |____manifest.json
      |____model.tflite
    

    Wszystkie 3 pliki muszą znajdować się w tym samym folderze. Zalecamy używanie plików w postaci pobranej, bez wprowadzania zmian (w tym nazw plików).

  2. Skopiuj folder do projektu Xcode, pamiętając o zaznaczeniu opcji Utwórz odwołania do folderu. Plik modelu i metadane zostaną uwzględnione w pakiecie aplikacji i będą dostępne dla ML Kit.

  3. Utwórz obiekt LocalModel, podając ścieżkę do pliku manifestu modelu:

    Swift

    guard let manifestPath = Bundle.main.path(
        forResource: "manifest",
        ofType: "json",
        inDirectory: "your_model_directory"
    ) else { return true }
    let localModel = LocalModel(manifestPath: manifestPath)
    

    Objective-C

    NSString *manifestPath =
        [NSBundle.mainBundle pathForResource:@"manifest"
                                      ofType:@"json"
                                 inDirectory:@"your_model_directory"];
    MLKLocalModel *localModel =
        [[MLKLocalModel alloc] initWithManifestPath:manifestPath];
    

Konfigurowanie źródła modelu hostowanego w Firebase

Aby użyć modelu hostowanego zdalnie, utwórz obiekt CustomRemoteModel, podając nazwę przypisaną do modelu podczas jego publikowania:

Swift

// Initialize the model source with the name you assigned in
// the Firebase console.
let remoteModelSource = FirebaseModelSource(name: "your_remote_model")
let remoteModel = CustomRemoteModel(remoteModelSource: remoteModelSource)

Objective-C

// Initialize the model source with the name you assigned in
// the Firebase console.
MLKFirebaseModelSource *firebaseModelSource =
    [[MLKFirebaseModelSource alloc] initWithName:@"your_remote_model"];
MLKCustomRemoteModel *remoteModel =
    [[MLKCustomRemoteModel alloc] initWithRemoteModelSource:firebaseModelSource];

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

Swift

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

MLKModelDownloadConditions *downloadConditions =
    [[MLKModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[MLKModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

Wiele aplikacji inicjuje zadanie pobierania w kodzie inicjującym, ale możesz to zrobić w dowolnym momencie, zanim zaczniesz używać modelu.

Tworzenie etykietowania obrazu na podstawie modelu

Po skonfigurowaniu źródeł modeli utwórz na ich podstawie obiekt ImageLabeler.

Jeśli masz tylko model zainstalowany lokalnie, utwórz etykietownik na podstawie obiektu LocalModel i skonfiguruj próg poziomu ufności, który chcesz wymagać (patrz Ocenianie modelu):

Swift

let options = CustomImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)  // Evaluate your model in the Cloud console
                                                    // to determine an appropriate value.
let imageLabeler = ImageLabeler.imageLabeler(options)

Objective-C

CustomImageLabelerOptions *options =
    [[CustomImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0f);  // Evaluate your model in the Cloud console
                                        // to determine an appropriate value.
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

Jeśli model jest hostowany zdalnie, przed jego uruchomieniem musisz sprawdzić, czy został pobrany. Stan pobierania modelu możesz sprawdzić, korzystając z metody isModelDownloaded(remoteModel:) menedżera modeli.

Chociaż musisz to potwierdzić przed uruchomieniem osoby oznaczającej etykietami, jeśli masz zarówno model hostowany zdalnie, jak i model umieszczony lokalnie, warto przeprowadzić tę kontrolę podczas tworzenia wystąpienia ImageLabeler: utwórz etykietę na podstawie modelu zdalnego (jeśli został pobrany) lub z modelu lokalnego.

Swift

var options: CustomImageLabelerOptions
if (ModelManager.modelManager().isModelDownloaded(remoteModel)) {
  options = CustomImageLabelerOptions(remoteModel: remoteModel)
} else {
  options = CustomImageLabelerOptions(localModel: localModel)
}
options.confidenceThreshold = NSNumber(value: 0.0)  // Evaluate your model in the Firebase console
                                                    // to determine an appropriate value.
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options;
if ([[MLKModelManager modelManager] isModelDownloaded:remoteModel]) {
  options = [[MLKCustomImageLabelerOptions alloc] initWithRemoteModel:remoteModel];
} else {
  options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
}
options.confidenceThreshold = @(0.0f);  // Evaluate your model in the Firebase console
                                        // to determine an appropriate value.
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

Jeśli masz tylko model hostowany zdalnie, wyłącz związane z nim funkcje – na przykład wyszarzanie lub ukrycie części interfejsu użytkownika – do czasu potwierdzenia pobrania modelu.

Stan pobierania modelu możesz uzyskać, dołączając obserwatorów do domyślnego Centrum powiadomień. W bloku obserwatora używaj słabego odwołania do self, ponieważ pobieranie może zająć trochę czasu, a obiekt źródłowy może zostać zwolniony przed zakończeniem pobierania. Przykład:

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitMLModelDownloadDidSucceed,
    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: .mlkitMLModelDownloadDidFail,
    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:MLKModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              MLKRemoteModel *model = note.userInfo[MLKModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:MLKModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[MLKModelDownloadUserInfoKeyError];
            }];

2. Przygotuj obraz wejściowy

Utwórz obiekt VisionImage za pomocą obiektu UIImage lub CMSampleBufferRef.

Jeśli używasz UIImage, wykonaj te czynności:

  • Utwórz obiekt VisionImage za pomocą funkcji UIImage. Pamiętaj, aby podać prawidłowy adres .orientation.

    Swift

    let image = VisionImage(image: uiImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

Jeśli używasz CMSampleBufferRef, wykonaj te czynności:

  • Określ orientację danych obrazu zawartych w buforze CMSampleBufferRef.

    Aby sprawdzić orientację obrazu:

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                          : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                          : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                          : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return position == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                          : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • Utwórz obiekt VisionImage, używając obiektu CMSampleBufferRef i jego orientacji:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. Uruchom narzędzie do etykietowania obrazów

Asynchronicznie:

Swift

imageLabeler.process(image) { labels, error in
    guard error == nil, let labels = labels, !labels.isEmpty else {
        // Handle the error.
        return
    }
    // Show results.
}

Objective-C

[imageLabeler
    processImage:image
      completion:^(NSArray<MLKImageLabel *> *_Nullable labels,
                   NSError *_Nullable error) {
        if (label.count == 0) {
            // Handle the error.
            return;
        }
        // Show results.
     }];

Synchronicznie:

Swift

var labels: [ImageLabel]
do {
    labels = try imageLabeler.results(in: image)
} catch let error {
    // Handle the error.
    return
}
// Show results.

Objective-C

NSError *error;
NSArray<MLKImageLabel *> *labels =
    [imageLabeler resultsInImage:image error:&error];
// Show results or handle the error.

4. Uzyskiwanie informacji o obiektach oznaczonych etykietami

Jeśli operacja etykietowania obrazu się powiedzie, zwróci tablicę ImageLabel. Każdy element ImageLabel reprezentuje coś, co zostało opisane na obrazie. Możesz uzyskać opis tekstowy każdej etykiety (jeśli jest dostępny w metadanych pliku modelu TensorFlow Lite), wskaźnik ufności i indeks. Przykład:

Swift

for label in labels {
  let labelText = label.text
  let confidence = label.confidence
  let index = label.index
}

Objective-C

for (MLKImageLabel *label in labels) {
  NSString *labelText = label.text;
  float confidence = label.confidence;
  NSInteger index = label.index;
}

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

Jeśli chcesz oznaczać obrazy w aplikacji w czasie rzeczywistym, postępuj zgodnie z tymi wskazówkami, aby uzyskać najlepszą liczbę klatek na sekundę:

  • ograniczać wywołania do tego detektora. Jeśli podczas działania detektora pojawi się nowa klatka wideo, odrzuć ją.
  • Jeśli używasz danych wyjściowych z detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik, a potem renderuj obraz i nakładaj go w jednym kroku. Dzięki temu renderowanie na powierzchni wyświetlania będzie odbywać się tylko raz na każdą klatkę wejściową. Przykładem są klasy previewOverlayViewFIRDetectionOverlayView w przykładowej aplikacji z galerii.