Détectez et suivez les objets avec ML Kit sur Android

Vous pouvez utiliser ML Kit pour détecter et suivre des objets à travers les images de la vidéo.

Lorsque vous passez des images ML Kit, ML Kit renvoie, pour chaque image, une liste de jusqu'à cinq objets détectés et leur position dans l'image. Lors de la détection d'objets dans des flux vidéo, chaque objet a un ID que vous pouvez utiliser pour suivre l'objet sur les images. Vous pouvez également éventuellement activer la classification grossière des objets, qui étiquette les objets avec des descriptions de catégories larges.

Avant que tu commences

  1. Si vous avez pas déjà, ajoutez Firebase à votre projet Android .
  2. Ajoutez les dépendances pour les bibliothèques ML Kit Android à votre module (app-niveau) de fichier Gradle (généralement app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
      implementation 'com.google.firebase:firebase-ml-vision-object-detection-model:19.0.6'
    }
    

1. Configurer le détecteur d'objets

Pour commencer la détection et le suivi d' objets, d' abord créer une instance de FirebaseVisionObjectDetector , en précisant le cas échéant les réglages du détecteur que vous souhaitez modifier la valeur par défaut.

  1. Configurer le détecteur d'objet pour votre cas d'utilisation avec un FirebaseVisionObjectDetectorOptions objet. 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'objet fonctionne avec une faible latence, mais peut produire des résultats incomplets (tels que les boîtes englobantes non spécifiées ou des étiquettes de catégorie) sur les premières invocations du détecteur. En outre, dans STREAM_MODE , les cessionnaires de détecteur ID de suivi des objets, que vous pouvez utiliser pour suivre des objets à travers des cadres. Utilisez ce mode lorsque vous souhaitez suivre des objets ou lorsqu'une faible latence est importante, comme lors du traitement de flux vidéo en temps réel.

    Dans SINGLE_IMAGE_MODE , le temps d' attente du détecteur d'objets jusqu'à ce que le cadre de sélection d'un objet détecté et (si vous avez activé la classification) étiquette de catégorie sont disponibles avant de retourner un résultat. En conséquence, la latence de détection est potentiellement plus élevée. En outre, dans SINGLE_IMAGE_MODE , ID de suivi ne sont pas affectés. Utilisez ce mode si la latence n'est pas critique et que vous ne souhaitez pas gérer des résultats partiels.

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

    Que ce soit pour détecter et suivre jusqu'à cinq objets ou uniquement l'objet le plus important (par défaut).

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

    Classer ou non les objets détectés dans des catégories grossières. Lorsqu'il est activé, le détecteur d'objets classe les objets dans les catégories suivantes : articles de mode, nourriture, articles pour la maison, lieux, plantes et inconnu.

    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 important dans le viseur de la caméra
    • Détection de plusieurs objets à partir d'une image statique

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

    Java

    // Live detection and tracking
    FirebaseVisionObjectDetectorOptions options =
            new FirebaseVisionObjectDetectorOptions.Builder()
                    .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE)
                    .enableClassification()  // Optional
                    .build();
    
    // Multiple object detection in static images
    FirebaseVisionObjectDetectorOptions options =
            new FirebaseVisionObjectDetectorOptions.Builder()
                    .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE)
                    .enableMultipleObjects()
                    .enableClassification()  // Optional
                    .build();
    

    Kotlin+KTX

    // Live detection and tracking
    val options = FirebaseVisionObjectDetectorOptions.Builder()
            .setDetectorMode(FirebaseVisionObjectDetectorOptions.STREAM_MODE)
            .enableClassification()  // Optional
            .build()
    
    // Multiple object detection in static images
    val options = FirebaseVisionObjectDetectorOptions.Builder()
            .setDetectorMode(FirebaseVisionObjectDetectorOptions.SINGLE_IMAGE_MODE)
            .enableMultipleObjects()
            .enableClassification()  // Optional
            .build()
    
  2. Obtenez une instance de FirebaseVisionObjectDetector :

    Java

    FirebaseVisionObjectDetector objectDetector =
            FirebaseVision.getInstance().getOnDeviceObjectDetector();
    
    // Or, to change the default settings:
    FirebaseVisionObjectDetector objectDetector =
            FirebaseVision.getInstance().getOnDeviceObjectDetector(options);
    

    Kotlin+KTX

    val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector()
    
    // Or, to change the default settings:
    val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options)
    

