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 resultados imediatos, envolventes e úteis aos usuários. É possível até 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 praticamente básico e seguir para várias etapas até conseguir gerar novas imagens diretamente no 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 o MediaPipe Tasks ao app Android

Baixar o app Android para iniciantes

Este codelab começa com um exemplo pré-criado que consiste na interface que será usada para uma versão básica de geração de imagens. Você pode encontrar esse app inicial no repositório oficial de exemplos do MediaPipe aqui. Clone o repositório ou faça o download do arquivo ZIP clicando em "Código" > Baixe o 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 onde você clonou ou fez o download do repositório e abra o codelabs/image_generation_basic/android/start directory.
  2. Nesta fase, o app não pode ser compilado porque você ainda não incluiu a dependência do MediaPipe Tasks.

Para corrigir o app e executá-lo, acesse o arquivo build.gradle e role para baixo até // Step 1 - Add regions. Depois, inclua a linha abaixo e pressione o 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, verifique se tudo abriu e instalou corretamente clicando na seta verde run ( 7e15a9c9e1620fe7.png) no canto superior direito do Android Studio. Você verá o app aberto em uma tela com dois botões de opção e um botão chamado INITIALIZE. Ao clicar nesse botão, uma interface separada consiste em um comando de texto e outras opções, além do botão GERAR.

83c31de8e8a320ee.png 78b8765e832024e3.png

Infelizmente, essa é a extensão do app inicial, então é hora de você aprender como concluir o app e começar a gerar novas imagens no dispositivo.

3. Como configurar o gerador de imagens

Neste exemplo, a maior parte do trabalho de geração de imagem 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 fará o trabalho pesado no app de geração de imagens.

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

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

imageGenerator = ImageGenerator.createFromOptions(context, options)

Abaixo disso, você verá outra função chamada setInput(). Ele aceita três parâmetros: uma string prompt que vai 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 seed que pode ser usado para criar novas versões de uma imagem com base no mesmo comando e gerar a mesma imagem quando a mesma sugestão é usada. O objetivo dessa função é definir esses parâmetros iniciais para o gerador de imagens quando você tenta criar uma imagem que exibe etapas intermediárias.

Substitua o corpo setInput() (onde você vai ver o comentário // Step 3 - Accept Inputs) por esta linha:

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

A geração é realizada nas próximas duas etapas. A função generate() aceita as mesmas entradas que setInput, mas cria uma imagem como uma chamada única que não retorna nenhuma imagem de etapa intermediária. 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 maneira síncrona. Portanto, você precisará chamar a função em uma linha de execução em segundo plano. Você aprenderá mais sobre isso posteriormente neste codelab.

A etapa final a ser executada nesse arquivo é preencher a função execute() (identificada como Etapa 5). Isso vai aceitar um parâmetro que informa se deve retornar uma imagem intermediária ou não para a única etapa de geração que será realizada com a função ImageGenerator execute(). 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

Isso é tudo sobre o arquivo auxiliar. Na próxima seção, você preencherá o arquivo ViewModel que processa a lógica deste exemplo.

4. Como reunir o app

O arquivo MainViewModel processará os estados da IU e outras lógicas relacionadas a este app de exemplo. Abra o app agora mesmo.

Na parte superior do arquivo, você verá o comentário // Step 6 - set model path. É aqui que você informará ao app onde ele pode encontrar os arquivos de modelo necessários para a geração de imagens. Neste exemplo, você 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/"

A partir daí, role para baixo até a função generateImage(). Na parte inferior dessa função, você verá as etapas 7 e 8, que serão usadas para gerar imagens com iterações retornadas ou nenhuma, respectivamente. Como essas duas operações ocorrem de forma síncrona, você vai perceber que elas estão agrupadas em uma corrotina. Você pode começar substituindo a // Etapa 7: gerar sem mostrar iterações com 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 fica um pouco mais complicada. Como a função execute() só executa uma etapa em vez de todas as etapas para a geração de imagens, você precisará chamar cada etapa individualmente usando uma repetição. Você também precisará determinar se a etapa atual será exibida para o usuário. Por fim, você atualizará o estado da interface se a iteração atual for exibida. 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 instalar o app, inicializar o gerador de imagens e criar uma nova imagem com base em um comando de texto.

... mas agora o app falha ao tentar inicializar o gerador de imagens. O motivo disso é que você precisa copiar os arquivos do modelo para seu dispositivo. Para acessar as informações mais atualizadas sobre modelos de terceiros conhecidos, convertê-los para esta 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 ambiente de execução.

5. Implantar e testar o app

Depois de tudo isso, você precisa ter um app em funcionamento que possa aceitar um comando de texto e gerar novas imagens totalmente no dispositivo. Implante o aplicativo em um dispositivo Android físico para testá-lo, mas lembre-se de tentar fazer isso com um dispositivo com pelo menos 8 GB de memória.

  1. Clique em Run ( 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 INICIAR.
  3. Na tela seguinte, defina as propriedades que quiser e clique no botão GERAR para ver o resultado da ferramenta.

e46cfaeb9d3fc235.gif

6. Parabéns!

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

Próximas etapas

Há muito mais que você pode fazer com a tarefa de geração de imagens, incluindo

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

Estamos ansiosos para ver todas as coisas legais que você vai criar com essa tarefa experimental e fique de olho nos próximos codelabs e conteúdos da equipe do MediaPipe.