Wykrywaj, śledź i klasyfikuj obiekty za pomocą niestandardowego modelu klasyfikacji w iOS

Za pomocą ML Kit możesz wykrywać i śledzić obiekty w kolejnych klatkach filmu.

Gdy przekażesz obraz do ML Kit, wykryje on maksymalnie 5 obiektów na obrazie wraz z ich położeniem. Podczas wykrywania obiektów w strumieniach wideo każdy obiekt ma unikalny identyfikator, którego możesz używać do śledzenia obiektu w kolejnych klatkach.

Do klasyfikowania wykrytych obiektów możesz użyć niestandardowego modelu klasyfikacji obrazów. Więcej informacji o wymaganiach dotyczących zgodności modeli, o tym, gdzie znaleźć wstępnie wytrenowane modele, i o tym, jak trenować własne modele, znajdziesz w artykule Modele niestandardowe w ML Kit.

Istnieją 2 sposoby integracji modelu niestandardowego. Możesz połączyć model, umieszczając go w folderze zasobów aplikacji, lub pobrać go dynamicznie z Cloud Storage. W tabeli poniżej porównujemy te 2 opcje.

Model połączony Model hostowany
Model jest częścią pliku .ipa aplikacji, co zwiększa jego rozmiar. Model nie jest częścią pliku .ipa aplikacji. Jest 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 jest wymagany projekt w Firebase. Wymaga projektu w Firebase (jeśli używasz Cloud Storage dla Firebase).
Aby zaktualizować model, musisz ponownie opublikować aplikację. Wysyłaj aktualizacje modelu bez ponownego publikowania aplikacji.
Brak wbudowanych testów A/B. Testy A/B z użyciem Zdalnej konfiguracji Firebase.

Wypróbuj

  • Przykład użycia modelu połączonego znajdziesz w aplikacji z krótkim wprowadzeniem do Vision, a przykład użycia modelu hostowanego – w aplikacji z krótkim wprowadzeniem do AutoML.
  • Kompletną implementację tego interfejsu API znajdziesz w aplikacji demonstracyjnej Material Design.

Zanim zaczniesz

  1. Dodaj biblioteki ML Kit do pliku Podfile:

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.0'
    
  2. Po zainstalowaniu lub zaktualizowaniu podów projektu otwórz projekt Xcode za pomocą 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 łączysz model.

1. Wczytaj model

Konfigurowanie lokalnego źródła modelu

Aby połączyć model z aplikacją:

  1. Skopiuj plik modelu (zwykle z rozszerzeniem .tflite lub .lite) do projektu Xcode, pamiętając, aby wybrać opcję Copy bundle resources (Kopiuj zasoby pakietu). Plik modelu zostanie uwzględniony w pakiecie aplikacji i będzie dostępny dla ML Kit.

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

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

Konfigurowanie źródła modelu hostowanego zdalnie

Aby używać 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 implementacji znajdziesz w przewodniku po migracji z Firebase ML do Cloud Storage.

2. Konfigurowanie detektora obiektów

Po skonfigurowaniu źródeł modelu skonfiguruj detektor obiektów pod kątem swojego przypadku użycia za pomocą obiektu CustomObjectDetectorOptions. Możesz zmienić te ustawienia:

Ustawienia detektora obiektów
Tryb wykrywania STREAM_MODE (domyślny) | SINGLE_IMAGE_MODE

W trybie STREAM_MODE (domyślnym) detektor obiektów działa z krótkim czasem oczekiwania, ale podczas pierwszych kilku wywołań może generować niepełne wyniki (np. nieokreślone ramki ograniczające lub etykiety kategorii). W trybie STREAM_MODE, detektor przypisuje też identyfikatory śledzenia do obiektów, których możesz używać do śledzenia obiektów w kolejnych klatkach. Używaj tego trybu, gdy chcesz śledzić obiekty lub gdy ważny jest krótki czas oczekiwania, np. podczas przetwarzania strumieni wideo w czasie rzeczywistym.

W trybie SINGLE_IMAGE_MODE detektor obiektów zwraca wynik po określeniu ramki ograniczającej obiektu. Jeśli włączysz też klasyfikację, zwróci wynik po udostępnieniu ramki ograniczającej i etykiety kategorii. W rezultacie, czas oczekiwania na wykrycie może być dłuższy. W trybie SINGLE_IMAGE_MODE nie są też przypisywane identyfikatory śledzenia. Używaj tego trybu, jeśli czas oczekiwania nie jest krytyczny i nie chcesz mieć do czynienia z częściowymi wynikami.

