Geração de imagens no dispositivo no Android com MediaPipe

1. Introdução

O que é o MediaPipe?

Com o MediaPipe Solutions, é possível aplicar soluções de machine learning (ML) nos apps. Ele oferece um framework para configurar pipelines de processamento pré-criados que entregam saídas imediatas, interativas e úteis para os usuários. Você pode até mesmo personalizar muitas dessas soluções com o MediaPipe Model Maker para atualizar os modelos padrão.

A geração de texto para imagem é uma das várias tarefas de ML que o MediaPipe Solutions oferece.

Neste codelab, você vai começar com um app Android quase vazio e, em seguida, passar por várias etapas até conseguir gerar novas imagens diretamente no seu dispositivo Android.

O que você vai aprender

  • Como implementar a geração de texto para imagem em execução localmente em um app Android com o MediaPipe Tasks.

O que é necessário

  • Uma versão instalada do Android Studio (este codelab foi escrito e testado com o Android Studio Giraffe).
  • Um dispositivo Android com pelo menos 8 GB de RAM.
  • Conhecimento básico de desenvolvimento para Android e capacidade de executar um script Python pré-escrito.

2. Adicionar as tarefas do MediaPipe ao app Android

Fazer o download do app inicial do Android

Este codelab vai começar com um exemplo pré-criado que consiste na interface que será usada para uma versão básica de geração de imagens. Confira o app de inicialização no repositório oficial de amostras do MediaPipe aqui. Clone o repositório ou faça o download do arquivo ZIP clicando em "Code" > "Download ZIP".

Importar o app para o Android Studio

  1. Abra o Android Studio.
  2. Na tela Welcome to Android Studio, selecione Open no canto superior direito.

a0b5b070b802e4ea.png

  1. Navegue até o local em que você clonou ou fez o download do repositório e abra o diretório codelabs/image_generation_basic/android/start.
  2. Nesta etapa, o app não precisa ser compilado porque você ainda não incluiu a dependência do MediaPipe Tasks.

Para corrigir o app e fazer com que ele seja executado, acesse o arquivo build.gradle e role para baixo até // Etapa 1: adicionar dependência. Em seguida, inclua a linha a seguir e clique no botão Sync Now que aparece no banner na parte de cima do Android Studio.

// Step 1 - Add dependency
implementation 'com.google.mediapipe:tasks-vision-image-generator:latest.release'

Depois que a sincronização for concluída, clique na seta verde run ( 7e15a9c9e1620fe7.png) no canto superior direito do Android Studio para verificar se tudo foi aberto e instalado corretamente. O app vai abrir uma tela com dois botões de opção e um botão INITIALIZE. Se você clicar nesse botão, será direcionado imediatamente para uma interface separada, que consiste em um comando de texto e outras opções, além de um botão com a etiqueta GERAR.

83c31de8e8a320ee.png 78b8765e832024e3.png

Infelizmente, esse é o tamanho do app inicial. É hora de aprender a finalizar esse app e começar a gerar novas imagens no seu dispositivo.

3. Como configurar o Gerador de imagens

Neste exemplo, a maior parte do trabalho de geração de imagens vai acontecer no arquivo ImageGenerationHelper.kt. Ao abrir esse arquivo, você vai notar uma variável na parte de cima da classe chamada imageGenerator. Esse é o objeto Task que vai fazer o trabalho pesado no app de geração de imagens.

Logo abaixo desse objeto, você vai encontrar uma função chamada initializeImageGenerator() com o seguinte comentário: // Etapa 2: inicializar o gerador de imagens. Como você pode imaginar, é aqui que você vai inicializar o objeto ImageGenerator. Substitua o corpo da função pelo código abaixo para definir o caminho do modelo de geração de imagens e inicializar o objeto ImageGenerator:

// Step 2 - initialize the image generator
val options = ImageGeneratorOptions.builder()
    .setImageGeneratorModelDirectory(modelPath)
    .build()

imageGenerator = ImageGenerator.createFromOptions(context, options)

Abaixo dela, você vai encontrar outra função chamada setInput(). Ela aceita três parâmetros: uma string de instrução que será usada para definir a imagem gerada, o número de iterações que a tarefa precisa passar ao gerar a nova imagem e um valor de semente que pode ser usado para criar novas versões de uma imagem com base na mesma instrução ao gerar a mesma imagem quando a mesma semente é usada. O objetivo dessa função é definir esses parâmetros iniciais para o gerador de imagens quando você tenta criar uma imagem que não mostra etapas intermediárias.

