Generación de imágenes en el dispositivo en Android con MediaPipe

1. Introducción

¿Qué es MediaPipe?

Las MediaPipe Solutions te permiten aplicar soluciones de aprendizaje automático (AA) en tus apps. Proporcionan un framework para que configures canalizaciones de procesamiento compiladas previamente que arrojan resultados inmediatos, pertinentes y útiles para los usuarios. Incluso puedes personalizar muchas de estas soluciones con MediaPipe Model Maker para actualizar los modelos predeterminados.

La generación de texto a imagen es una de las muchas tareas de AA que ofrecen las MediaPipe Solutions.

En este codelab, comenzarás con una app para Android casi vacía y, luego, avanzarás por varios pasos hasta que puedas generar imágenes nuevas directamente en tu dispositivo Android.

Qué aprenderás

  • Cómo implementar la generación de texto a imagen que se ejecuta de forma local en una app para Android con MediaPipe Tasks.

Requisitos

  • Una versión instalada de Android Studio (este codelab se escribió y probó con Android Studio Giraffe)
  • Un dispositivo Android con al menos 8 GB de RAM
  • Conocimientos básicos sobre el desarrollo de Android y la capacidad de ejecutar una secuencia de comandos de Python escrita previamente

2. Agrega MediaPipe Tasks a la app para Android

Descarga la app de inicio para Android

Este codelab comenzará con una muestra precompilada que consta de la IU que se usará para una versión básica de la generación de imágenes. Puedes encontrar esa app de inicio en el repositorio oficial de MediaPipe Samples aquí. Para clonar el repositorio o descargar el archivo zip, haz clic en Code > Download ZIP.

Importa la app a Android Studio

  1. Abre Android Studio.
  2. En la pantalla Welcome to Android Studio, selecciona Open en la esquina superior derecha.

a0b5b070b802e4ea.png

  1. Navega hasta donde clonaste o descargaste el repositorio y abre el directorio codelabs/image_generation_basic/android/start.
  2. En esta etapa, la app no debería compilarse porque aún no incluiste la dependencia de MediaPipe Tasks.

Para corregir la app y hacer que se ejecute, ve al archivo build.gradle y desplázate hacia abajo hasta // Step 1 - Add dependency. Desde allí, incluye la siguiente línea y, luego, presiona el botón Sync Now que aparece en el banner en la parte superior de Android Studio.

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

Una vez que se complete la sincronización, haz clic en la flecha verde run ( 7e15a9c9e1620fe7.png) en la parte superior derecha de Android Studio para verificar que todo se haya abierto e instalado correctamente. Deberías ver que la app se abre en una pantalla con dos botones de selección y un botón etiquetado como INITIALIZE. Si haces clic en ese botón, deberías ir de inmediato a una IU independiente que consta de una instrucción de texto y otras opciones junto con un botón etiquetado como GENERATE.

83c31de8e8a320ee.png 78b8765e832024e3.png

Lamentablemente, eso es todo lo que ofrece la app de inicio, por lo que es hora de que aprendas a terminarla y comiences a generar imágenes nuevas en tu dispositivo.

3. Configura el generador de imágenes

En este ejemplo, la mayor parte del trabajo de generación de imágenes se realizará en el archivo ImageGenerationHelper.kt. Cuando abras este archivo, notarás una variable en la parte superior de la clase llamada imageGenerator. Este es el objeto Task que hará el trabajo pesado en tu app de generación de imágenes.

Justo debajo de ese objeto, verás una función llamada initializeImageGenerator() con el siguiente comentario: // Step 2 - initialize the image generator. Como puedes imaginar, aquí es donde inicializarás el objeto ImageGenerator. Reemplaza el cuerpo de esa función por el siguiente código para establecer la ruta del modelo de generación de imágenes y, luego, inicializar el objeto ImageGenerator:

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

imageGenerator = ImageGenerator.createFromOptions(context, options)

Debajo, verás otra función llamada setInput(). Esta acepta tres parámetros: una cadena prompt que se usará para definir la imagen generada, la cantidad de iterations que debe realizar la tarea mientras genera la imagen nueva y un valor seed que se puede usar para crear versiones nuevas de una imagen basada en la misma instrucción mientras se genera la misma imagen cuando se usa la misma semilla. El propósito de esta función es establecer estos parámetros iniciales para el generador de imágenes cuando intentas crear una imagen que muestra pasos intermedios.