Wykrywanie i śledzenie wielu obiektów false (domyślne) | true

Określa, czy wykrywać i śledzić maksymalnie 5 obiektów, czy tylko najbardziej widoczny obiekt (domyślnie).

Klasyfikowanie obiektów false (domyślne) | true

Określa, czy klasyfikować wykryte obiekty za pomocą podanego niestandardowego modelu klasyfikatora. Aby używać niestandardowego modelu klasyfikacji model, musisz ustawić tę wartość na true.

Próg ufności klasyfikacji

Minimalny wynik ufności wykrytych etykiet. Jeśli nie zostanie ustawiony, będzie używany 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.

Maksymalna liczba etykiet na obiekt

Maksymalna liczba etykiet na obiekt, które zwróci detektor. Jeśli nie zostanie ustawiony, zostanie użyta wartość domyślna 10.

Jeśli masz tylko model połączony lokalnie, utwórz detektor obiektów z obiektu LocalModel:

Swift

let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3

Objective-C

MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;

Jeśli masz model hostowany zdalnie, przed jego uruchomieniem musisz sprawdzić, czy został pobrany.

Chociaż musisz to potwierdzić tylko przed uruchomieniem detektora obiektów, jeśli masz zarówno model hostowany zdalnie, jak i model połączony lokalnie, warto przeprowadzić tę kontrolę podczas tworzenia instancji ObjectDetector: utwórz detektor z modelu zdalnego, jeśli został pobrany, a w przeciwnym razie z 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 = CustomObjectDetectorOptions(localModel: model)
options.detectorMode = .singleImage
options.shouldEnableClassification = true
options.shouldEnableMultipleObjects = true
options.classificationConfidenceThreshold = NSNumber(value: 0.5)
options.maxPerObjectLabelCount = 3
let objectDetector = ObjectDetector.objectDetector(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];
}

MLKCustomObjectDetectorOptions *options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:model];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableClassification = YES;
options.shouldEnableMultipleObjects = YES;
options.classificationConfidenceThreshold = @(0.5);
options.maxPerObjectLabelCount = 3;
MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions: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.initializeDetector(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 detector
      self.initializeDetector(with: modelURL)
    }
  }
}

func initializeDetector(with modelURL: URL) {
  let localModel = LocalModel(path: modelURL.path)
  let options = CustomObjectDetectorOptions(localModel: localModel)
  options.detectorMode = .singleImage
  options.shouldEnableClassification = true
  options.shouldEnableMultipleObjects = true
  self.objectDetector = ObjectDetector.objectDetector(options: options)
  // Enable ML features in UI
  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 initializeDetectorWithURL: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 detector
                 [self initializeDetectorWithURL:URL];
               }
             }];
}

- (void)initializeDetectorWithURL:(NSURL *)modelURL {
  MLKLocalModel *localModel = [[MLKLocalModel alloc] initWithPath:modelURL.path];
  MLKCustomObjectDetectorOptions *options = [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
  options.detectorMode = MLKObjectDetectorModeSingleImage;
  options.shouldEnableClassification = YES;
  options.shouldEnableMultipleObjects = YES;
  self.objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];

  // Enable ML features in UI
  [self enableMLFeatures];
}

Interfejs API do wykrywania i śledzenia obiektów jest zoptymalizowany pod kątem tych 2 podstawowych przypadków użycia:

  • Wykrywanie i śledzenie w czasie rzeczywistym najbardziej widocznego obiektu w wizjerze aparatu.
  • Wykrywanie wielu obiektów na obrazie statycznym.

Aby skonfigurować interfejs API pod kątem tych przypadków użycia:

Swift

// Live detection and tracking
let options = CustomObjectDetectorOptions(localModel: localModel)
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

// Multiple object detection in static images
let options = CustomObjectDetectorOptions(localModel: localModel)
options.detectorMode = .singleImage
options.shouldEnableMultipleObjects = true
options.shouldEnableClassification = true
options.maxPerObjectLabelCount = 3

Objective-C

// Live detection and tracking
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

// Multiple object detection in static images
MLKCustomObjectDetectorOptions *options =
    [[MLKCustomObjectDetectorOptions alloc] initWithLocalModel:localModel];
