Détecter et suivre des objets avec ML Kit sur Android

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, celui-ci détecte jusqu'à cinq objets dans l'image ainsi que la position de chaque objet dans l'image. Lorsque vous détectez des objets dans flux vidéo, chaque objet possède un identifiant unique que vous pouvez utiliser pour suivre d'une image à l'autre. Vous pouvez aussi activer la connectivité de classification, qui attribue aux objets des descriptions de catégories générales.

Essayer

Avant de commencer

  1. Dans le fichier build.gradle au niveau du projet, veillez à inclure dépôt Maven de Google dans vos fichiers buildscript et allprojects.
  2. Ajoutez les dépendances des bibliothèques Android ML Kit au fichier fichier Gradle au niveau de l'application, généralement app/build.gradle:
    dependencies {
      // ...
    
      implementation 'com.google.mlkit:object-detection:17.0.1'
    
    }
    

1. Configurer le détecteur d'objets

Pour détecter et suivre des objets, créez d'abord une instance de ObjectDetector, puis et éventuellement spécifier les paramètres de détecteur que vous souhaitez modifier par défaut.

  1. Configurez le détecteur d'objets pour votre cas d'utilisation à l'aide d'une ObjectDetectorOptions. Vous pouvez modifier les paramètres suivants : paramètres:

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

    Dans STREAM_MODE (par défaut), le détecteur d'objets s'exécute avec une faible latence, mais peut produire des résultats incomplets (comme cadres de délimitation ou étiquettes de catégorie non spécifiés) sur les premiers les appels du détecteur. Dans STREAM_MODE, le détecteur attribue des identifiants de suivi aux objets, que vous pouvez utiliser pour suivre des objets sur des frames. Utilisez ce mode pour suivre ou lorsqu'une faible latence est importante, par exemple des flux vidéo en temps réel.

    Dans SINGLE_IMAGE_MODE, le détecteur d'objets renvoie le résultat après la détermination du cadre de délimitation de l'objet. Si vous activer également la classification. Le résultat est renvoyé après la délimitation case et libellé de catégorie sont tous deux disponibles. Par conséquent, la latence de détection est potentiellement plus élevée. Dans SINGLE_IMAGE_MODE, aucun ID de suivi n'est attribué. Utilisez ce mode si la latence n'est pas critique et que vous ne voulez pas des résultats partiels.

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

    Permet de détecter et de suivre jusqu'à cinq objets, ou seulement les plus objet proéminent (par défaut).

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

    Indique s'il faut classer ou non les objets détectés en catégories approximatives. Lorsqu'il est activé, le détecteur d'objets classe les objets dans les catégories suivantes: articles de mode, alimentation, articles pour la maison, des lieux et des plantes.

    L'API de détection et de suivi des objets est optimisée pour ces deux utilisations principales cas:

    • Détection et suivi en direct de l'objet le plus proéminent de la caméra viseur.
    • La détection de plusieurs objets à partir d'une image statique.

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

    Kotlin

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

    Java

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

    Kotlin

    val objectDetector = ObjectDetection.getClient(options)

    Java

    ObjectDetector objectDetector = ObjectDetection.getClient(options);

2. Préparer l'image d'entrée

Pour détecter et suivre des objets, transmettez des images à ObjectDetector la méthode process() de l'instance.

Le détecteur d'objets s'exécute directement à partir d'un Bitmap, d'un ByteBuffer NV21 ou d'un YUV_420_888 media.Image Créer un InputImage à partir de ces sources si vous disposez d'un accès direct à l'un d'entre eux. Si vous construisez un InputImage provenant d'autres sources, nous gérerons la conversion en interne pour vous et peut s'avérer moins efficace.

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

Vous pouvez créer un InputImage de différentes sources. Chacune d'elles est expliquée ci-dessous.

Utiliser un media.Image

Pour créer un InputImage à partir d'un objet media.Image, par exemple lorsque vous capturez une image à partir d'un l'appareil photo de l'appareil, transmettez l'objet media.Image et l'image la rotation sur InputImage.fromMediaImage().

Si vous utilisez les la bibliothèque CameraX, les OnImageCapturedListener et Les classes ImageAnalysis.Analyzer calculent la valeur de rotation pour vous.