2. Exécutez le détecteur d'objets

Pour détecter et suivre des objets, passer des images à l' FirebaseVisionObjectDetector de l' instance processImage() méthode.

Pour chaque trame de vidéo ou d'image d'une séquence, procédez comme suit :

  1. Créer un FirebaseVisionImage objet à partir de votre image.

    • Pour créer un FirebaseVisionImage objet à partir d' un media.Image objet, par exemple lors de la capture d' une image de la caméra d'un dispositif, passe media.Image objet et la rotation de l'image pour FirebaseVisionImage.fromMediaImage() .

      Si vous utilisez la CameraX bibliothèque, les OnImageCapturedListener et ImageAnalysis.Analyzer cours calculent la valeur de rotation pour vous, il vous suffit de convertir la rotation à l' un des ML Kit ROTATION_ constantes avant d' appeler FirebaseVisionImage.fromMediaImage() :

      Java

      private class YourAnalyzer implements ImageAnalysis.Analyzer {
      
          private int degreesToFirebaseRotation(int degrees) {
              switch (degrees) {
                  case 0:
                      return FirebaseVisionImageMetadata.ROTATION_0;
                  case 90:
                      return FirebaseVisionImageMetadata.ROTATION_90;
                  case 180:
                      return FirebaseVisionImageMetadata.ROTATION_180;
                  case 270:
                      return FirebaseVisionImageMetadata.ROTATION_270;
                  default:
                      throw new IllegalArgumentException(
                              "Rotation must be 0, 90, 180, or 270.");
              }
          }
      
          @Override
          public void analyze(ImageProxy imageProxy, int degrees) {
              if (imageProxy == null || imageProxy.getImage() == null) {
                  return;
              }
              Image mediaImage = imageProxy.getImage();
              int rotation = degreesToFirebaseRotation(degrees);
              FirebaseVisionImage image =
                      FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
              // Pass image to an ML Kit Vision API
              // ...
          }
      }
      

      Kotlin+KTX

      private class YourImageAnalyzer : ImageAnalysis.Analyzer {
          private fun degreesToFirebaseRotation(degrees: Int): Int = when(degrees) {
              0 -> FirebaseVisionImageMetadata.ROTATION_0
              90 -> FirebaseVisionImageMetadata.ROTATION_90
              180 -> FirebaseVisionImageMetadata.ROTATION_180
              270 -> FirebaseVisionImageMetadata.ROTATION_270
              else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
          }
      
          override fun analyze(imageProxy: ImageProxy?, degrees: Int) {
              val mediaImage = imageProxy?.image
              val imageRotation = degreesToFirebaseRotation(degrees)
              if (mediaImage != null) {
                  val image = FirebaseVisionImage.fromMediaImage(mediaImage, imageRotation)
                  // Pass image to an ML Kit Vision API
                  // ...
              }
          }
      }
      

      Si vous n'utilisez pas de bibliothèque de caméras qui vous donne la rotation de l'image, vous pouvez la calculer à partir de la rotation de l'appareil et de l'orientation du capteur de la caméra dans l'appareil :

      Java

      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
      
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      private int getRotationCompensation(String cameraId, Activity activity, Context context)
              throws CameraAccessException {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
          int rotationCompensation = ORIENTATIONS.get(deviceRotation);
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
          int sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION);
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360;
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          int result;
          switch (rotationCompensation) {
              case 0:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  break;
              case 90:
                  result = FirebaseVisionImageMetadata.ROTATION_90;
                  break;
              case 180:
                  result = FirebaseVisionImageMetadata.ROTATION_180;
                  break;
              case 270:
                  result = FirebaseVisionImageMetadata.ROTATION_270;
                  break;
              default:
                  result = FirebaseVisionImageMetadata.ROTATION_0;
                  Log.e(TAG, "Bad rotation value: " + rotationCompensation);
          }
          return result;
      }

      Kotlin+KTX

      private val ORIENTATIONS = SparseIntArray()
      
      init {
          ORIENTATIONS.append(Surface.ROTATION_0, 90)
          ORIENTATIONS.append(Surface.ROTATION_90, 0)
          ORIENTATIONS.append(Surface.ROTATION_180, 270)
          ORIENTATIONS.append(Surface.ROTATION_270, 180)
      }
      /**
       * Get the angle by which an image must be rotated given the device's current
       * orientation.
       */
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      @Throws(CameraAccessException::class)
      private fun getRotationCompensation(cameraId: String, activity: Activity, context: Context): Int {
          // Get the device's current rotation relative to its "native" orientation.
          // Then, from the ORIENTATIONS table, look up the angle the image must be
          // rotated to compensate for the device's rotation.
          val deviceRotation = activity.windowManager.defaultDisplay.rotation
          var rotationCompensation = ORIENTATIONS.get(deviceRotation)
      
          // On most devices, the sensor orientation is 90 degrees, but for some
          // devices it is 270 degrees. For devices with a sensor orientation of
          // 270, rotate the image an additional 180 ((270 + 270) % 360) degrees.
          val cameraManager = context.getSystemService(CAMERA_SERVICE) as CameraManager
          val sensorOrientation = cameraManager
                  .getCameraCharacteristics(cameraId)
                  .get(CameraCharacteristics.SENSOR_ORIENTATION)!!
          rotationCompensation = (rotationCompensation + sensorOrientation + 270) % 360
      
          // Return the corresponding FirebaseVisionImageMetadata rotation value.
          val result: Int
          when (rotationCompensation) {
              0 -> result = FirebaseVisionImageMetadata.ROTATION_0
              90 -> result = FirebaseVisionImageMetadata.ROTATION_90
              180 -> result = FirebaseVisionImageMetadata.ROTATION_180
              270 -> result = FirebaseVisionImageMetadata.ROTATION_270
              else -> {
                  result = FirebaseVisionImageMetadata.ROTATION_0
                  Log.e(TAG, "Bad rotation value: $rotationCompensation")
              }
          }
          return result
      }

      Ensuite, passer le media.Image objet et la valeur de rotation de FirebaseVisionImage.fromMediaImage() :

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
    • Pour créer un FirebaseVisionImage objet à partir d' un fichier URI, passez le contexte de l' application et de l' URI fichier à FirebaseVisionImage.fromFilePath() . Ceci est utile lorsque vous utilisez une ACTION_GET_CONTENT intention de demander à l'utilisateur de sélectionner une image à partir de leur application Galerie.

      Java

      FirebaseVisionImage image;
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri);
      } catch (IOException e) {
          e.printStackTrace();
      }

      Kotlin+KTX

      val image: FirebaseVisionImage
      try {
          image = FirebaseVisionImage.fromFilePath(context, uri)
      } catch (e: IOException) {
          e.printStackTrace()
      }
    • Pour créer un FirebaseVisionImage objet à partir d' un ByteBuffer ou un tableau d'octets, tout d' abord calculer la rotation de l' image , comme décrit ci - dessus pour media.Image entrée.

      Ensuite, créez un FirebaseVisionImageMetadata objet qui contient la hauteur de l'image, la largeur, le format de codage couleur, et la rotation:

      Java

      FirebaseVisionImageMetadata metadata = new FirebaseVisionImageMetadata.Builder()
              .setWidth(480)   // 480x360 is typically sufficient for
              .setHeight(360)  // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build();

      Kotlin+KTX

      val metadata = FirebaseVisionImageMetadata.Builder()
              .setWidth(480) // 480x360 is typically sufficient for
              .setHeight(360) // image recognition
              .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
              .setRotation(rotation)
              .build()

      Utiliser le tampon ou d'un tableau, et l'objet de méta - données, pour créer un FirebaseVisionImage objet:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata);
      // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata)
      // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
    • Pour créer un FirebaseVisionImage objet d'un Bitmap objet:

      Java

      FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);

      Kotlin+KTX

      val image = FirebaseVisionImage.fromBitmap(bitmap)
      L'image représentée par le Bitmap objet doit être en position verticale, sans rotation supplémentaire nécessaire.
  2. Transmettre l'image à la processImage() méthode:

    Java

    objectDetector.processImage(image)
            .addOnSuccessListener(
                    new OnSuccessListener<List<FirebaseVisionObject>>() {
                        @Override
                        public void onSuccess(List<FirebaseVisionObject> detectedObjects) {
                            // Task completed successfully
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            // Task failed with an exception
                            // ...
                        }
                    });
    

    Kotlin+KTX

    objectDetector.processImage(image)
            .addOnSuccessListener { detectedObjects ->
                // Task completed successfully
                // ...
            }
            .addOnFailureListener { e ->
                // Task failed with an exception
                // ...
            }
    
  3. Si l'appel à processImage() réussit, une liste de FirebaseVisionObject s est passé à l'auditeur de succès.

    Chaque FirebaseVisionObject contient les propriétés suivantes:

    Boîte englobante Un Rect indiquant la position de l'objet dans l'image.
    Identifiant de suivi Un entier qui identifie l'objet à travers les images. Null dans SINGLE_IMAGE_MODE.
    Catégorie La catégorie grossière de l'objet. Si le détecteur d'objet n'a pas le classement activé, ce qui est toujours FirebaseVisionObject.CATEGORY_UNKNOWN .
    Confiance La valeur de confiance de la classification de l'objet. Si le détecteur d'objet n'a pas activé le classement, ou l'objet est classé comme inconnu, c'est null .

    Java

    // The list of detected objects contains one item if multiple object detection wasn't enabled.
    for (FirebaseVisionObject obj : detectedObjects) {
        Integer id = obj.getTrackingId();
        Rect bounds = obj.getBoundingBox();
    
        // If classification was enabled:
        int category = obj.getClassificationCategory();
        Float confidence = obj.getClassificationConfidence();
    }
    

    Kotlin+KTX

    // The list of detected objects contains one item if multiple object detection wasn't enabled.
    for (obj in detectedObjects) {
        val id = obj.trackingId       // A number that identifies the object across images
        val bounds = obj.boundingBox  // The object's position in the image
    
        // If classification was enabled:
        val category = obj.classificationCategory
        val confidence = obj.classificationConfidence
    }
    

