Détecter, suivre et classer des objets avec un modèle de classification personnalisé sur iOS

Vous pouvez utiliser ML Kit pour détecter et suivre des objets dans des images vidéo successives.

Lorsque vous transmettez une image à ML Kit, il détecte jusqu'à cinq objets dans l'image, ainsi que la position de chaque objet dans l'image. Lors de la détection d'objets dans des flux vidéo, chaque objet possède un ID unique que vous pouvez utiliser pour suivre l'objet d'une image à l'autre.

Vous pouvez utiliser un modèle de classification d'images personnalisé pour classer les objets détectés. Consultez la section Modèles personnalisés avec ML Kit pour obtenir des conseils sur les exigences de compatibilité des modèles, l'emplacement des modèles pré-entraînés et la façon d'entraîner vos propres modèles.

Il existe deux façons d'intégrer un modèle personnalisé. Vous pouvez regrouper le modèle en le plaçant dans le dossier des éléments de votre application ou le télécharger de manière dynamique depuis Cloud Storage. Le tableau suivant compare les deux options.

Modèle regroupé Modèle hébergé
Le modèle fait partie du fichier .ipa de votre application, ce qui augmente sa taille. Le modèle ne fait pas partie du fichier .ipa de votre application. Il est hébergé en étant importé dans Cloud Storage. Nous vous recommandons d'utiliser Cloud Storage for Firebase.
Le modèle est disponible immédiatement, même lorsque l'appareil Android est hors connexion. Votre application doit inclure du code pour télécharger le modèle à la demande.
Aucun projet Firebase n'est nécessaire. Nécessite un projet Firebase (si vous utilisez Cloud Storage for Firebase).
Vous devez republier votre application pour mettre à jour le modèle. Envoyez les mises à jour du modèle sans republier votre application.
Aucun test A/B intégré Tests A/B avec Firebase Remote Config

Essayer

Avant de commencer

  1. Incluez les bibliothèques ML Kit dans votre Podfile :

    pod 'GoogleMLKit/ObjectDetectionCustom', '8.0.0'
    
  2. Une fois les pods de votre projet installés ou mis à jour, ouvrez votre projet Xcode à l'aide de son fichier .xcworkspace. ML Kit est compatible avec Xcode version 13.2.1 ou ultérieure.

  3. Si vous souhaitez télécharger un modèle à l'aide de Cloud Storage for Firebase, assurez vous d' ajouter Firebase à votre projet iOS, si ce n'est pas déjà fait. Cela n'est pas nécessaire lorsque vous regroupez le modèle.

1. Charger le modèle

Configurer une source de modèle local

Pour regrouper le modèle avec votre application :

  1. Copiez le fichier de modèle (se terminant généralement par .tflite ou .lite) dans votre projet Xcode, en veillant à sélectionner Copy bundle resources (Copier les ressources du bundle) lorsque vous le faites. Le fichier de modèle sera inclus dans l'app bundle et disponible pour ML Kit.

  2. Créez un objet LocalModel en spécifiant le chemin d'accès au fichier de modèle :

    Swift

    let localModel = LocalModel(path: localModelFilePath)

    Objective-C

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

Configurer une source de modèle hébergé à distance

Pour utiliser le modèle hébergé à distance, vous devez télécharger le fichier de modèle dans le stockage local de l'appareil à l'aide de votre propre logique d'application, puis le charger en tant que modèle local. Nous vous recommandons d'utiliser Cloud Storage for Firebase pour héberger un modèle. Pour en savoir plus sur l' implémentation, consultez le guide de migration de Firebase ML vers Cloud Storage.

2. Configurer le détecteur d'objets

Une fois que vous avez configuré vos sources de modèle, configurez le détecteur d'objets pour votre cas d'utilisation avec un objet CustomObjectDetectorOptions. Vous pouvez modifier les paramètres suivants :

Paramètres du détecteur d'objets
Mode de détection STREAM_MODE (par défaut) | SINGLE_IMAGE_MODE

En STREAM_MODE (par défaut), le détecteur d'objets s'exécute avec une faible latence, mais peut produire des résultats incomplets (tels que des cadres de délimitation ou des libellés de catégorie non spécifiés) lors des premières invocations du détecteur. De plus, en STREAM_MODE, le détecteur attribue des ID de suivi aux objets, que vous pouvez utiliser pour suivre les objets d'une image à l'autre. Utilisez ce mode lorsque vous souhaitez suivre des objets ou lorsque la faible latence est importante, par exemple lors du traitement de flux vidéo en temps réel.

En SINGLE_IMAGE_MODE, le détecteur d'objets renvoie le résultat une fois le cadre de délimitation de l'objet déterminé. Si vous activez également la classification, il renvoie le résultat une fois que le cadre de délimitation et le libellé de catégorie sont disponibles. Par conséquent, la latence de détection est potentiellement plus élevée. De plus, en SINGLE_IMAGE_MODE, aucun ID de suivi n'est attribué. Utilisez ce mode si la latence n'est pas critique et que vous ne souhaitez pas traiter de résultats partiels.

Détecter et suivre plusieurs objets false (par défaut) | true

Indique si vous souhaitez détecter et suivre jusqu'à cinq objets ou uniquement l'objet le plus visible (par défaut).

Classer des objets false (par défaut) | true

Indique si vous souhaitez classer ou non les objets détectés à l'aide du modèle de classificateur personnalisé fourni. Pour utiliser votre modèle de classification personnalisé, vous devez définir cette valeur sur true.

Seuil de confiance de la classification

