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 leur position. Lors de la détection d'objets dans des flux vidéo, chaque objet possède un identifiant unique que vous pouvez utiliser pour suivre l'objet d'une image à l'autre. Vous pouvez également activer la classification approximative d'objets, qui attribue des étiquettes aux objets avec des descriptions de catégorie étendues.
Essayer
- Jouez avec l'application exemple pour voir un exemple d'utilisation de cette API.
- Accédez à l'application Material Design Showcase pour découvrir l'implémentation de cette API de bout en bout.
Avant de commencer
- Dans le fichier
build.gradle
au niveau du projet, veillez à inclure le dépôt Maven de Google dans les sectionsbuildscript
etallprojects
. - Ajoutez les dépendances des bibliothèques Android de ML Kit au fichier Gradle au niveau de l'application de votre module, qui est 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
et spécifiez éventuellement les paramètres de détecteur que vous souhaitez modifier par rapport aux valeurs par défaut.
Configurez le détecteur d'objets pour votre cas d'utilisation avec un objet
ObjectDetectorOptions
. 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
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 (par exemple, des cadres de délimitation ou des libellés de catégorie non spécifiés) lors des premiers appels du détecteur. En outre, dansSTREAM_MODE
, le détecteur attribue des ID de suivi aux objets, que vous pouvez utiliser pour suivre des objets sur plusieurs frames. Utilisez ce mode lorsque vous souhaitez suivre des objets ou lorsqu'une faible latence est importante, par exemple pour traiter des flux vidéo en temps réel.Dans
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, elle renvoie le résultat une fois que le cadre de délimitation et le libellé de catégorie sont tous deux disponibles. Par conséquent, la latence de détection est potentiellement plus élevée. Par ailleurs, dansSINGLE_IMAGE_MODE
, les ID de suivi ne sont pas attribué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
Permet de détecter et de suivre jusqu'à cinq objets ou uniquement l'objet le plus important (par défaut).
Classer des objets false
(par défaut) |true
Indique si les objets détectés doivent être classés dans des 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, lieux et plantes.
L'API de détection et de suivi des objets est optimisée pour ces deux principaux cas d'utilisation:
- Détection et suivi en direct de l'objet le plus proéminent 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:
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();
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 à la méthodeprocess()
de l'instance ObjectDetector
.
Le détecteur d'objets s'exécute directement à partir d'un appareil Bitmap
, NV21 ByteBuffer
ou YUV_420_888 media.Image
. Il est recommandé de construire un InputImage
à partir de ces sources si vous disposez d'un accès direct à l'une d'entre elles. Si vous créez un InputImage
à partir d'autres sources, nous traiterons la conversion en interne pour vous, ce qui peut s'avérer moins efficace.
Pour chaque image d'une vidéo ou d'une image dans une séquence, procédez comme suit:
Vous pouvez créer un objet InputImage
à partir de différentes sources, expliquées ci-dessous.
Utiliser un media.Image
Pour créer un objet InputImage
à partir d'un objet media.Image
, par exemple lorsque vous capturez une image avec l'appareil photo d'un appareil, transmettez l'objet media.Image
et la rotation de l'image à InputImage.fromMediaImage()
.
Si vous utilisez la bibliothèque
Camera Camera, les classes OnImageCapturedListener
et ImageAnalysis.Analyzer
calculent automatiquement la valeur de rotation.
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 fournit le degré de rotation de l'image, vous pouvez le calculer à partir du degré de rotation de l'appareil et de l'orientation du capteur de l'appareil photo 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; }
Transmettez ensuite l'objet media.Image
et la valeur du degré de rotation à 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 objet 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 utilisez un intent ACTION_GET_CONTENT
pour inviter l'utilisateur à sélectionner une image dans 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 objet InputImage
à partir d'un ByteBuffer
ou d'un ByteArray
, commencez par calculer le degré de rotation de l'image comme décrit précédemment pour l'entrée media.Image
.
Créez ensuite l'objet InputImage
avec le tampon ou le tableau, ainsi que la hauteur, la largeur, le format d'encodage des couleurs et le degré de rotation de l'image:
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 objet 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
accompagné de degrés de rotation.
3. Traiter l'image
Transmettez l'image à la méthodeprocess()
:
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 de 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 | Rect qui indique la position de l'objet dans l'image. |
||||||
ID de suivi | Entier qui identifie l'objet dans les images. Valeur nulle en mode SINGLE_IMAGE_MODE. | ||||||
Étiquettes |
|
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 les consignes ci-dessous dans votre application:
- La réussite de la détection d'un objet dépend de sa complexité visuelle. 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 des 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 n'appartiennent pas correctement aux catégories compatibles, mettez en œuvre un traitement spécial pour les objets inconnus.
Consultez également l'application de présentation ML Kit Material Design et la collection Material Design Patterns for machine learning features (Modèles pour des 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'ayez pas recours à la détection d'objets multiples, car la plupart des appareils ne pourront pas produire de fréquences d'images adéquates.
Désactivez la classification si vous n'en avez pas besoin.
- Si vous utilisez l'API
Camera
oucamera2
, limitez les appels au détecteur. Si une nouvelle image vidéo devient disponible pendant que le détecteur est en cours d'exécution, déposez l'image. Consultez la classeVisionProcessorBase
dans l'exemple d'application de démarrage rapide pour voir un exemple. - Si vous utilisez l'API
CameraX
, assurez-vous que la stratégie de contre-pression est définie sur sa valeur par défautImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
. Cela garantit qu'une seule image à la fois sera diffusée pour analyse. Si davantage d'images sont produites alors que l'analyseur est occupé, elles seront automatiquement supprimées et ne seront pas mises en file d'attente pour la diffusion. Une fois l'image analysée fermée en appelant ImageProxy.close(), la dernière image suivante sera diffusée. - Si vous utilisez la sortie du détecteur pour superposer des éléments graphiques à l'image d'entrée, obtenez d'abord le résultat de ML Kit, puis affichez l'image et la superposition en une seule étape. La surface d'affichage n'est donc rendue qu'une seule fois pour chaque image d'entrée. Consultez les classes
CameraSourcePreview
etGraphicOverlay
dans l'exemple d'application de démarrage rapide pour voir un exemple. - Si vous utilisez l'API Camera2, capturez des images au format
ImageFormat.YUV_420_888
. Si vous utilisez l'ancienne API Camera, capturez des images au formatImageFormat.NV21
.