Amélioration de la convivialité et des performances

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

  • Une détection d'objet réussie dépend de la complexité visuelle de l'objet. Les objets avec un petit nombre de caractéristiques visuelles peuvent avoir besoin d'occuper une plus grande partie de l'image pour être détectés. 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.
  • Lors de l'utilisation de la classification, si vous souhaitez détecter des objets qui n'entrent pas clairement dans les catégories prises en charge, implémentez une gestion spéciale pour les objets inconnus.

En outre, consultez le [ML Kit Matériel VITRINE application] [vitrine-link] {: .external} et le design matériel modèles pour les fonctions alimenté par l' apprentissage automatique collection.

Lorsque vous utilisez le mode streaming dans une application en temps réel, suivez ces instructions pour obtenir les meilleures fréquences d'images :

  • N'utilisez pas la détection d'objets multiples en mode streaming, car la plupart des appareils ne seront pas en mesure de produire des fréquences d'images adéquates.

  • Désactivez la classification si vous n'en avez pas besoin.

  • L'accélérateur appelle le détecteur. Si une nouvelle image vidéo devient disponible pendant que le détecteur fonctionne, supprimez l'image.
  • Si vous utilisez la sortie du détecteur pour superposer des graphiques sur l'image d'entrée, obtenez d'abord le résultat du kit ML, puis restituez l'image et superposez en une seule étape. Ce faisant, vous effectuez un rendu sur la surface d'affichage une seule fois pour chaque image d'entrée.
  • Si vous utilisez l'API Camera2, de capturer des images dans ImageFormat.YUV_420_888 format.

    Si vous utilisez l'API de l' appareil plus ancien, de capturer des images dans ImageFormat.NV21 format.