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

Za pomocą ML Kit możesz rozpoznawać obiekty na obrazie i przypisywać im etykiety. Ten interfejs API obsługuje szeroką gamę niestandardowych modeli klasyfikacji obrazów. Wskazówki dotyczące wymagań związanych z kompatybilnością modeli, miejsc, w których można znaleźć wstępnie wytrenowane modele, oraz sposobu trenowania własnych modeli znajdziesz w artykule Modele niestandardowe w ML Kit.

Model niestandardowy można zintegrować na 2 sposoby. Model możesz dołączyć do aplikacji, umieszczając go w folderze zasobów, lub pobrać go dynamicznie z Cloud Storage. W tabeli poniżej porównaliśmy te 2 opcje.

Model pakietowy Model hostowany
Model jest częścią pliku APK aplikacji, co zwiększa jego rozmiar. Model nie jest częścią pliku APK. Jest on hostowany przez przesłanie do Cloud Storage. Zalecamy korzystanie z Cloud Storage dla Firebase.
Model jest dostępny od razu, nawet gdy urządzenie z Androidem jest offline. Aplikacja musi zawierać kod umożliwiający pobieranie modelu na żądanie.
Nie musisz mieć projektu w Firebase Wymaga projektu w Firebase (jeśli używasz Cloud Storage dla Firebase).
Aby zaktualizować model, musisz ponownie opublikować aplikację Wysyłanie aktualizacji modelu bez ponownego publikowania aplikacji
Brak wbudowanych testów A/B Testy A/B za pomocą Zdalnej konfiguracji Firebase

Wypróbuj

Zanim zaczniesz

  1. Dodaj biblioteki ML Kit do pliku Podfile:

    pod 'GoogleMLKit/ImageLabelingCustom', '8.0.0'
    
  2. Po zainstalowaniu lub zaktualizowaniu Pods w projekcie otwórz projekt Xcode, używając pliku .xcworkspace. ML Kit jest obsługiwany w Xcode w wersji 13.2.1 lub nowszej.

  3. Jeśli chcesz pobrać model za pomocą Cloud Storage dla Firebase, upewnij się, że dodasz Firebase do projektu na iOS, jeśli jeszcze tego nie zrobisz. Nie jest to wymagane, gdy dołączasz model.

1. Wczytywanie modelu

Konfigurowanie źródła modelu lokalnego

Aby połączyć model z aplikacją:

  1. Skopiuj plik modelu (zwykle z rozszerzeniem .tflite lub .lite) do projektu Xcode, pamiętając, aby wybrać 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];

Konfigurowanie zdalnego źródła modelu

Aby użyć modelu hostowanego zdalnie, musisz pobrać plik modelu do pamięci lokalnej urządzenia za pomocą własnej logiki aplikacji, a następnie wczytać go jako model lokalny. Do hostowania modelu zalecamy używanie Cloud Storage dla Firebase. Szczegółowe informacje o wdrażaniu znajdziesz w przewodniku po migracji z Firebase ML do Cloud Storage.

Konfigurowanie narzędzia do etykietowania obrazów

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

Dostępne są te ustawienia:

Opcje
confidenceThreshold

Minimalny poziom ufności wykrytych etykiet. Jeśli nie zostanie ustawiony, użyty zostanie dowolny próg klasyfikatora określony w metadanych modelu. Jeśli model nie zawiera metadanych lub metadane nie określają progu klasyfikatora, zostanie użyty domyślny próg 0,0.

maxResultCount

Maksymalna liczba etykiet do zwrócenia. Jeśli nie zostanie ustawiony, użyta zostanie wartość domyślna 10.

Jeśli masz tylko model dołączony lokalnie, utwórz narzędzie do etykietowania z 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ł pobrany.

Chociaż musisz potwierdzić to tylko przed uruchomieniem etykietowania, jeśli masz zarówno model hostowany zdalnie, jak i model dołączony lokalnie, warto przeprowadzić to sprawdzenie podczas tworzenia instancji ImageLabeler: utwórz etykietowanie na podstawie modelu zdalnego, jeśli został on pobrany, a w przeciwnym razie na podstawie modelu lokalnego.

Swift

// Path where your download logic saves the model
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")

let model: LocalModel
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Use the downloaded model
  model = LocalModel(path: localModelURL.path)
} else {
  // Fall back to bundled model
  guard let bundledModelPath = Bundle.main.path(forResource: "model", ofType: "tflite") else { return }
  model = LocalModel(path: bundledModelPath)
}

let options = CustomImageLabelerOptions(localModel: model)
let imageLabeler = ImageLabeler.imageLabeler(options: options)

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];