options.detectorMode = MLKObjectDetectorModeSingleImage;
options.shouldEnableMultipleObjects = YES;
options.shouldEnableClassification = YES;
options.maxPerObjectLabelCount = 3;

3. Przygotuj obraz wejściowy

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

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

  • Utwórz obiekt VisionImage za pomocą UIImage. Pamiętaj, aby określić 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 CMSampleBuffer, wykonaj te czynności:

  • Określ orientację danych obrazu zawartych w 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 za pomocą 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];

4. Utwórz i uruchom detektor obiektów

  1. Utwórz nowy detektor obiektów:

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. Następnie użyj detektora:

    Asynchronicznie:

    Swift

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

    Objective-C

    [objectDetector
        processImage:image
          completion:^(NSArray *_Nullable objects,
                       NSError *_Nullable error) {
            if (objects.count == 0) {
                // Handle the error.
                return;
            }
            // Show results.
         }];

    Synchronicznie:

    Swift

    var objects: [Object]
    do {
        objects = try objectDetector.results(in: image)
    } catch let error {
        // Handle the error.
        return
    }
    // Show results.

    Objective-C

    NSError *error;
    NSArray *objects =
        [objectDetector resultsInImage:image error:&error];
    // Show results or handle the error.

5. Uzyskaj informacje o oznaczonych obiektach

Jeśli wywołanie procesora obrazów się powiedzie, przekaże on listę obiektów Object do procedury obsługi zakończenia lub zwróci listę w zależności od tego, czy wywołasz metodę asynchroniczną czy synchroniczną.

Każdy obiekt Object zawiera te właściwości:

frame A CGRect wskazujący położenie obiektu na obrazie.
trackingID Liczba całkowita, która identyfikuje obiekt na obrazach, lub `nil` w SINGLE_IMAGE_MODE.
labels
label.text Tekstowy opis etykiety. Zwracany tylko wtedy, gdy metadane modelu LiteRT zawierają opisy etykiet.
label.index Indeks etykiety wśród wszystkich etykiet obsługiwanych przez klasyfikator.
label.confidence Poziom ufności klasyfikacji obiektu.

Swift

// objects contains one item if multiple object detection wasn't enabled.
for object in objects {
  let frame = object.frame
  let trackingID = object.trackingID
  let description = object.labels.enumerated().map { (index, label) in
    "Label \(index): \(label.text), \(label.confidence), \(label.index)"
  }.joined(separator: "\n")
}

Objective-C

// The list of detected objects contains one item if multiple object detection
// wasn't enabled.
for (MLKObject *object in objects) {
  CGRect frame = object.frame;
  NSNumber *trackingID = object.trackingID;
  for (MLKObjectLabel *label in object.labels) {
    NSString *labelString =
        [NSString stringWithFormat:@"%@, %f, %lu",
                                   label.text,
                                   label.confidence,
                                   (unsigned long)label.index];
  }
}

Zapewnianie wygody użytkownikom

Aby zapewnić użytkownikom jak największą wygodę, w aplikacji stosuj te wytyczne:

  • Skuteczne wykrywanie obiektów zależy od ich złożoności wizualnej. Aby można było wykryć obiekty z niewielką liczbą cech wizualnych, mogą one zajmować większą część obrazu. Powinieneś(-aś) poinformować użytkowników, jak robić zdjęcia, które dobrze sprawdzają się w przypadku wykrywanych obiektów.
  • Jeśli używasz klasyfikacji i chcesz wykrywać obiekty, które nie pasują do obsługiwanych kategorii, zaimplementuj specjalną obsługę nieznanych obiektów.

Zapoznaj się też z aplikacją demonstracyjną ML Kit Material Design i zbiorem wzorców Material Design dla funkcji opartych na uczeniu maszynowym.

Zwiększanie skuteczności

Jeśli chcesz używać wykrywania obiektów w aplikacji działającej w czasie rzeczywistym, stosuj te wytyczne, aby uzyskać jak najlepszą liczbę klatek na sekundę:

  • Jeśli używasz trybu przesyłania strumieniowego w aplikacji działającej w czasie rzeczywistym, nie używaj wykrywania wielu obiektów, ponieważ większość urządzeń nie będzie w stanie zapewnić odpowiedniej liczby klatek na sekundę.

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