Kotlin

private class YourImageAnalyzer : ImageAnalysis.Analyzer {

    override fun analyze(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
            // Pass image to an ML Kit Vision API
            // ...
        }
    }
}

Java

private class YourAnalyzer implements ImageAnalysis.Analyzer {

    @Override
    public void analyze(ImageProxy imageProxy) {
        Image mediaImage = imageProxy.getImage();
        if (mediaImage != null) {
          InputImage image =
                InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees());
          // Pass image to an ML Kit Vision API
          // ...
        }
    }
}

Si vous n'utilisez pas de bibliothèque d'appareils photo qui indique le degré de rotation de l'image, le calcul à partir du degré de rotation de l'appareil et de l'orientation de la caméra capteur de l'appareil:

Kotlin

private val ORIENTATIONS = SparseIntArray()

init {
    ORIENTATIONS.append(Surface.ROTATION_0, 0)
    ORIENTATIONS.append(Surface.ROTATION_90, 90)
    ORIENTATIONS.append(Surface.ROTATION_180, 180)
    ORIENTATIONS.append(Surface.ROTATION_270, 270)
}

/**
 * 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, isFrontFacing: Boolean): 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)

    // Get the device's sensor orientation.
    val cameraManager = activity.getSystemService(CAMERA_SERVICE) as CameraManager
    val sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360
    }
    return rotationCompensation
}

Java

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
    ORIENTATIONS.append(Surface.ROTATION_0, 0);
    ORIENTATIONS.append(Surface.ROTATION_90, 90);
    ORIENTATIONS.append(Surface.ROTATION_180, 180);
    ORIENTATIONS.append(Surface.ROTATION_270, 270);
}

/**
 * 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, boolean isFrontFacing)
        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);

    // Get the device's sensor orientation.
    CameraManager cameraManager = (CameraManager) activity.getSystemService(CAMERA_SERVICE);
    int sensorOrientation = cameraManager
            .getCameraCharacteristics(cameraId)
            .get(CameraCharacteristics.SENSOR_ORIENTATION);

    if (isFrontFacing) {
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
    } else { // back-facing
        rotationCompensation = (sensorOrientation - rotationCompensation + 360) % 360;
    }
    return rotationCompensation;
}

Ensuite, transmettez l'objet media.Image et valeur du degré de rotation sur InputImage.fromMediaImage():

Kotlin

val image = InputImage.fromMediaImage(mediaImage, rotation)

Java

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

Utiliser un URI de fichier

Pour créer un InputImage à partir d'un URI de fichier, transmettez le contexte de l'application et l'URI du fichier à InputImage.fromFilePath() Cela est utile lorsque vous Utiliser un intent ACTION_GET_CONTENT pour inviter l'utilisateur à sélectionner une image de son application Galerie.

Kotlin

val image: InputImage
try {
    image = InputImage.fromFilePath(context, uri)
} catch (e: IOException) {
    e.printStackTrace()
}

Java

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

Utiliser un ByteBuffer ou un ByteArray

Pour créer un InputImage d'un objet ByteBuffer ou ByteArray, calculez d'abord l'image degré de rotation décrit précédemment pour l'entrée media.Image. Ensuite, créez l'objet InputImage avec le tampon ou le tableau, ainsi que l'objet image la hauteur, la largeur, le format d'encodage des couleurs et le degré de rotation:

Kotlin

val image = InputImage.fromByteBuffer(
        byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)
// Or:
val image = InputImage.fromByteArray(
        byteArray,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
)

Java

InputImage image = InputImage.fromByteBuffer(byteBuffer,
        /* image width */ 480,
        /* image height */ 360,
        rotationDegrees,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);
// Or:
InputImage image = InputImage.fromByteArray(
        byteArray,
        /* image width */480,
        /* image height */360,
        rotation,
        InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
);

Utiliser un Bitmap

Pour créer un InputImage à partir d'un objet Bitmap, effectuez la déclaration suivante:

Kotlin

val image = InputImage.fromBitmap(bitmap, 0)

Java

InputImage image = InputImage.fromBitmap(bitmap, rotationDegree);