MLKLocalModel *model;
if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Use the downloaded model
  model = [[MLKLocalModel alloc] initWithPath:localModelPath];
} else {
  // Fall back to bundled model
  NSString *bundledModelPath = [NSBundle.mainBundle pathForResource:@"model" ofType:@"tflite"];
  model = [[MLKLocalModel alloc] initWithPath:bundledModelPath];
}

MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:model];
MLKImageLabeler *imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

Jeśli masz tylko model hostowany zdalnie, wyłącz funkcje związane z modelem, np. wyszarz lub ukryj część interfejsu, dopóki nie potwierdzisz, że model został pobrany.

Swift

let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let localModelURL = documentDirectory.appendingPathComponent("my_remote_model.tflite")
if FileManager.default.fileExists(atPath: localModelURL.path) {
  // Model is already cached, initialize immediately
  self.initializeLabeler(with: localModelURL)
} else {
  // Model is not yet available, show loading UI and start download
  self.showLoadingUI()
  let storage = Storage.storage()
  let modelRef = storage.reference(forURL: "gs://YOUR_BUCKET/path/to/model.tflite")
  modelRef.write(toFile: localModelURL) { url, error in
    self.hideLoadingUI()
    if let error = error {
      // Handle download error
      self.showErrorUI()
    } else if let modelURL = url {
      // Download success, initialize labeler
      self.initializeLabeler(with: modelURL)
    }
  }
}

func initializeLabeler(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomImageLabelerOptions(localModel: localModel)
  self.imageLabeler = ImageLabeler.imageLabeler(options: options)
  // Enable ML-related UI features here
  self.enableMLFeatures()
}

Objective-C

NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *localModelPath = [documentsDirectory stringByAppendingPathComponent:@"my_remote_model.tflite"];
NSURL *localModelURL = [NSURL fileURLWithPath:localModelPath];

if ([NSFileManager.defaultManager fileExistsAtPath:localModelPath]) {
  // Model is already cached, initialize immediately
  [self initializeLabelerWithURL:localModelURL];
} else {
  // Model is not yet available, show loading UI and start download
  [self showLoadingUI];

  FIRStorage *storage = [FIRStorage storage];
  FIRStorageReference *modelRef = [storage referenceForURL:@"gs://YOUR_BUCKET/path/to/model.tflite"];

  [modelRef writeToFile:localModelURL
             completion:^(NSURL * _Nullable URL, NSError * _Nullable error) {
               [self hideLoadingUI];
               if (error != nil) {
                 // Handle download error
                 [self showErrorUI];
               } else {
                 // Download success, initialize labeler
                 [self initializeLabelerWithURL:URL];
               }
             }];
}

- (void)initializeLabelerWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomImageLabelerOptions *options = [[MLKCustomImageLabelerOptions alloc] initWithLocalModel:localModel];
  self.imageLabeler = [MLKImageLabeler imageLabelerWithOptions:options];

  // Enable ML-related UI features here
  [self enableMLFeatures];
}

2. Przygotowywanie obrazu wejściowego

Utwórz obiekt VisionImage za pomocą UIImage lub CMSampleBuffer.

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

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

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

    Aby uzyskać 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 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 CMSampleBuffer i 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. Uruchomienie narzędzia do etykietowania obrazów

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

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. Uzyskiwanie informacji o oznaczonych encjach

Jeśli operacja etykietowania obrazu się powiedzie, zwróci tablicę obiektów ImageLabel. Każdy symbol ImageLabel reprezentuje coś, co zostało oznaczone na obrazie. Możesz uzyskać tekstowy opis każdej etykiety (jeśli jest dostępny w metadanych pliku modelu LiteRT), 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 działającej w czasie rzeczywistym, postępuj zgodnie z tymi wytycznymi, aby uzyskać najlepszą liczbę klatek na sekundę:

  • Do przetwarzania klatek wideo używaj results(in:)synchronicznego interfejsu API detektora. Wywołaj tę metodę z funkcji AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:), aby synchronicznie uzyskać wyniki z danej klatki filmu. Ustaw AVCaptureVideoDataOutput alwaysDiscardsLateVideoFrames na true, aby ograniczyć liczbę wywołań detektora. Jeśli podczas działania detektora pojawi się nowa klatka wideo, zostanie ona odrzucona.
  • Jeśli używasz danych wyjściowych detektora do nakładania grafiki na obraz wejściowy, najpierw uzyskaj wynik z ML Kit, a potem w jednym kroku wyrenderuj obraz i nałóż na niego grafikę. Dzięki temu renderujesz na powierzchnię wyświetlania tylko raz dla każdej przetworzonej klatki wejściowej. Przykład znajdziesz w funkcji updatePreviewOverlayViewWithLastFrame w przykładowej aplikacji ML Kit na początek.