Guia de incorporação de imagens para Android

A tarefa MediaPipe Image Embedder permite converter dados de imagem em uma representação numérica para realizar tarefas de processamento de imagens relacionadas ao ML, como comparar a semelhança de duas imagens. Estas instruções mostram como usar o incorporador de imagens com apps Android.

Para mais informações sobre os recursos, modelos e opções de configuração dessa tarefa, consulte a Visão geral.

Exemplo de código

O código de exemplo do MediaPipe Tasks é uma implementação simples de um app Image Embedder para Android. O exemplo usa a câmera de um dispositivo Android físico para incorporar imagens continuamente e também pode executar o incorporador em arquivos de imagem armazenados no dispositivo.

Use o app como ponto de partida para seu próprio app Android ou consulte-o ao modificar um app já existente. O código de exemplo do Image Embedder está hospedado no GitHub (em inglês).

Fazer o download do código

As instruções a seguir mostram como criar uma cópia local do código de exemplo usando a ferramenta de linha de comando git.

Para fazer o download do código de exemplo:

  1. Clone o repositório git usando o seguinte comando:
    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. Como opção, configure sua instância git para usar a finalização da compra esparsa para que você tenha apenas os arquivos do app de exemplo do incorporador de imagens:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_embedder/android
    

Depois de criar uma versão local do código de exemplo, você poderá importar o projeto para o Android Studio e executar o app. Para conferir instruções, consulte o Guia de configuração para Android.

Principais componentes

Os arquivos a seguir contêm o código essencial para este aplicativo de exemplo de incorporador de imagem:

  • ImageEmbedderHelper.kt: inicializa o incorporador de imagem, manipula o modelo e delega a seleção.
  • MainActivity.kt: implementa o aplicativo e monta os componentes da interface do usuário.

Configuração

Esta seção descreve as principais etapas para configurar seu ambiente de desenvolvimento e projetos de código para usar o incorporador de imagens. Para informações gerais sobre a configuração do ambiente de desenvolvimento para o uso de tarefas do MediaPipe, incluindo requisitos de versão da plataforma, consulte o Guia de configuração para Android.

Dependências

O incorporador de imagens usa a biblioteca com.google.mediapipe:tasks-vision. Adicione essa dependência ao arquivo build.gradle do projeto de desenvolvimento do app Android. Importe as dependências necessárias com o seguinte código:

dependencies {
    ...
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
}

Modelo

A tarefa MediaPipe Image Embedder requer um modelo treinado compatível com ela. Para mais informações sobre os modelos treinados disponíveis para o incorporador de imagens, consulte a seção Modelos de visão geral da tarefa.

Selecione e faça o download do modelo e armazene-o no diretório do projeto:

<dev-project-root>/src/main/assets

Especifique o caminho do modelo no parâmetro ModelAssetPath. No código de exemplo, o modelo é definido na função setupImageEmbedder() no arquivo ImageEmbedderHelper.kt:

Use o método BaseOptions.Builder.setModelAssetPath() para especificar o caminho usado pelo modelo. Esse método é mencionado no exemplo de código na próxima seção.

Criar a tarefa

Use a função createFromOptions para criar a tarefa. A função createFromOptions aceita opções de configuração para definir as opções do incorporador. Para mais informações sobre as opções de configuração, consulte Visão geral da configuração.

A tarefa Incorporador de imagens oferece suporte a três tipos de dados de entrada: imagens estáticas, arquivos de vídeo e streams de vídeo ao vivo. Ao criar a tarefa, é necessário especificar o modo de execução correspondente ao tipo de dados de entrada. Escolha a guia correspondente ao tipo de dados de entrada para conferir como criar a tarefa e executar a inferência.

Imagem

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.IMAGE)
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

Video

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.VIDEO)
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

Transmissão ao vivo

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setResultListener((result, inputImage) -> {
         // Process the embedding result here.
    })
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

O exemplo de implementação de código permite que o usuário alterne entre os modos de processamento. A abordagem torna o código de criação de tarefas mais complicado e pode não ser adequado para seu caso de uso. Esse código pode ser encontrado na função setupImageEmbedder() do arquivo ImageEmbedderHelper.kt.

Opções de configuração

Esta tarefa tem as seguintes opções de configuração para apps Android:

Nome da opção Descrição Intervalo de valor Valor padrão
runningMode Define o modo de execução da tarefa. Há três modos:

IMAGE: o modo para entradas de imagem única.

VIDEO: o modo para frames decodificados de um vídeo.

