Análise de imagem

O caso de uso de análise de imagem fornece ao seu app uma imagem acessível por CPU em que é possível realizar processamento de imagens, visão computacional ou inferências de machine learning. O app implementa um método analyze() executado em cada frame.

Modos de operação

Quando o pipeline de análise do app não consegue atender aos requisitos de frame rate da CameraX, essa biblioteca pode ser configurada para descartar frames de uma das seguintes maneiras:

  • Sem bloqueio (padrão): nesse modo, o executor sempre armazena em cache a imagem mais recente em um buffer de imagem, semelhante a uma fila com profundidade de uma, enquanto o app analisa a imagem anterior. Se a CameraX receber uma nova imagem antes do término do processamento do app, a nova imagem será salva no mesmo buffer, substituindo a imagem anterior. Observe que ImageAnalysis.Builder.setImageQueueDepth() não tem efeito nesse cenário, e o conteúdo do buffer é sempre substituído. É possível ativar esse modo sem bloqueio chamando o método setBackpressureStrategy() com STRATEGY_KEEP_ONLY_LATEST. Para mais informações sobre implicações do executor, consulte a documentação de referência para STRATEGY_KEEP_ONLY_LATEST.

  • Bloqueio: nesse modo, o executor interno pode adicionar várias imagens à fila interna de imagens e começar a descartar frames somente quando a fila estiver cheia. O bloqueio ocorre em todo o escopo do dispositivo de câmera. Se o dispositivo tiver vários casos de uso vinculados, eles serão bloqueados enquanto a CameraX estiver processando essas imagens. Por exemplo, quando a prévia e a análise de imagem estão vinculadas a um dispositivo de câmera, a prévia também é bloqueada durante o processamento das imagens pela CameraX. É possível ativar o modo de bloqueio transmitindo STRATEGY_BLOCK_PRODUCER para o método setBackpressureStrategy(). Também é possível configurar a profundidade da fila de imagens usando ImageAnalysis.Builder.setImageQueueDepth().

Com um analisador de baixa latência e alto desempenho, em que o tempo total para analisar uma imagem é menor que a duração de um frame da CameraX (16 ms para 60 fps, por exemplo), qualquer um dos modos de operação oferece uma experiência suave de maneira geral. O modo de bloqueio ainda pode ser útil em algumas situações, como no caso de instabilidades muito breves no sistema.

Com um analisador de alta latência e alto desempenho, o modo de bloqueio com uma fila mais longa é necessário para compensar a latência. No entanto, o app ainda pode processar todos os frames.

Com uma alta latência e um analisador demorado (que não consegue processar todos os frames), um modo sem bloqueio pode ser a escolha mais adequada, já que os frames precisam ser descartados no caminho de análise, mas outros casos de uso vinculados simultaneamente ainda podem ver todos os frames.

Implementação

Para usar a análise de imagem no seu app, siga estas etapas:

Imediatamente após a vinculação, a CameraX envia imagens para seu analisador registrado. Depois de concluir a análise, chame ImageAnalysis.clearAnalyzer() ou desvincule o caso de uso de ImageAnalysis para interromper a análise.

Criar um caso de uso de ImageAnalysis

A classe ImageAnalysis conecta seu analisador (um consumidor de imagem) à CameraX, que é uma produtora de imagens. Os apps podem usar a classe ImageAnalysis.Builder para criar um objeto ImageAnalysis. Com a ImageAnalysis.Builder, o app pode configurar o seguinte:

Os apps podem definir a resolução ou a proporção, mas não ambos. A resolução exata depende da proporção ou do tamanho solicitado para o app e dos recursos de hardware e pode ser diferente da proporção ou do tamanho solicitado. Para informações sobre o algoritmo de correspondência de resolução, consulte a documentação de setTargetResolution()

Um app pode configurar os pixels da imagem de saída para ficarem em espaços de cor YUV (padrão) ou RGBA. Ao configurar um formato de saída RGBA, a CameraX converte internamente imagens de YUV para RGBA e empacota os bits de imagem na classe ByteBuffer do primeiro plano de ImageProxy (os outros dois planos não são usados) com esta sequência:

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

Ao realizar uma análise de imagem complicada, em que o dispositivo não consegue acompanhar o frame rate, configure a CameraX para descartar frames com as estratégias descritas na seção Modos de operação desta página.

Criar seu analisador

Os apps podem criar analisadores implementando a interface ImageAnalysis.Analyzer e substituindo analyze(ImageProxy image). Em cada analisador, os apps recebem uma interface ImageProxy, que é um wrapper para Media.Image. O formato da imagem pode ser consultado com ImageProxy.getFormat(). O formato é um dos seguintes valores fornecidos pelo app com a classe ImageAnalysis.Builder:

  • ImageFormat.RGBA_8888 se o app solicitou OUTPUT_IMAGE_FORMAT_RGBA_8888.
  • ImageFormat.YUV_420_888 se o app solicitou OUTPUT_IMAGE_FORMAT_YUV_420_888.

Consulte a seção Criar um caso de uso de ImageAnalysis para ver configurações de espaço de cores e onde os bytes de pixel podem ser recuperados.

Em um analisador, o app precisa fazer o seguinte:

  1. Analisar um frame específico o mais rápido possível, de preferência dentro do limite de tempo de frame rate especificado (por exemplo, menos de 32 ms para 30 fps). Se o app não conseguir analisar um frame com rapidez suficiente, considere um dos mecanismos suportados para descarte de frames.
  2. Libere a interface ImageProxy para a CameraX chamando ImageProxy.close(). Não chame a função de fechamento da Media.Image encapsulada (Media.Image.close()).

Os apps podem usar a Media.Image encapsulada diretamente na interface ImageProxy. Só não chame Media.Image.close() na imagem encapsulada, porque isso corromperia o mecanismo de compartilhamento de imagens na CameraX. Em vez disso, use ImageProxy.close() para liberar a Media.Image para a CameraX.

Configurar o analisador para a ImageAnalysis

Depois de criar um analisador, use ImageAnalysis.setAnalyzer() para registrá-lo e dar início à análise. Quando ela terminar, use ImageAnalysis.clearAnalyer() para remover o analisador registrado.

Apenas um analisador ativo pode ser configurado para a análise de imagens. Chamar ImageAnalysis.setAnalyzer() substitui o analisador registrado, se ele já existir. Os apps podem definir um novo analisador a qualquer momento, antes ou depois de vincular o caso de uso.

Vincular a ImageAnalysis a um ciclo de vida

É altamente recomendável vincular a ImageAnalysis a um ciclo de vida do AndroidX com a função ProcessCameraProvider.bindToLifecycle(). Observe que a função bindToLifecycle() retorna o dispositivo de câmera selecionado, que pode ser usado para ajustar configurações avançadas, como a exposição.

O exemplo a seguir combina todas as etapas anteriores, vinculando os casos de uso de ImageAnalysis e Preview da CameraX a um proprietário 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);

Outros recursos

Para saber mais sobre o CameraX, consulte os seguintes recursos.

Codelab

  • Introdução à CameraX
  • Exemplo de código

  • App de exemplo oficial do CameraX (link em inglês)