Continúa y reemplaza el cuerpo de setInput() (donde verás el comentario // Step 3 - accept inputs) por esta línea:

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

En los siguientes dos pasos, se realiza la generación. La función generate() acepta las mismas entradas que setInput, pero crea una imagen como una llamada única que no muestra ninguna imagen de paso intermedio. Puedes reemplazar el cuerpo de esta función (que incluye el comentario // Step 4 - generate without showing iterations) por lo siguiente:

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

Es importante saber que esta tarea se realiza de forma síncrona, por lo que deberás llamar a la función desde un subproceso en segundo plano. Obtendrás más información sobre eso un poco más adelante en este codelab.

El último paso que realizarás en este archivo es completar la función execute() (etiquetada como Step 5). Esta aceptará un parámetro que le indicará si debe mostrar una imagen intermedia o no para el único paso de generación que se realizará con la función execute() de ImageGenerator. Reemplaza el cuerpo de la función 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

Y eso es todo para el archivo de ayuda. En la siguiente sección, completarás el archivo ViewModel que controla la lógica de este ejemplo.

4. Reúne la app

El archivo MainViewModel controlará los estados de la IU y otra lógica relacionada con esta app de ejemplo. Ábrelo ahora.

En la parte superior del archivo, deberías ver el comentario // Step 6 - set model path. Aquí es donde le indicarás a tu app dónde puede encontrar los archivos de modelo necesarios para la generación de imágenes. En este ejemplo, establecerás el valor en /data/local/tmp/image_generator/bins/.

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

Desde allí, desplázate hacia abajo hasta la función generateImage(). En la parte inferior de esta función, verás los pasos 7 y 8, que se usarán para generar imágenes con iteraciones mostradas o sin ellas, respectivamente. Como ambas operaciones se realizan de forma síncrona, notarás que están envueltas en una corrutina. Para comenzar, reemplaza // Step 7 - Generate without showing iterations por este bloque de código para llamar a generate() desde el archivo ImageGenerationHelper y, luego, actualiza el estado de la IU.

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

El paso 8 es un poco más complicado. Debido a que la función execute() solo realiza un paso en lugar de todos los pasos para la generación de imágenes, deberás llamar a cada paso de forma individual a través de un bucle. También deberás determinar si se debe mostrar el paso actual al usuario. Por último, actualizarás el estado de la IU si se debe mostrar la iteración actual. Puedes hacer todo esto ahora.

// 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)",
            )
        }
    }
}

En este punto, deberías poder instalar tu app, inicializar el generador de imágenes y, luego, crear una imagen nueva basada en una instrucción de texto.

… excepto que ahora la app falla cuando intentas inicializar el generador de imágenes. El motivo por el que sucede esto es que debes copiar los archivos de modelo en tu dispositivo. Para obtener la información más actualizada sobre los modelos de terceros que funcionan, convertirlos para esta tarea de MediaPipe y copiarlos en tu dispositivo, puedes revisar esta sección de la documentación oficial.

Además de copiar archivos directamente en tu dispositivo de desarrollo, también es posible configurar Firebase Storage para descargar los archivos necesarios directamente en el dispositivo del usuario en el tiempo de ejecución.

5. Implementa y prueba la app

Después de todo eso, deberías tener una app que funcione y que pueda aceptar una instrucción de texto y generar imágenes nuevas por completo en el dispositivo. Continúa y, luego, implementa la app en un dispositivo Android físico para probarla, pero recuerda que querrás probarla con un dispositivo que tenga al menos 8 GB de memoria.

  1. Haz clic en Run ( 7e15a9c9e1620fe7.png) en la barra de herramientas de Android Studio para ejecutar la app.
  2. Selecciona el tipo de pasos de generación (final o con iteraciones) y, luego, presiona el botón INITIALIZE.
  3. En la siguiente pantalla, establece las propiedades que desees y haz clic en el botón GENERATE para ver qué genera la herramienta.

e46cfaeb9d3fc235.gif

6. ¡Felicitaciones!

¡Lo lograste! En este codelab, aprendiste a agregar la generación de texto a imagen en el dispositivo a una app para Android.

Próximos pasos

Hay más cosas que puedes hacer con la tarea de generación de imágenes, incluidas las siguientes:

  • Usar una imagen base para estructurar imágenes generadas a través de complementos o entrenar tus propios pesos de LoRA adicionales a través de Vertex AI
  • Usar Firebase Storage para recuperar archivos de modelo en tu dispositivo sin requerir el uso de la herramienta ADB

Esperamos ver todas las cosas interesantes que creas con esta tarea experimental y estate atento a más codelabs y contenido del equipo de MediaPipe.