LIVE_STREAM: é o modo para uma transmissão ao vivo de dados de entrada, como de uma câmera. Nesse modo, o resultListener precisa ser chamado para configurar um listener para receber resultados de forma assíncrona.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
l2_normalize Define se o vetor de atributo retornado será normalizado com a norma L2. Use essa opção somente se o modelo ainda não tiver uma operação nativa do TFLite L2_NORMALIZATION. Na maioria dos casos, esse já é o caso, e a normalização L2 é, portanto, feita por meio da inferência TFLite sem precisar dessa opção. Boolean False
quantize Define se o embedding retornado precisa ser quantizado em bytes por meio da quantização escalar. Os embeddings são implicitamente considerados padrão de unidade e, portanto, qualquer dimensão tem um valor em [-1.0, 1.0]. Use a opção l2_normalize se esse não for o caso. Boolean False
resultListener Define o listener de resultados para receber os resultados de incorporação de forma assíncrona quando o incorporador de imagens está no modo de transmissão ao vivo. Só pode ser usado quando o modo de corrida está definido como LIVE_STREAM N/A Não definido
errorListener Define um listener de erro opcional. N/A Não definido

preparar dados

O Incorporador de imagens funciona com imagens, arquivos de vídeo e transmissões de vídeo ao vivo. A tarefa lida com o pré-processamento da entrada de dados, incluindo redimensionamento, rotação e normalização de valores.

É necessário converter a imagem ou o frame de entrada em um objeto com.google.mediapipe.framework.image.MPImage antes de transmiti-lo para a tarefa de incorporação de imagens.

Imagem

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load an image on the user’s device as a Bitmap object using BitmapFactory.

// Convert an Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(bitmap).build();
    

Video

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load a video file on the user's device using MediaMetadataRetriever

// From the video’s metadata, load the METADATA_KEY_DURATION and
// METADATA_KEY_VIDEO_FRAME_COUNT value. You’ll need them
// to calculate the timestamp of each frame later.

// Loop through the video and load each frame as a Bitmap object.

// Convert the Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(frame).build();
    

Transmissão ao vivo

import com.google.mediapipe.framework.image.MediaImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Create a CameraX’s ImageAnalysis to continuously receive frames
// from the device’s camera. Configure it to output frames in RGBA_8888
// format to match with what is required by the model.

// For each Android’s ImageProxy object received from the ImageAnalysis,
// extract the encapsulated Android’s Image object and convert it to
// a MediaPipe’s Image object.
android.media.Image mediaImage = imageProxy.getImage()
Image mpImage = new MediaImageBuilder(mediaImage).build();
    

No código de exemplo, a preparação de dados é processada no arquivo ImageEmbedderHelper.kt.

Executar a tarefa

É possível chamar a função embed correspondente ao modo de execução para acionar inferências. A API Image Embedder retorna os vetores de embedding para a imagem ou o frame de entrada.

Imagem

ImageEmbedderResult embedderResult = imageEmbedder.embed(image);
    

Video

// Calculate the timestamp in milliseconds of the current frame.
long frame_timestamp_ms = 1000 * video_duration * frame_index / frame_count;

// Run inference on the frame.
ImageEmbedderResult embedderResult =
    imageEmbedder.embedForVideo(image, frameTimestampMs);
    

Transmissão ao vivo


// Run inference on the frame. The embedding results will be available
// via the `resultListener` provided in the `ImageEmbedderOptions` when
// the image embedder was created.
imageEmbedder.embedAsync(image, frameTimestampMs);
    

Observe o seguinte:

  • Ao executar no modo de vídeo ou de transmissão ao vivo, também é necessário fornecer o carimbo de data/hora do frame de entrada para a tarefa de incorporação de imagens.
  • Quando executada no modo de imagem ou vídeo, a tarefa de incorporação de imagens bloqueia a linha de execução atual até que ela termine de processar a imagem ou o frame de entrada. Para evitar o bloqueio da linha de execução atual, execute o processamento em uma linha de execução em segundo plano.
  • Quando executada no modo de transmissão ao vivo, a tarefa de incorporação de imagens não bloqueia a linha de execução atual, mas retorna imediatamente. Ele vai invocar o listener de resultados com o resultado da detecção sempre que terminar de processar um frame de entrada. Se a função embedAsync for chamada quando a tarefa do incorporador de imagens estiver ocupada processando outro frame, a tarefa ignorará o novo frame de entrada.

No código de exemplo, a função embed é definida no arquivo ImageEmbedderHelper.kt.

Gerenciar e mostrar resultados

Ao executar a inferência, a tarefa do incorporador de imagens retorna um objeto ImageEmbedderResult que contém uma lista de embeddings (ponto flutuante ou quantizados escalares) para a imagem de entrada.

Veja a seguir um exemplo dos dados de saída dessa tarefa:

ImageEmbedderResult:
  Embedding #0 (sole embedding head):
    float_embedding: {0.0, 0.0, ..., 0.0, 1.0, 0.0, 0.0, 2.0}
    head_index: 0

Este resultado foi obtido com a incorporação da seguinte imagem:

Compare a semelhança de dois embeddings usando a função ImageEmbedder.cosineSimilarity. Confira um exemplo no código a seguir.

// Compute cosine similarity.
double similarity = ImageEmbedder.cosineSimilarity(
  result.embeddingResult().embeddings().get(0),
  otherResult.embeddingResult().embeddings().get(0));