Rileva, monitora e classifica gli oggetti con un modello di classificazione personalizzato su iOS

Puoi utilizzare ML Kit per rilevare e monitorare gli oggetti in fotogrammi video successivi.

Quando passi un'immagine a ML Kit, rileva fino a cinque oggetti nell'immagine insieme alla posizione di ciascun oggetto nell'immagine. Quando rileva oggetti nei flussi video, ogni oggetto ha un ID univoco che puoi utilizzare per monitorare l'oggetto da un frame all'altro.

Puoi utilizzare un modello di classificazione delle immagini personalizzato per classificare gli oggetti rilevati. Consulta Modelli personalizzati con ML Kit per indicazioni sui requisiti di compatibilità dei modelli, su dove trovare i modelli preaddestrati e su come addestrare i tuoi modelli.

Esistono due modi per integrare un modello personalizzato. Puoi raggruppare il modello inserendolo nella cartella degli asset dell'app oppure scaricarlo dinamicamente da Cloud Storage. La tabella seguente mette a confronto le due opzioni.

Modello in bundle Modello ospitato
Il modello fa parte del file .ipa della tua app, il che ne aumenta le dimensioni. Il modello non fa parte del file .ipa della tua app. Viene ospitato tramite il caricamento su Cloud Storage. Ti consigliamo di utilizzare Cloud Storage for Firebase.
Il modello è disponibile immediatamente, anche quando il dispositivo Android è offline La tua app deve includere il codice per scaricare il modello on demand
Non è necessario un progetto Firebase Richiede un progetto Firebase (se utilizzi Cloud Storage for Firebase).
Devi ripubblicare l'app per aggiornare il modello Eseguire il push degli aggiornamenti del modello senza ripubblicare l'app
Nessun test A/B integrato Test A/B con Firebase Remote Config

Prova

Prima di iniziare

  1. Includi le librerie ML Kit nel tuo Podfile:

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.0'
    
  2. Dopo aver installato o aggiornato i pod del progetto, apri il progetto Xcode utilizzando il file .xcworkspace. ML Kit è supportato in Xcode versione 13.2.1 o successive.

  3. Se vuoi scaricare un modello utilizzando Cloud Storage for Firebase, assicurati di aggiungere Firebase al tuo progetto iOS, se non l'hai già fatto. Questa operazione non è necessaria quando raggruppi il modello.

1. Carica il modello

Configurare un'origine modello locale

Per raggruppare il modello con l'app:

  1. Copia il file del modello (di solito termina con .tflite o .lite) nel progetto Xcode, avendo cura di selezionare Copy bundle resources. Il file del modello verrà incluso nell'app bundle e sarà disponibile per ML Kit.

  2. Crea l'oggetto LocalModel specificando il percorso del file del modello:

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

Configura un'origine modello ospitata in remoto

Per utilizzare il modello ospitato in remoto, devi scaricare il file del modello nello spazio di archiviazione locale del dispositivo utilizzando la logica della tua app, quindi caricarlo come modello locale. Ti consigliamo di utilizzare Cloud Storage for Firebase per ospitare un modello. Per i dettagli di implementazione, consulta la guida alla migrazione da Firebase ML a Cloud Storage.

2. Configura il rilevatore di oggetti

Dopo aver configurato le origini del modello, configura il rilevatore di oggetti per il tuo caso d'uso con un oggetto CustomObjectDetectorOptions. Puoi modificare le seguenti impostazioni:

Impostazioni del rilevamento di oggetti
Modalità di rilevamento STREAM_MODE (predefinito) | SINGLE_IMAGE_MODE

In STREAM_MODE (impostazione predefinita), il rilevatore di oggetti viene eseguito con bassa latenza, ma potrebbe produrre risultati incompleti (ad esempio riquadri di selezione o etichette di categoria non specificati) nelle prime invocazioni del rilevatore. Inoltre, in STREAM_MODE, il rilevatore assegna ID di monitoraggio agli oggetti, che puoi utilizzare per monitorare gli oggetti nei vari frame. Utilizza questa modalità quando vuoi monitorare oggetti o quando la bassa latenza è importante, ad esempio quando elabori flussi video in tempo reale.

In SINGLE_IMAGE_MODE, il rilevatore di oggetti restituisce il risultato dopo aver determinato il riquadro di delimitazione dell'oggetto. Se attivi anche la classificazione, il risultato viene restituito dopo che sono disponibili sia il riquadro di selezione sia l'etichetta della categoria. Di conseguenza, la latenza di rilevamento è potenzialmente più elevata. Inoltre, in SINGLE_IMAGE_MODE, non vengono assegnati ID monitoraggio. Utilizza questa modalità se la latenza non è fondamentale e non vuoi gestire risultati parziali.

Rilevare e monitorare più oggetti false (predefinito) | true

Se rilevare e monitorare fino a cinque oggetti o solo l'oggetto più prominente (impostazione predefinita).

Classificare gli oggetti false (predefinito) | true

Indica se classificare o meno gli oggetti rilevati utilizzando la categoria di classificazione personalizzata fornita. Per utilizzare il modello di classificazione personalizzato, devi impostare questo valore su true.

Soglia di confidenza della classificazione

Punteggio di confidenza minimo delle etichette rilevate. Se non impostato, viene utilizzata qualsiasi soglia del classificatore specificata dai metadati del modello. Se il modello non contiene metadati o se i metadati non specificano una soglia del classificatore, viene utilizzata una soglia predefinita di 0,0.

