Análisis de imágenes

El caso de uso de análisis de imágenes le proporciona a tu app una imagen a la que se puede acceder desde la CPU en la que puedes realizar procesamiento de imágenes, visión artificial o inferencia de aprendizaje automático. La aplicación implementa un método analyze() que se ejecuta en cada fotograma.

Modos de operación

Cuando la canalización de análisis de la aplicación no puede seguir el ritmo de los requisitos de velocidad de fotogramas de CameraX, se puede configurar CameraX para descartar fotogramas de alguna de las siguientes maneras:

  • Sin bloqueo (configuración predeterminada): En este modo, mientras la aplicación analiza la imagen anterior, el ejecutor siempre almacena en caché la imagen más reciente en un búfer de imagen (similar a una cola con una profundidad de uno). Si CameraX recibe una imagen nueva antes de que la aplicación termine de procesarse, la imagen nueva se guarda en el mismo búfer y reemplaza la anterior. Ten en cuenta que ImageAnalysis.Builder.setImageQueueDepth() no tiene ningún efecto en esta situación y que el contenido del búfer siempre se reemplaza. Para habilitar este modo sin bloqueo, llama a setBackpressureStrategy() con STRATEGY_KEEP_ONLY_LATEST. Para obtener más información sobre las implicaciones del ejecutor, consulta la documentación de referencia de STRATEGY_KEEP_ONLY_LATEST.

  • Bloqueo: En este modo, el ejecutor interno puede agregar varias imágenes a la cola de imágenes internas y comenzar a descartar fotogramas solo cuando la cola esté completa. El bloqueo ocurre en todo el alcance del dispositivo de cámara: Si este tiene varios casos de uso vinculados, estos se bloquearán mientras CameraX procese estas imágenes. Por ejemplo, si la vista previa y el análisis de imágenes están vinculados a un dispositivo de cámara, la vista previa también se bloquea mientras CameraX procesa las imágenes. Puedes habilitar el modo de bloqueo si pasas STRATEGY_BLOCK_PRODUCER a setBackpressureStrategy(). También puedes configurar la profundidad de la cola de imágenes mediante ImageAnalysis.Builder.setImageQueueDepth().

Con un analizador de baja latencia y alto rendimiento en el que el tiempo total para analizar una imagen es menor que la duración de un fotograma de CameraX (por ejemplo, 16 ms para 60 fps), cualquiera de los modos operativos proporciona una buena experiencia general. El modo de bloqueo aún puede ser útil en algunas situaciones; por ejemplo, para jitters muy breves en el sistema.

Con un analizador de latencia alta y de alto rendimiento, el modo de bloqueo debe tener una cola más larga para compensar la latencia. Sin embargo, ten en cuenta que la aplicación aún podrá procesar todos los fotogramas.

Con un analizador que consume mucho tiempo y tiene latencia alta (el analizador no puede procesar todos los fotogramas), un modo sin bloqueo podría ser una opción más apropiada, ya que se deben descartar los fotogramas para la ruta de análisis, pero otros casos de uso de vinculación simultáneos aún pueden ver todos los fotogramas.

Implementación

Para usar el análisis de imágenes en tu aplicación, sigue estos pasos:

Inmediatamente después de la vinculación, CameraX envía imágenes a tu analizador registrado. Después de completar el análisis, llama a ImageAnalysis.clearAnalyzer() o desvincula el caso de uso de ImageAnalysis para detener el análisis.

Compila el caso de uso de ImageAnalysis

ImageAnalysis conecta tu analizador (un consumidor de imágenes) a CameraX, que es un productor de imágenes. Las aplicaciones pueden usar ImageAnalysis.Builder para compilar un objeto ImageAnalysis. Con ImageAnalysis.Builder, la aplicación puede configurar lo siguiente:

