Analyse des images

Le cas d'utilisation de l'analyse des images fournit à votre application une image accessible par processeur permettant d'effectuer le traitement d'images, la vision par ordinateur ou l'inférence de machine learning. L'application implémente une méthode analyze() qui est exécutée sur chaque frame.

Pour savoir comment intégrer le ML Kit de Google à votre application CameraX, consultez la page Analyseur ML Kit.

Modes de fonctionnement

Lorsque le pipeline d'analyse de l'application ne peut pas répondre aux exigences de fréquence de frames de CameraX, il est possible de configurer CameraX pour abandonner des frames de l'une des manières suivantes :

  • Non bloquant (par défaut) : dans ce mode, l'exécuteur met toujours en cache la dernière image dans un tampon d'image (semblable à une file d'attente dont la profondeur correspond à un) pendant que l'application analyse l'image précédente. Si CameraX reçoit une nouvelle image avant la fin du traitement de l'application, celle-ci est enregistrée dans le même tampon et remplace l'image précédente. Notez que ImageAnalysis.Builder.setImageQueueDepth() n'a aucun effet dans ce scénario et que le contenu du tampon est toujours remplacé. Vous pouvez activer ce mode non bloquant en appelant setBackpressureStrategy() avec STRATEGY_KEEP_ONLY_LATEST. Pour en savoir plus sur les conséquences liées à l'exécuteur, consultez la documentation de référence sur STRATEGY_KEEP_ONLY_LATEST.

  • Bloquant : dans ce mode, l'exécuteur interne peut ajouter plusieurs images à la file d'attente d'images internes et ne commence à abandonner des frames que lorsque la file d'attente est pleine. Le blocage s'applique à l'ensemble du champ d'application de l'appareil photo : si l'appareil photo est associé à plusieurs cas d'utilisation, ceux-ci seront tous bloqués pendant que CameraX traite ces images. Par exemple, lorsque l'aperçu et l'analyse des images sont associés à un appareil photo, l'aperçu est également bloqué pendant que CameraX traite les images. Vous pouvez activer le mode bloquant en transmettant STRATEGY_BLOCK_PRODUCER à setBackpressureStrategy(). Vous pouvez également configurer la profondeur de la file d'attente d'images à l'aide de ImageAnalysis.Builder.setImageQueueDepth().

Avec un analyseur hautes performances à faible latence permettant d'analyser une image en un temps total inférieur à la durée d'un frame CameraX (16 ms pour 60 fps, par exemple), les deux modes de fonctionnement offrent une expérience globale fluide. Le mode bloquant peut s'avérer utile dans certains cas, tels que les gigues très brèves du système.

Avec un analyseur hautes performances à latence élevée, un mode bloquant avec une file d'attente plus longue est nécessaire pour compenser la latence. Notez toutefois que l'application peut toujours traiter tous les frames.

Avec un analyseur chronophage à latence élevée (l'analyseur est incapable de traiter tous les frames), il serait plus judicieux d'opter pour un mode non bloquant, car les frames doivent être abandonnés pour le chemin d'analyse, mais les autres cas d'utilisation simultanés associés peuvent toujours voir tous les frames.

Implémentation

Pour utiliser l'analyse des images dans votre application, procédez comme suit :

Dès que la liaison est établie, CameraX envoie des images à votre analyseur enregistré. Une fois l'analyse terminée, appelez ImageAnalysis.clearAnalyzer() ou dissociez le cas d'utilisation ImageAnalysis pour arrêter l'analyse.

Créer un cas d'utilisation "ImageAnalysis"

ImageAnalysis connecte votre analyseur (un consommateur d'images) à CameraX, qui génère des images. Les applications peuvent utiliser ImageAnalysis.Builder pour créer un objet ImageAnalysis. ImageAnalysis.Builder permet à l'application de configurer les éléments suivants :

Les applications peuvent définir la résolution ou le format, mais pas les deux. La résolution de sortie exacte dépend de la taille (ou du format) demandée et des capacités matérielles de l'application. Elle peut différer de la taille ou du ratio demandé. Pour en savoir plus sur l'algorithme de mise en correspondance des résolutions, consultez la documentation sur setTargetResolution().

Une application peut configurer les pixels d'image de sortie dans des espaces de couleur YUV (par défaut) ou RVBA. Lors de la définition d'un format de sortie RVBA, CameraX convertit en interne les images de l'espace couleur de YUV à RVBA et regroupe les bits de l'image dans le ByteBuffer du premier plan de "ImageProxy" (les deux autres plans ne sont pas utilisés) avec la séquence suivante :

ImageProxy.getPlanes()[0].buffer[0]: alpha
ImageProxy.getPlanes()[0].buffer[1]: red
ImageProxy.getPlanes()[0].buffer[2]: green
ImageProxy.getPlanes()[0].buffer[3]: blue
...

Lorsque vous effectuez une analyse complexe des images et que l'appareil ne peut pas suivre la fréquence de frames, vous pouvez configurer CameraX pour abandonner les frames avec les stratégies décrites dans la section Modes de fonctionnement de ce sujet.

Créer votre analyseur

Les applications peuvent créer des analyseurs en implémentant l'interface ImageAnalysis.Analyzer et en ignorant analyze(ImageProxy image). Dans chaque analyseur, les applications reçoivent un ImageProxy, qui est un wrapper pour Media.Image. Le format d'image peut être interrogé avec ImageProxy.getFormat(). Le format est l'une des valeurs suivantes fournies par l'application avec l'ImageAnalysis.Builder :

  • ImageFormat.RGBA_8888 si l'application a demandé OUTPUT_IMAGE_FORMAT_RGBA_8888.
  • ImageFormat.YUV_420_888 si l'application a demandé OUTPUT_IMAGE_FORMAT_YUV_420_888.

Consultez la section Créer un cas d'utilisation "ImageAnalysis" pour en savoir plus sur les configurations de l'espace de couleur et où récupérer les octets de pixels.

Dans un analyseur, l'application doit :

  1. Analyser un frame donné le plus rapidement possible, de préférence dans la limite de temps de fréquence de frames donnée (par exemple, moins de 32 ms pour une fréquence de 30 fps). Si l'application ne peut pas analyser un frame assez rapidement, pensez à utiliser l'un des mécanismes d'abandon de frames compatibles.
  2. Appeler ImageProxy.close() afin de libérer l'ImageProxy pour CameraX. Notez que vous ne devez pas appeler la fonction de fermeture de "Media.Image" (Media.Image.close()) encapsulée.

Les applications peuvent utiliser la Media.Image encapsulée dans "ImageProxy" directement. N'appelez pas Media.Image.close() sur l'image encapsulée, car cela endommagerait le mécanisme de partage d'image dans CameraX. Utilisez plutôt ImageProxy.close() afin de libérer la Media.Image sous-jacente pour CameraX.

Configurer votre analyseur pour "ImageAnalysis"

Une fois l'analyseur créé, utilisez ImageAnalysis.setAnalyzer() pour l'enregistrer et commencer l'analyse. Une fois l'analyse terminée, supprimez l'analyseur enregistré à l'aide de ImageAnalysis.clearAnalyzer().

Vous ne pouvez configurer qu'un seul analyseur actif pour l'analyse des images. L'appel de ImageAnalysis.setAnalyzer() remplace l'analyseur enregistré s'il existe déjà. Les applications peuvent définir un nouvel analyseur à tout moment, avant ou après la liaison du cas d'utilisation.

Associer "ImageAnalysis" à un cycle de vie

Il est vivement recommandé d'associer votre ImageAnalysis à un cycle de vie AndroidX existant à l'aide de la fonction ProcessCameraProvider.bindToLifecycle(). Notez que la fonction bindToLifecycle() renvoie l'appareil Camera sélectionné, qui peut être utilisé pour affiner des paramètres avancés, notamment l'exposition. Pour en savoir plus sur le contrôle de la sortie de l'appareil photo, consultez ce guide.

L'exemple suivant combine tous les éléments des étapes précédentes, en associant les cas d'utilisation ImageAnalysis et Preview de CameraX à un propriétaire de lifeCycle :

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    // enable the following line if RGBA output is needed.
    // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
    .setTargetResolution(Size(1280, 720))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->
    val rotationDegrees = imageProxy.imageInfo.rotationDegrees
    // insert your code here.
    ...
    // after done, release the ImageProxy object
    imageProxy.close()
})

cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)

Java

ImageAnalysis imageAnalysis =
    new ImageAnalysis.Builder()
        // enable the following line if RGBA output is needed.
        //.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
        .setTargetResolution(new Size(1280, 720))
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build();

imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
    @Override
    public void analyze(@NonNull ImageProxy imageProxy) {
        int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
            // insert your code here.
            ...
            // after done, release the ImageProxy object
            imageProxy.close();
        }
    });

cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, imageAnalysis, preview);

Ressources supplémentaires

Pour en savoir plus sur CameraX, consultez les ressources supplémentaires suivantes.

Atelier de programmation

  • Premiers pas avec CameraX
  • Exemple de code

  • Applications exemples de CameraX