Numero massimo di etichette per oggetto

Numero massimo di etichette per oggetto che il rilevatore restituirà. Se non viene impostato, viene utilizzato il valore predefinito 10.

Se hai solo un modello in bundle locale, crea un rilevatore di oggetti dall'oggetto 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;

Se hai un modello ospitato in remoto, devi verificare che sia stato scaricato prima della sua esecuzione.

Anche se devi confermare questa operazione solo prima di eseguire il rilevatore di oggetti, se hai sia un modello ospitato in remoto sia un modello incluso localmente, potrebbe essere opportuno eseguire questo controllo durante l'istanziamento di ObjectDetector: crea un rilevatore dal modello remoto se è stato scaricato e dal modello locale in caso contrario.

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];

Se hai solo un modello ospitato in remoto, devi disattivare la funzionalità correlata al modello, ad esempio oscurare o nascondere parte della tua UI, finché non confermi che il modello è stato scaricato.

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

L'API per il rilevamento di oggetti e il monitoraggio è ottimizzata per questi due casi d'uso principali:

  • Rilevamento e monitoraggio in tempo reale dell'oggetto più in evidenza nel mirino della videocamera.
  • Il rilevamento di più oggetti da un'immagine statica.

Per configurare l'API per questi casi d'uso:

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. Prepara l'immagine di input

Crea un oggetto VisionImage utilizzando un UIImage o un CMSampleBuffer.

Se utilizzi un UIImage, segui questi passaggi:

  • Crea un oggetto VisionImage con UIImage. Assicurati di specificare il .orientation corretto.

    Swift

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

    Objective-C

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

Se utilizzi un CMSampleBuffer, segui questi passaggi:

  • Specifica l'orientamento dei dati immagine contenuti in CMSampleBuffer.

    Per ottenere l'orientamento dell'immagine:

    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;
      }
    }
          
  • Crea un oggetto VisionImage utilizzando l'oggetto CMSampleBuffer e l'orientamento:

    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. Crea ed esegui il rilevatore di oggetti

  1. Crea un nuovo rilevatore di oggetti:

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. Poi, utilizza il rilevatore:

    In modo asincrono:

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

    In modo sincrono:

    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. Ottenere informazioni sugli oggetti etichettati

Se la chiamata al processore di immagini va a buon fine, passa un elenco di Object al gestore del completamento o restituisce l'elenco, a seconda che tu abbia chiamato il metodo asincrono o sincrono.

Ogni Object contiene le seguenti proprietà:

frame Un CGRect che indica la posizione dell'oggetto nell'immagine.
trackingID Un numero intero che identifica l'oggetto nelle immagini o `nil` in SINGLE_IMAGE_MODE.
labels
label.text La descrizione testuale dell'etichetta. Restituito solo se i metadati del modello LiteRT contengono descrizioni delle etichette.
label.index L'indice dell'etichetta tra tutte le etichette supportate dal classificatore.
label.confidence Il valore di affidabilità della classificazione dell'oggetto.

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

Garantire un'esperienza utente ottimale

Per garantire la migliore esperienza possibile agli utenti, segui queste linee guida nella tua app:

  • Il rilevamento degli oggetti dipende dalla loro complessità visiva. Per essere rilevati, gli oggetti con un numero ridotto di caratteristiche visive potrebbero dover occupare una parte più ampia dell'immagine. Devi fornire agli utenti indicazioni su come acquisire input che funzionino bene con il tipo di oggetti che vuoi rilevare.
  • Quando utilizzi la classificazione, se vuoi rilevare oggetti che non rientrano chiaramente nelle categorie supportate, implementa una gestione speciale per gli oggetti sconosciuti.

Dai un'occhiata anche all'app demo di ML Kit Material Design e alla raccolta di pattern di Material Design per le funzionalità basate sul machine learning.

Ottimizzazione del rendimento

Se vuoi utilizzare il rilevamento degli oggetti in un'applicazione in tempo reale, segui queste linee guida per ottenere i migliori frame rate:

  • Quando utilizzi la modalità streaming in un'applicazione in tempo reale, non utilizzare il rilevamento di più oggetti, in quanto la maggior parte dei dispositivi non sarà in grado di produrre frame rate adeguati.

  • Per l'elaborazione dei frame video, utilizza l'results(in:)API sincrona del rilevatore. Chiama questo metodo dalla funzione AVCaptureVideoDataOutputSampleBufferDelegate di captureOutput(_, didOutput:from:) per ottenere in modo sincrono i risultati dal frame video specificato. Mantieni AVCaptureVideoDataOutput's alwaysDiscardsLateVideoFrames come true per limitare le chiamate al rilevatore. Se un nuovo frame video diventa disponibile durante l'esecuzione del rilevatore, verrà eliminato.
  • Se utilizzi l'output del rilevatore per sovrapporre elementi grafici all'immagine di input, recupera prima il risultato da ML Kit, quindi esegui il rendering dell'immagine e della sovrapposizione in un unico passaggio. In questo modo, esegui il rendering sulla superficie di visualizzazione una sola volta per ogni frame di input elaborato. Per un esempio, consulta updatePreviewOverlayViewWithLastFrame nell'esempio di avvio rapido di ML Kit.