L'image est représentée par un objet Bitmap associé à des degrés de rotation.

3. Traiter l'image

Transmettez l'image à la méthode process():

Kotlin

objectDetector.process(image)
    .addOnSuccessListener { detectedObjects ->
        // Task completed successfully
        // ...
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

Java

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

4. Obtenir des informations sur les objets détectés

Si l'appel à process() aboutit, une liste de DetectedObject est transmise à l'écouteur de réussite.

Chaque DetectedObject contient les propriétés suivantes:

Cadre de délimitation Un Rect qui indique la position de l'objet dans l'image.
ID de suivi Entier qui identifie l'objet dans les images. Null dans SINGLE_IMAGE_MODE
Libellés
Description de libellé Texte descriptif du libellé. Il s'agit de l'une des chaînes définies dans PredefinedCategory.
Index des libellés Index de l'étiquette parmi toutes les étiquettes prises en charge par classificateur. Il s'agit de l'une des constantes entières définies dans PredefinedCategory.
Confiance de l'étiquette Valeur de confiance de la classification d'objets.

Kotlin

for (detectedObject in detectedObjects) {
    val boundingBox = detectedObject.boundingBox
    val trackingId = detectedObject.trackingId
    for (label in detectedObject.labels) {
        val text = label.text
        if (PredefinedCategory.FOOD == text) {
            ...
        }
        val index = label.index
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        val confidence = label.confidence
    }
}

Java

// The list of detected objects contains one item if multiple
// object detection wasn't enabled.
for (DetectedObject detectedObject : detectedObjects) {
    Rect boundingBox = detectedObject.getBoundingBox();
    Integer trackingId = detectedObject.getTrackingId();
    for (Label label : detectedObject.getLabels()) {
        String text = label.getText();
        if (PredefinedCategory.FOOD.equals(text)) {
            ...
        }
        int index = label.getIndex();
        if (PredefinedCategory.FOOD_INDEX == index) {
            ...
        }
        float confidence = label.getConfidence();
    }
}

Garantir une expérience utilisateur optimale

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

  • La réussite d'une détection d'objets dépend de sa complexité visuelle. Dans pour être détectés, les objets dotés d'un petit nombre de caractéristiques visuelles peuvent avoir besoin pour occuper une plus grande partie de l'image. Vous devez fournir aux utilisateurs des conseils sur en capturant une entrée qui fonctionne bien avec le type d'objets que vous souhaitez détecter.
  • Quand vous utilisez la classification, si vous souhaitez détecter les objets qui ne tombent pas correctement dans les catégories prises en charge, implémenter un traitement spécial pour les d'objets.

Consultez également les Application de présentation Material Design de ML Kit et Material Design Modèles pour la collection de 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 pour obtenir des fréquences d'images optimales:

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

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

  • Si vous utilisez les Camera ou API camera2 limiter les appels au détecteur. Si une nouvelle vidéo devient disponible pendant l'exécution du détecteur, supprimez la trame. Consultez le VisionProcessorBase de l'application exemple de démarrage rapide.
  • Si vous utilisez l'API CameraX, Assurez-vous que la stratégie de contre-pression est définie sur sa valeur par défaut ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST Cela garantit qu'une seule image à la fois sera envoyée pour analyse. Si davantage d'images sont générées lorsque l'analyseur est occupé, elles sont automatiquement abandonnées et ne sont pas mises en file d'attente la livraison. Une fois que l'image en cours d'analyse est fermée en appelant ImageProxy.close(), l'image suivante la plus récente sera envoyée.
  • Si vous utilisez la sortie du détecteur pour superposer des graphiques sur l'image d'entrée, récupérez d'abord le résultat à partir de ML Kit, puis effectuez le rendu de l'image. et les superposer en une seule étape. Le rendu à la surface d'affichage une seule fois pour chaque trame d'entrée. Consultez le CameraSourcePreview et GraphicOverlay de l'application exemple de démarrage rapide.
  • Si vous utilisez l'API Camera2, capturez des images Format ImageFormat.YUV_420_888. Si vous utilisez l'ancienne API Camera, capturez les images Format ImageFormat.NV21.