Análisis de imágenes

El caso de uso de análisis de imágenes le proporciona a la 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.

Para obtener información sobre cómo integrar el ML Kit de Google a la app de CameraX, consulta Analizador de ML Kit.

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 que quite fotogramas de una 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 con 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 de operación 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 requiere mucho tiempo y una latencia alta (el analizador no puede procesar todos los fotogramas), el 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 al 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.

Cómo compilar el caso de uso de ImageAnalysis

ImageAnalysis conecta el 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 la 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 de salida 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 de color 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
...

Cuando se realiza un análisis de imágenes complicado en el 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 de operación de este tema.

Cómo crear tu analizador

Las aplicaciones pueden crear analizadores implementando la interfaz ImageAnalysis.Analyzer y anulando 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 el caso de uso de compilación de ImageAnalysis para conocer las configuraciones de espacio de color y dónde se pueden recuperar los bytes de píxeles.

Dentro de un analizador, la aplicación debe hacer lo siguiente:

  1. Analizar un fotograma dado lo más rápido posible, preferentemente dentro del límite de tiempo de velocidad de fotogramas determinado (por ejemplo, menos de 32 ms para un caso de 30 fps). Si la aplicación no puede analizar un fotograma con la suficiente rapidez, considera uno de los mecanismos de descarte de fotogramas 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 el elemento Media.Image subyacente a CameraX.

Cómo configurar el analizador para ImageAnalysis

Una vez que hayas creado el analizador, usa ImageAnalysis.setAnalyzer() para registrarlo a fin de comenzar el análisis. Una vez que hayas terminado con el análisis, usa ImageAnalysis.clearAnalyzer() 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 Camera seleccionado, que se puede usar con el propósito de ajustar los parámetros de configuración avanzada, como la exposición, entre otros. Consulta esta guía para obtener más información para controlar la salida de la cámara.

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

  • Cómo comenzar a usar CameraX
  • Muestra de código

  • Apps de ejemplo de CameraX