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. Proporciona un framework para configurar canalizaciones de procesamiento compiladas previamente que generan resultados inmediatos, atractivos 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 ofrece MediaPipe Solutions.

En este codelab, comenzarás con una app para Android mayormente completa y, luego, avanzarás a través de 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 de manera 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 habilidad para ejecutar secuencias de comandos de Python predefinidas

2. Cómo agregar tareas de MediaPipe a la app para Android

Descarga la app de partida para Android

Este codelab comenzará con una muestra prediseñada que consta de la IU que se usará para una versión básica de generación de imágenes. Puedes encontrar esa app de inicio en el repositorio oficial de MediaPipe Samples aquí. Clona el repo o descarga el archivo ZIP haciendo clic en Código > Descargar 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 a la ubicación en la que clonaste o descargaste el repositorio y abre el directorio codelabs/image_generation_basic/android/start.
  2. En esta etapa, la app no debe compilar porque aún no incluiste la dependencia de MediaPipe Tasks.

Para corregir la app y ponerla en funcionamiento, ve al archivo build.gradle y desplázate hacia abajo hasta // Step 1 - Agrega dependencia. Desde allí, incluye la siguiente línea y, luego, presiona el botón Sync Now que aparece en el banner de 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 ejecutar ( 7e15a9c9e1620fe7.png) en la parte superior derecha de Android Studio para verificar que todo se haya abierto e instalado correctamente. Deberías ver la app abierta en una pantalla con dos botones de selección y un botón con la etiqueta INITIALIZE. Si haces clic en ese botón, se te debería redireccionar de inmediato a una IU separada que consta de una instrucción de texto y otras opciones junto a un botón con la etiqueta GENERAR.

83c31de8e8a320ee.png 78b8765e832024e3.png

Lamentablemente, esa es la extensión de la app de partida, por lo que es hora de que aprendas cómo terminar esta app y comenzar a generar imágenes nuevas en tu dispositivo.

3. Configura el generador de imágenes

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

Justo debajo de ese objeto, verás una función llamada initialImageGenerator() con el siguiente comentario: // Paso 2: inicializa el generador de imágenes. Como puedes suponer, 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(). Se aceptan tres parámetros: una cadena prompt que se usará para definir la imagen generada, la cantidad de iteraciones que la tarea debe atravesar mientras genera la imagen nueva y un valor seed que se puede usar para crear nuevas versiones de una imagen basadas en la misma instrucción y generar la misma imagen cuando se usa el mismo origen. 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 los pasos intermedios.

Reemplaza el cuerpo de setInput() (donde verás el comentario // Paso 3: Acepta las entradas) con esta línea:

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

En los próximos dos pasos, se lleva a cabo la generación. La función generate() acepta las mismas entradas que setInput, pero crea una imagen como una llamada única que no devuelve ninguna imagen de pasos intermedios. Puedes reemplazar el cuerpo de esta función (que incluye el comentario // Paso 4: generar sin mostrar iteraciones) 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. Aprenderás más al respecto más adelante en este codelab.

El último paso que realizarás en este archivo será completar la funciónExecute() (etiquetada como Paso 5). Esto aceptará un parámetro que le indique si debe mostrar una imagen intermedia o no para el paso único de generación que se realizará con la función running() 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

Eso es todo con respecto al archivo auxiliar. En la siguiente sección, completarás el archivo ViewModel que controla la lógica para este ejemplo.

4. Cómo integrar la aplicación

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 // Paso 6: Establece la ruta de acceso al modelo. Aquí es donde le indicarás a tu app dónde puede encontrar los archivos del modelo que son necesarios para la generación de imágenes. Para este ejemplo, establecerás el valor como /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 el paso 7 y el paso 8, que se usarán para generar imágenes con iteraciones devueltas o ninguna, respectivamente. A medida que ambas operaciones ocurren de forma síncrona, notarás que están unidas en una corrutina. Para comenzar, reemplaza el siguiente paso: // Paso 7: Generar sin mostrar iteraciones con este bloque de código para llamar a generate() desde el archivo ImageGenerationHelper y, luego, actualizar 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 se vuelve un poco más complicado. Debido a que la función run() 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. Ya puedes hacer todo esto.

// 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.

... salvo que ahora la app falla cuando intentas inicializar el generador de imágenes. Esto sucede porque necesitas copiar los archivos del modelo en tu dispositivo. Para obtener la información más actualizada sobre modelos de terceros conocidos, convertirlos para esta tarea de MediaPipe y copiarlos en tu dispositivo, consulta esta sección de la documentación oficial.

Además de copiar archivos directamente a 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 esto, deberías tener una app que funcione y que pueda aceptar solicitudes de texto y generar imágenes nuevas completamente en el dispositivo. Implementa la app en un dispositivo Android físico para probarla, aunque recuerda que querrás probarla con un dispositivo con 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 (finales o con iteraciones) y, luego, presiona el botón INITIALIZE.
  3. En la siguiente pantalla, configura las propiedades que desees y haz clic en el botón GENERAR para ver lo que ofrece la herramienta.

e46cfaeb9d3fc235.gif

6. ¡Felicitaciones!

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

Próximos pasos

Puedes realizar más acciones con la tarea de generación de imágenes, como

  • usando una imagen base para estructurar las imágenes generadas con complementos o entrenar tus propios pesos de LoRA adicionales con Vertex AI.
  • Usa Firebase Storage para recuperar archivos de modelos en tu dispositivo sin necesidad de usar la herramienta ADB.

Esperamos con ansias ver todos los aspectos geniales que lograrás con esta tarea experimental. Mantente alerta a los próximos codelabs y contenido del equipo de MediaPipe.