Substitua o corpo da função setInput() (onde você vai encontrar o comentário // Etapa 3 - aceitar entradas) por esta linha:

// Step 3 - accept inputs
imageGenerator.setInputs(prompt, iteration, seed)

As duas próximas etapas são onde a geração ocorre. A função generate() aceita as mesmas entradas que setInput, mas cria uma imagem como uma chamada única que não retorna imagens de etapas intermediárias. Você pode substituir o corpo dessa função (que inclui o comentário // Etapa 4: gerar sem mostrar iterações) pelo seguinte:

// Step 4 - generate without showing iterations
val result = imageGenerator.generate(prompt, iteration, seed)
val bitmap = BitmapExtractor.extract(result?.generatedImage())
return bitmap

É importante saber que essa tarefa acontece de forma síncrona. Portanto, você vai precisar chamar a função em uma linha de execução em segundo plano. Você vai aprender mais sobre isso mais adiante neste codelab.

A etapa final que você vai realizar neste arquivo é preencher a função execute() (rotulada como Etapa 5). Isso vai aceitar um parâmetro que informa se ele deve retornar uma imagem intermediária ou não para a única etapa de geração que será realizada com a função execute() do ImageGenerator. Substitua o corpo da função por este código:

// Step 5 - generate with iterations
val result = imageGenerator.execute(showResult)

if (result == null || result.generatedImage() == null) {
    return Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888)
        .apply {
            val canvas = Canvas(this)
            val paint = Paint()
            paint.color = Color.WHITE
            canvas.drawPaint(paint)
        }
}

val bitmap =
    BitmapExtractor.extract(result.generatedImage())

return bitmap

E é isso para o arquivo auxiliar. Na próxima seção, você vai preencher o arquivo ViewModel que processa a lógica para este exemplo.

4. Como criar um app

O arquivo MainViewModel vai processar estados da interface e outras lógicas relacionadas a este app de exemplo. Abra-o agora.

Na parte de cima do arquivo, você vai encontrar o comentário // Etapa 6: definir o caminho do modelo. É aqui que você informa ao app onde ele pode encontrar os arquivos de modelo necessários para a geração de imagens. Neste exemplo, você vai definir o valor como /data/local/tmp/image_generator/bins/.

// Step 6 - set model path
private val MODEL_PATH = "/data/local/tmp/image_generator/bins/"

Role a tela para baixo até a função generateImage(). Na parte de baixo da função, você vai encontrar as etapas 7 e 8, que serão usadas para gerar imagens com iterações retornadas ou nenhuma, respectivamente. Como essas duas operações acontecem simultaneamente, elas são agrupadas em uma corrotina. Comece substituindo // Etapa 7: gerar sem mostrar iterações por este bloco de código para chamar generate() do arquivo ImageGenerationHelper e atualizar o estado da interface.

// Step 7 - Generate without showing iterations
val result = helper?.generate(prompt, iteration, seed)
_uiState.update {
    it.copy(outputBitmap = result)
}

A etapa 8 é um pouco mais complicada. Como a função execute() executa apenas uma etapa em vez de todas as etapas para a geração de imagens, é necessário chamar cada etapa individualmente em um loop. Você também precisa determinar se a etapa atual precisa ser mostrada ao usuário. Por fim, você vai atualizar o estado da interface se a iteração atual precisar ser mostrada. Você pode fazer tudo isso agora.

// Step 8 - Generate with showing iterations
helper?.setInput(prompt, iteration, seed)
for (step in 0 until iteration) {
    isDisplayStep =
        (displayIteration > 0 && ((step + 1) % displayIteration == 0))
    val result = helper?.execute(isDisplayStep)

    if (isDisplayStep) {
        _uiState.update {
            it.copy(
                outputBitmap = result,
                generatingMessage = "Generating... (${step + 1}/$iteration)",
            )
        }
    }
}

Neste ponto, você deve conseguir instalar o app, inicializar o gerador de imagens e criar uma nova imagem com base em um comando de texto.

... exceto que agora o app falha quando você tenta inicializar o gerador de imagens. Isso acontece porque você precisa copiar os arquivos de modelo para o dispositivo. Para conferir as informações mais recentes sobre modelos de terceiros que funcionam, convertê-los para essa tarefa do MediaPipe e copiá-los para seu dispositivo, consulte esta seção da documentação oficial.

Além de copiar arquivos diretamente para o dispositivo de desenvolvimento, também é possível configurar o Firebase Storage para fazer o download dos arquivos necessários diretamente para o dispositivo do usuário no momento da execução.

5. Implantar e testar o app

Depois disso, você terá um app que aceita um comando de texto e gera novas imagens totalmente no dispositivo. Implante o app em um dispositivo Android físico para testar. No entanto, tente fazer isso com um dispositivo com pelo menos 8 GB de memória.

  1. Clique em Executar ( 7e15a9c9e1620fe7.png) na barra de ferramentas do Android Studio para executar o app.
  2. Selecione o tipo de etapas de geração (final ou com iterações) e pressione o botão Inicializar.
  3. Na próxima tela, defina as propriedades que você quiser e clique no botão GERAR para conferir o que a ferramenta vai mostrar.

e46cfaeb9d3fc235.gif

6. Parabéns!

Parabéns! Neste codelab, você aprendeu a adicionar a geração de texto para imagem no dispositivo a um app Android.

Próximas etapas

Você pode fazer mais coisas com a tarefa de geração de imagens, incluindo:

  • usando uma imagem base para estruturar imagens geradas por meio de plug-ins ou treinando seus próprios pesos LoRA adicionais com a Vertex AI.
  • Use o Armazenamento do Firebase para recuperar arquivos de modelo no seu dispositivo sem precisar usar a ferramenta ADB.

Estamos ansiosos para ver as coisas legais que você vai criar com essa tarefa experimental. Fique de olho em mais codelabs e conteúdo da equipe do MediaPipe.