Oznaczanie obrazów etykietami za pomocą modelu niestandardowego w iOS

Za pomocą ML Kit możesz rozpoznawać encje na obrazach i oznaczać je etykietami. Ten interfejs API obsługuje szeroką gamę niestandardowych modeli klasyfikacji obrazów. W artykule Modele niestandardowe z użyciem ML Kit dowiesz się, jakie są wymagania dotyczące zgodności modeli, gdzie znaleźć wytrenowane modele i jak trenować własne modele.

Model niestandardowy można zintegrować na 2 sposoby. Możesz połączyć model, umieszczając go w folderze zasobów aplikacji lub pobrać dynamicznie z Firebase. Tabela poniżej zawiera porównanie obu opcji.

Model w pakiecie Model hostowany
Model jest częścią pliku APK aplikacji, co zwiększa swój rozmiar. Ten model nie jest częścią Twojego pliku APK. Jest on hostowany przez przesłanie go do systemów uczących się Firebase.
Model jest dostępny natychmiast, nawet jeśli urządzenie z Androidem jest offline. Model jest pobierany na żądanie
Nie potrzebujesz projektu Firebase Wymaga projektu Firebase
Aby zaktualizować model, musisz ponownie opublikować aplikację Przesyłaj aktualizacje modelu bez ponownego publikowania aplikacji
Brak wbudowanych testów A/B łatwe testy A/B dzięki Zdalnej konfiguracji Firebase.

Wypróbuj

Zanim zaczniesz

  1. Dodaj biblioteki ML Kit do pliku Podfile:

    Aby dodać model do pakietu z aplikacją:

    pod 'GoogleMLKit/ImageLabelingCustom', '3.2.0'
    

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

    pod 'GoogleMLKit/ImageLabelingCustom', '3.2.0'
    pod 'GoogleMLKit/LinkFirebase', '3.2.0'
    
  2. Po zainstalowaniu lub zaktualizowaniu podów w projekcie otwórz projekt Xcode, korzystając z .xcworkspace. ML Kit obsługuje Xcode w wersji 13.2.1 lub nowszej.

  3. Jeśli chcesz pobrać model, dodaj Firebase do swojego projektu iOS, jeśli jeszcze tego nie zrobiłeś. Nie jest to wymagane podczas pakowania modelu.

1. Wczytywanie modelu

Skonfiguruj źródło modelu lokalnego

Aby połączyć model z aplikacją:

  1. Skopiuj do projektu Xcode plik modelu (zwykle kończący się na .tflite lub .lite), wybierając przy tym Copy bundle resources. Plik modelu zostanie dołączony do pakietu aplikacji i będzie dostępny dla ML Kit.

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

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

    MLKLocalModel *localModel =
        [[MLKLocalModel alloc] initWithPath:localModelFilePath];

Skonfiguruj źródło modelu hostowane w Firebase

Aby użyć modelu hostowanego zdalnie, utwórz obiekt RemoteModel z nazwą przypisaną do modelu w chwili jego opublikowania:

Swift

let firebaseModelSource = FirebaseModelSource(
    name: "your_remote_model") // The name you assigned in
                               // the Firebase console.
let remoteModel = CustomRemoteModel(remoteModelSource: firebaseModelSource)

Objective-C

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

Następnie rozpocznij zadanie pobierania modelu, określając warunki, które mają mieć możliwość pobierania. Jeśli modelu nie ma na urządzeniu lub dostępna jest jego nowsza wersja, zadanie pobierze asynchronicznie 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] downloadModel:remoteModel
                                       conditions:downloadConditions];

Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjowania, ale możesz to zrobić w dowolnym momencie, zanim zajdzie potrzeba używania modelu.

Konfigurowanie oznaczania obrazów etykietami

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

Dostępne są te ustawienia:

Opcje
confidenceThreshold

Minimalny wskaźnik ufności wykrytych etykiet. Jeśli nie skonfigurujesz tej zasady, zostanie użyty dowolny próg klasyfikatora określony przez metadane modelu. Jeśli model nie zawiera żadnych metadanych lub metadane nie określają progu klasyfikatora, zostanie użyty domyślny próg równy 0,0.

maxResultCount

Maksymalna liczba etykiet do zwrócenia. Jeśli zasada nie jest skonfigurowana, używana jest wartość domyślna, czyli 10.

Jeśli masz tylko model dołączony do pakietu lokalnie, po prostu utwórz etykietę na podstawie obiektu LocalModel:

Swift

let options = CustomImageLabelerOptions(localModel: localModel)
options.confidenceThreshold = NSNumber(value: 0.0)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

MLKCustomImageLabelerOptions *options =
    [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
options.confidenceThreshold = @(0.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

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

Musisz to potwierdzić przed uruchomieniem osoby oznaczającej etykietami, ale jeśli używasz zarówno modelu hostowanego zdalnie, jak i modelu dołączonego lokalnie, podczas tworzenia wystąpienia obiektu ImageLabeler warto wykonać tę procedurę sprawdzania: utwórz etykietę z modelu zdalnego (jeśli został on pobrany, a w przeciwnym razie 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)
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.0);
MLKImageLabeler *imageLabeler =
    [MLKImageLabeler imageLabelerWithOptions:options];

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

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

Swift

NotificationCenter.default.addObserver(
    forName: .mlkitModelDownloadDidSucceed,
    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: .mlkitModelDownloadDidFail,
    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ą UIImage lub CMSampleBuffer.

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

  • Utwórz obiekt VisionImage za pomocą UIImage. Pamiętaj, aby podać prawidłową wartość .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 urządzenia CMSampleBuffer, wykonaj te czynności:

  • Określ orientację danych obrazu w elemencie CMSampleBuffer.

    Aby pobrać orientację:

    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 cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • Utwórz obiekt VisionImage, używając obiektu i orientacji CMSampleBuffer:

    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. Uruchamianie narzędzia do etykietowania obrazów

Aby oznaczyć etykietami obiekty na obrazie, przekaż obiekt image do metody process() metody ImageLabeler.

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 *_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 *labels =
    [imageLabeler resultsInImage:image error:&error];
// Show results or handle the error.

4. Uzyskaj informacje o elementach oznaczonych etykietami

Jeśli operacja oznaczania obrazów zakończy się powodzeniem, zwróci tablicę ImageLabel. Każdy element ImageLabel reprezentuje coś, co zostało oznaczone 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. Na 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ć etykietami obrazy w aplikacji przesyłającej dane w czasie rzeczywistym, postępuj zgodnie z tymi wytycznymi, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Do przetwarzania klatek wideo użyj synchronicznego interfejsu API results(in:) wzorca. Wywołaj tę metodę z funkcji captureOutput(_, didOutput:from:) obiektu AVCaptureVideoDataOutputSampleBufferDelegate, aby synchronicznie pobierać wyniki z danej klatki wideo. Pozostaw alwaysDiscardsLateVideoFrames obiektu AVCaptureVideoDataOutput jako true, aby ograniczać wywołania do wzorca. Jeśli podczas działania wzorca pojawi się nowa ramka wideo, zostanie ona usunięta.
  • Jeśli używasz danych wyjściowych wzorca do nakładania grafiki na obraz wejściowy, najpierw pobierz wynik z ML Kit, a następnie wyrenderuj obraz i nakładkę w jednym kroku. Dzięki temu na każdą przetworzoną klatkę wejściową renderujesz się tylko raz. Przykład znajdziesz w sekcji updatePreviewOverlayViewWithLastFrame w przykładowym krótkim wprowadzeniu do ML Kit.