Las aplicaciones pueden establecer la resolución o la relación de aspecto, pero no ambas. La resolución exacta del resultado depende del tamaño solicitado (o la relación de aspecto) de la aplicación y de las capacidades del hardware, y puede diferir del tamaño o la relación solicitados. Para obtener información sobre el algoritmo de coincidencia de resolución, consulta la documentación de setTargetResolution().

Una aplicación puede configurar los píxeles de imagen de salida para que estén en YUV (predeterminado) o en espacios de color RGBA. Cuando configuras un formato de salida RGBA, CameraX convierte internamente las imágenes del espacio YUV a RGBA, y empaqueta los bits de imagen en el ByteBuffer del primer plano del ImageProxy (no se usan los otros dos planos) con la siguiente secuencia:

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

Al realizar un análisis de imágenes complicado en los que el dispositivo no puede seguir el ritmo de la velocidad de fotogramas, puedes configurar CameraX para que descarte fotogramas con las estrategias que se describen en la sección Modos operativos de este tema.

Cómo crear tu analizador

Las aplicaciones pueden crear analizadores mediante la implementación de la interfaz ImageAnalysis.Analyzer y la anulación de analyze(ImageProxy image). En cada analizador, las aplicaciones reciben un ImageProxy, que es un wrapper para Media.Image. El formato de imagen se puede buscar con ImageProxy.getFormat(). El formato es uno de los siguientes valores que la aplicación proporciona con el ImageAnalysis.Builder:

  • ImageFormat.RGBA_8888 si la app solicitó OUTPUT_IMAGE_FORMAT_RGBA_8888.
  • ImageFormat.YUV_420_888 si la app solicitó OUTPUT_IMAGE_FORMAT_YUV_420_888.

Consulta la compilación de caso de uso de ImageAnalysis para obtener configuraciones de espacio de color y dónde se pueden recuperar los bytes de píxeles.

Dentro de un analizador, la app debería hacer lo siguiente:

  1. Analizar un fotograma determinado lo más rápido posible, preferentemente dentro del límite de velocidad de fotogramas determinado (por ejemplo, inferior a 32 ms para casos de 30 fps). Si la aplicación no puede analizar un fotograma con la suficiente rapidez, considera uno de los mecanismos de descarte compatibles.
  2. Llama a ImageProxy.close() para liberar el ImageProxy a CameraX. Ten en cuenta que no debes llamar a la función de cierre de Media.Image unida (Media.Image.close()).

Las aplicaciones pueden usar el objeto Media.Image unido dentro de ImageProxy directamente. No llames a Media.Image.close() en la imagen unida, ya que se interrumpiría el mecanismo de uso compartido de la imagen dentro de CameraX. En su lugar, usa ImageProxy.close() para liberar la Media.Image subyacente a CameraX.

Cómo configurar el analizador para ImageAnalysis

Una vez que hayas creado un analizador, usa ImageAnalysis.setAnalyzer() para registrarlo a fin de comenzar el análisis. Una vez que hayas terminado con el análisis, usa ImageAnalysis.clearAnalyer() para quitar el analizador registrado.

Solo se puede configurar un analizador activo para el análisis de imágenes. Llamar a ImageAnalysis.setAnalyzer() reemplaza al analizador registrado si ya existe. Las aplicaciones pueden establecer un analizador nuevo en cualquier momento, antes o después de vincular el caso de uso.

Cómo vincular ImageAnalysis a un ciclo de vida

Te recomendamos vincular el ImageAnalysis a un ciclo de vida de AndroidX existente con la función ProcessCameraProvider.bindToLifecycle(). Ten en cuenta que la función bindToLifecycle() muestra el dispositivo de cámara seleccionado, que se puede usar para ajustar la configuración avanzada, como la exposición, entre otras.

En el siguiente ejemplo se combinan todos los pasos anteriores, lo que vincula los casos de uso de CameraX ImageAnalysis y Preview a un propietario 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);

Recursos adicionales

Para obtener más información sobre CameraX, consulta los siguientes recursos adicionales:

Codelab

  • Comienza a usar CameraX
  • Muestra de código

  • App de muestra de CameraX oficial