Analyse d'image

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 :

  • Mode non bloquant (par défaut) : dans ce mode, l'exécuteur met toujours en cache la dernière image reçue dans un tampon d'image (semblable à une file d'attente d'une profondeur égale à 1) pendant que l'application analyse l'image précédente. Si CameraX reçoit une nouvelle image alors que le traitement en cours dans l'application n'est pas terminé, cette nouvelle image 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'utilisation de l'exécuteur, consultez la documentation de référence sur STRATEGY_KEEP_ONLY_LATEST.

  • Mode bloquant : dans ce mode, l'exécuteur interne peut ajouter plusieurs images à la file d'attente d'images interne, 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, si l'aperçu et l'analyse d'image sont tous deux 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().

Si vous utilisez un analyseur hautes performances à faible latence avec lequel la durée totale d'analyse d'une image est inférieure à celle 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, par exemple si le système fait l'objet de gigues très brèves.

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 pourra quand même traiter tous les frames.

Avec un analyseur chronophage à latence élevée (qui ne peut pas traiter tous les frames), il peut s'avérer plus judicieux d'opter pour un mode non bloquant. En effet, les frames doivent être abandonnés dans le chemin d'analyse, mais les autres cas d'utilisation simultanés associés pourront quand même voir tous les frames.

Implémentation

Pour utiliser l'analyse d'image dans votre application :

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 par l'application et des capacités matérielles. 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 de l'image de sortie dans des espaces de couleurs YUV (par défaut) ou RVBA. Si le format de sortie choisi est RVBA, CameraX convertit les images en interne pour les faire passer de l'espace de couleurs YUV à l'espace de couleurs RVBA. Il regroupe aussi les bits des images dans le ByteBuffer du premier plan d'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
...

Si 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 qu'il abandonne les frames en utilisant les stratégies décrites dans la section Modes de fonctionnement de cet article.

Créer votre analyseur

Les applications peuvent créer des analyseurs en implémentant l'interface ImageAnalysis.Analyzer et en remplaçant 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 des espaces de couleurs 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 définie pour la fréquence de frames (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 directement dans ImageProxy. 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 d'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