Score de confiance minimal des libellés détectés. Si ce paramètre n'est pas défini, tout seuil de classificateur spécifié par les métadonnées du modèle sera utilisé. Si le modèle ne contient aucune métadonnée ou si les métadonnées ne spécifient pas de seuil de classificateur, un seuil par défaut de 0,0 sera utilisé.

Nombre maximal de libellés par objet

Nombre maximal de libellés par objet que le détecteur renverra. Si ce paramètre n'est pas défini, la valeur par défaut de 10 sera utilisée.

Si vous ne disposez que d'un modèle regroupé localement, créez simplement un détecteur d'objets à partir de votre objet 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;

Si vous disposez d'un modèle hébergé à distance, vous devrez vérifier qu'il a été téléchargé avant de l'exécuter.

Bien que vous n'ayez à confirmer cela qu'avant d'exécuter le détecteur d'objets, si vous disposez à la fois d'un modèle hébergé à distance et d'un modèle regroupé localement, il peut être judicieux d'effectuer cette vérification lors de l'instanciation de l'ObjectDetector : créez un détecteur à partir du modèle distant s'il a été téléchargé, et à partir du modèle local dans le cas contraire.

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

Si vous ne disposez que d'un modèle hébergé à distance, vous devez désactiver les fonctionnalités liées au modèle (par exemple, griser ou masquer une partie de votre interface utilisateur) jusqu'à ce que vous confirmiez que le modèle a été téléchargé.

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 de détection et de suivi d'objets est optimisée pour ces deux cas d'utilisation principaux :

  • Détection et suivi en direct de l'objet le plus visible dans le viseur de l'appareil photo.
  • Détection de plusieurs objets à partir d'une image statique.

Pour configurer l'API pour ces cas d'utilisation :

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. Préparer l'image d'entrée

Créez un VisionImage objet à l'aide d'un UIImage ou d'un CMSampleBuffer.

Si vous utilisez un UIImage, procédez comme suit :

  • Créez un VisionImage objet avec le UIImage. Veillez à spécifier le .orientation correct.

    Swift

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

    Objective-C

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

Si vous utilisez un CMSampleBuffer, procédez comme suit :

  • Spécifiez l'orientation des données d'image contenues dans le CMSampleBuffer.

    Pour obtenir l'orientation de l'image :

    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;
      }
    }
          
  • Créez un objet VisionImage à l'aide de l' CMSampleBuffer objet et de l'orientation :

    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. Créer et exécuter le détecteur d'objets

  1. Créez un détecteur d'objets :

    Swift

    let objectDetector = ObjectDetector.objectDetector(options: options)

    Objective-C

    MLKObjectDetector *objectDetector = [MLKObjectDetector objectDetectorWithOptions:options];
  2. Ensuite, utilisez le détecteur :

    De manière asynchrone :

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

    De manière synchrone :

    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. Obtenir des informations sur les objets étiquetés

Si l'appel au processeur d'images réussit, il transmet une liste d'Object au gestionnaire de saisie semi-automatique ou renvoie la liste, selon que vous avez appelé la méthode asynchrone ou synchrone.

Chaque Object contient les propriétés suivantes :

frame Un CGRect indiquant la position de l'objet dans l' image.
trackingID Un entier qui identifie l'objet dans les images, ou `nil` en SINGLE_IMAGE_MODE.
labels
label.text Description textuelle du libellé. N'est renvoyé que si les métadonnées du modèle LiteRT contiennent des descriptions de libellés.
label.index Index du libellé parmi tous les libellés compatibles avec le classificateur.
label.confidence Niveau de confiance de la classification de l'objet.

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

Garantir une expérience utilisateur optimale

Pour une expérience utilisateur optimale, suivez ces consignes dans votre application :

  • La réussite de la détection d'objets dépend de la complexité visuelle de l'objet. Pour être détectés, les objets comportant peu de caractéristiques visuelles peuvent avoir besoin d'occuper une plus grande partie de l'image. Vous devez fournir aux utilisateurs des conseils sur la capture d'entrées qui fonctionnent bien avec le type d'objets que vous souhaitez détecter.
  • Lorsque vous utilisez la classification, si vous souhaitez détecter des objets qui ne correspondent pas clairement aux catégories compatibles, implémentez une gestion spéciale pour les objets inconnus.

Consultez également l'application de démonstration ML Kit Material Design et la collection de modèles Material Design pour les fonctionnalités basées sur le machine learning.

Amélioration des performances

Si vous souhaitez utiliser la détection d'objets dans une application en temps réel, suivez ces consignes pour obtenir les meilleures fréquences d'images :

  • Lorsque vous utilisez le mode streaming dans une application en temps réel, n'utilisez pas la détection de plusieurs objets, car la plupart des appareils ne pourront pas produire des fréquences d'images adéquates.

  • Pour traiter les images vidéo, utilisez l'API synchrone results(in:) du détecteur. Appelez cette méthode à partir de la AVCaptureVideoDataOutputSampleBufferDelegate captureOutput(_, didOutput:from:) fonction pour obtenir de manière synchrone les résultats de l'image vidéo donnée. Conservez AVCaptureVideoDataOutput de alwaysDiscardsLateVideoFrames comme true pour limiter les appels au détecteur. Si une nouvelle image vidéo devient disponible pendant l'exécution du détecteur, elle sera supprimée.
  • Si vous utilisez la sortie du détecteur pour superposer des graphiques sur l'image d'entrée, obtenez d'abord le résultat de ML Kit, puis effectuez le rendu de l'image et de la superposition en une seule étape. Ce faisant, vous n'effectuez le rendu sur la surface d'affichage qu'une seule fois pour chaque image d'entrée traitée. Pour obtenir un exemple, consultez updatePreviewOverlayViewWithLastFrame dans l'exemple de démarrage rapide ML Kit.