Vous pouvez utiliser ML Kit pour détecter et suivre des objets dans les images d'une vidéo.
Lorsque vous transmettez des images à ML Kit, celui-ci renvoie, pour chaque image, une liste contenant jusqu'à cinq objets détectés et leur position dans l'image. Lorsque vous détectez des objets dans des flux vidéo, chaque objet possède un ID que vous pouvez utiliser pour suivre l' objet dans les images. Vous pouvez également activer la classification grossière des objets, qui les étiquette avec des descriptions de catégories générales.
Avant de commencer
- Si ce n'est pas déjà fait, ajoutez Firebase à votre projet Android.
- Ajoutez les dépendances pour les bibliothèques Android ML Kit au fichier Gradle (généralement
app/build.gradle) de votre module (au niveau de l'application) :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 à détecter et à suivre des objets, créez d'abord une instance de
FirebaseVisionObjectDetector, en spécifiant éventuellement les paramètres de détecteur que vous
souhaitez modifier par rapport à la valeur par défaut.
Configurez le détecteur d'objets pour votre cas d'utilisation avec un
FirebaseVisionObjectDetectorOptionsobjet. 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_MODEEn
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 premières invocations du détecteur. De plus, enSTREAM_MODE, le détecteur attribue des ID de suivi aux objets, que vous pouvez utiliser pour suivre les objets dans les images. Utilisez ce mode lorsque vous souhaitez suivre des objets ou lorsque la faible latence est importante, par exemple lorsque vous traitez des flux vidéo en temps réel.En
SINGLE_IMAGE_MODE, le détecteur d'objets attend que le cadre de délimitation d'un objet détecté et (si vous avez activé la classification) le libellé de catégorie soient disponibles avant de renvoyer un résultat. Par conséquent, la latence de détection est potentiellement plus élevée. De plus, en modeSINGLE_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) |trueIndique 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) |trueIndique si vous souhaitez classer les objets détectés dans des catégories grossières. Lorsque cette option est activée, le détecteur d'objets classe les objets dans les catégories suivantes : articles de mode, aliments, 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 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 :
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
// 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()Obtenez une instance de
FirebaseVisionObjectDetector:Java
FirebaseVisionObjectDetector objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(); // Or, to change the default settings: FirebaseVisionObjectDetector objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options);Kotlin
val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector() // Or, to change the default settings: val objectDetector = FirebaseVision.getInstance().getOnDeviceObjectDetector(options)
2. Exécuter le détecteur d'objets
Pour détecter et suivre des objets, transmettez des images à la FirebaseVisionObjectDetector
méthode processImage() de l'instance.
Pour chaque image ou image vidéo d'une séquence, procédez comme suit :
Créez un objet
FirebaseVisionImageà partir de votre image.-
Pour créer un objet
FirebaseVisionImageà partir d'un objetmedia.Image, par exemple lorsque vous capturez une image à partir de l'appareil photo d'un appareil, transmettez l'objetmedia.Imageet la rotation de l'image àFirebaseVisionImage.fromMediaImage().Si vous utilisez la bibliothèque CameraX, les classes
OnImageCapturedListeneretImageAnalysis.Analyzercalculent la valeur de rotation pour vous. Vous n'avez donc qu'à convertir la rotation en l'une des constantesROTATION_de ML Kit avant d'appelerFirebaseVisionImage.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
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 d'appareil photo 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 l'appareil photo 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
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 }
Transmettez ensuite l'objet
media.Imageet la valeur de rotation àFirebaseVisionImage.fromMediaImage():Java
FirebaseVisionImage image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation);
Kotlin
val image = FirebaseVisionImage.fromMediaImage(mediaImage, rotation)
- Pour créer un objet
FirebaseVisionImageà partir d'un URI de fichier, transmettez le contexte de l'application et l'URI du fichier àFirebaseVisionImage.fromFilePath(). Cela est utile lorsque vous utilisez un intentACTION_GET_CONTENTpour inviter l'utilisateur à sélectionner une image dans son application Galerie.Java
FirebaseVisionImage image; try { image = FirebaseVisionImage.fromFilePath(context, uri); } catch (IOException e) { e.printStackTrace(); }
Kotlin
val image: FirebaseVisionImage try { image = FirebaseVisionImage.fromFilePath(context, uri) } catch (e: IOException) { e.printStackTrace() }
- Pour créer un objet
FirebaseVisionImageà partir d'unByteBufferou d'un tableau d'octets, calculez d'abord la rotation de l'image comme décrit ci-dessus pour l'entréemedia.Image.Créez ensuite un objet
FirebaseVisionImageMetadatacontenant la hauteur, la largeur, le format d'encodage des couleurs et la rotation de l'image :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
val metadata = FirebaseVisionImageMetadata.Builder() .setWidth(480) // 480x360 is typically sufficient for .setHeight(360) // image recognition .setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21) .setRotation(rotation) .build()
Utilisez le tampon ou le tableau, ainsi que l'objet de métadonnées, pour créer un
FirebaseVisionImageobjet :Java
FirebaseVisionImage image = FirebaseVisionImage.fromByteBuffer(buffer, metadata); // Or: FirebaseVisionImage image = FirebaseVisionImage.fromByteArray(byteArray, metadata);
Kotlin
val image = FirebaseVisionImage.fromByteBuffer(buffer, metadata) // Or: val image = FirebaseVisionImage.fromByteArray(byteArray, metadata)
- Pour créer un objet
FirebaseVisionImageà partir d'unBitmapobjet :L'image représentée par l'objetJava
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(bitmap);
Kotlin
val image = FirebaseVisionImage.fromBitmap(bitmap)
Bitmapdoit être droite, sans rotation supplémentaire requise.
-
Transmettez 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
objectDetector.processImage(image) .addOnSuccessListener { detectedObjects -> // Task completed successfully // ... } .addOnFailureListener { e -> // Task failed with an exception // ... }Si l'appel à
processImage()réussit, une liste deFirebaseVisionObjects est transmise à l'écouteur de réussite.Chaque
FirebaseVisionObjectcontient les propriétés suivantes :Cadre de délimitation Un Rectindiquant la position de l'objet dans l' image.ID de suivi Un entier qui identifie l'objet dans les images. Nul en SINGLE_IMAGE_MODE. Catégorie Catégorie grossière de l'objet. Si la classification n'est pas activée dans le détecteur d'objets, cette valeur est toujours FirebaseVisionObject.CATEGORY_UNKNOWN.Confiance Valeur de confiance de la classification de l'objet. Si la classification n'est pas activée dans le détecteur d'objets ou si l'objet est classé comme inconnu, cette valeur 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
// 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éliorer l'utilisabilité et les performances
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. Les objets comportant 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.
- 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][showcase-link]{: .external } et la collection de modèles Material Design pour les fonctionnalités basées sur le machine learning.
Lorsque vous utilisez le mode streaming dans une application en temps réel, suivez ces consignes pour obtenir les meilleures fréquences d'images :
N'utilisez pas la détection de plusieurs objets en mode streaming, car la plupart des appareils ne pourront pas produire des fréquences d'images adéquates.
Désactivez la classification si vous n'en avez pas besoin.
- Limitez les appels au détecteur. Si une nouvelle image vidéo devient disponible pendant l'exécution du détecteur, 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 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.
-
Si vous utilisez l'API Camera2, capturez des images au format
ImageFormat.YUV_420_888Si vous utilisez l'ancienne API Camera, capturez des images au
ImageFormat.NV21format.