Generazione di immagini on-device su Android con MediaPipe

1. Introduzione

Che cos'è MediaPipe?

Soluzioni MediaPipe ti consente di applicare soluzioni di machine learning (ML) alle tue app. Fornisce un framework per la configurazione di pipeline di elaborazione predefinite che forniscono agli utenti output immediati, coinvolgenti e utili. Puoi anche personalizzare molte di queste soluzioni con Model Maker di MediaPipe per aggiornare i modelli predefiniti.

La generazione da testo a immagine è una delle diverse attività di ML che le soluzioni MediaPipe offrono.

In questo codelab, inizierai con un'app Android quasi nuda, quindi procederai in vari passaggi finché non sarai in grado di generare nuove immagini direttamente sul tuo dispositivo Android.

Obiettivi didattici

  • Come implementare la generazione da testo a immagine in esecuzione localmente in un'app per Android con Attività di MediaPipe.

Che cosa ti serve

  • Una versione installata di Android Studio (questo codelab è stato scritto e testato con Android Studio Giraffe).
  • Un dispositivo Android con almeno 8 GB di RAM.
  • Conoscenza di base dello sviluppo Android e capacità di eseguire uno script Python già scritto.

2. Aggiungi Attività MediaPipe all'app per Android

Scarica l'app iniziale per Android

Questo codelab inizierà con un esempio predefinito costituito dalla UI che verrà utilizzato per una versione di base della generazione di immagini. Puoi trovare l'app iniziale nel repository ufficiale degli esempi di MediaPipe qui. Clona il repository o scarica il file ZIP facendo clic su Codice > Scarica ZIP.

Importa l'app in Android Studio

  1. Apri Android Studio.
  2. Dalla schermata Ti diamo il benvenuto in Android Studio, seleziona Apri nell'angolo in alto a destra.

a0b5b070b802e4ea.png

  1. Passa alla posizione in cui hai clonato o scaricato il repository e apri la codelabs/image_generation_basic/android/start directory.
  2. In questa fase l'app non deve compilarsi perché non hai ancora incluso la dipendenza Attività di MediaPipe.

Per correggere l'app e metterla in esecuzione, vai al file build.gradle e scorri verso il basso fino a // Passaggio 1 - Aggiungi dipendenza. Includi la riga seguente e fai clic sul pulsante Sincronizza ora visualizzato nel banner nella parte superiore di Android Studio.

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

Una volta completata la sincronizzazione, verifica che tutti i contenuti siano aperti e installati correttamente facendo clic sulla freccia verde Esegui ( 7e15a9c9e1620fe7.png) nell'angolo in alto a destra di Android Studio. Dovresti vedere l'app aperta su una schermata con due pulsanti di opzione e un pulsante denominato INIZIALIZZA. Se fai clic sul pulsante, vieni immediatamente indirizzato a una UI separata composta da un prompt di testo e altre opzioni insieme a un pulsante con l'etichetta GENERA.

83c31de8e8a320ee.png 78b8765e832024e3.png

Purtroppo questa è la portata dell'app iniziale, quindi è il momento di imparare come finirai l'app e inizierai a generare nuove immagini sul tuo dispositivo.

3. Configurazione del generatore di immagini

Per questo esempio, la maggior parte del lavoro di generazione delle immagini verrà svolta nel file ImageGenerationHelper.kt. Quando apri questo file, noterai una variabile nella parte superiore della classe chiamata imageGenerator. Questo è l'oggetto Tasks che farà il resto nell'app di generazione delle immagini.

Appena sotto l'oggetto c'è una funzione inizializzata dal generatore di immagini con il seguente commento: // Step 2 - inizializza il generatore di immagini. Come puoi intuire, è qui che inizializzerai l'oggetto ImageGenerator. Sostituisci il corpo della funzione con il seguente codice per impostare il percorso del modello di generazione delle immagini e inizializzare l'oggetto ImageGenerator:

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

imageGenerator = ImageGenerator.createFromOptions(context, options)

Sotto c'è un'altra funzione denominata setInput(). Questo accetta tre parametri: una stringa prompt che verrà utilizzata per definire l'immagine generata, il numero di iterazioni che l'attività deve superare durante la generazione della nuova immagine e un valore seed che può essere utilizzato per creare nuove versioni di un'immagine in base allo stesso prompt generando allo stesso tempo la stessa immagine quando viene utilizzato lo stesso seed. Questa funzione ha lo scopo di impostare questi parametri iniziali per il generatore di immagini quando tenti di creare un'immagine che mostra passaggi intermedi.

Sostituisci il corpo del setInput() (dove vedrai il commento // Step 3 - accept inputs) con questa riga:

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

Nei prossimi due passaggi avviene la generazione. La funzione generate() accetta gli stessi input di setInput, ma crea un'immagine come chiamata one-shot che non restituisce immagini di passaggi intermedi. Puoi sostituire il corpo di questa funzione (che include il commento // Passaggio 4 - genera senza mostrare le iterazioni) con quanto segue:

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

È importante sapere che questa attività viene eseguita in modo sincrono, quindi dovrai chiamare la funzione da un thread in background. Scoprirai di più su questo argomento più avanti in questo codelab.

L'ultimo passaggio da eseguire in questo file è compilare la funzione Esegui() (etichettata come Passaggio 5). In questo modo viene accettato un parametro che indica se deve restituire o meno un'immagine intermedia per il singolo passaggio di generazione che verrà eseguito con la funzione ImageGenerator. Sostituisci il corpo della funzione con questo codice:

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

Questo è tutto per il file helper. Nella sezione successiva compilerai il file ViewModel che gestisce la logica per questo esempio.

4. Unire le app

Il file MainViewModel gestirà gli stati dell'interfaccia utente e l'altra logica relativa a questa app di esempio. Procedi e aprilo ora.

Verso la parte superiore del file dovresti vedere il commento // Passaggio 6 - Imposta percorso del modello. Questa scheda consente di indicare alla tua app dove può trovare i file del modello necessari per la generazione delle immagini. Per questo esempio imposterai il valore su /data/local/tmp/image_generator/bins/.

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

Da qui, scorri verso il basso fino alla funzione generateImage(). Verso la parte inferiore di questa funzione vedrai sia il passaggio 7 che il passaggio 8, che verranno utilizzati per generare immagini rispettivamente con iterazioni restituite o nessuna. Poiché entrambe queste operazioni avvengono in modo sincrono, noterai che sono avvolte in una coroutine. Puoi iniziare sostituendo // Step 7 - Genera senza mostrare iterazioni con questo blocco di codice per chiamare generate() dal file ImageGenerationHelper, quindi aggiorna lo stato dell'interfaccia utente.

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

Il passaggio 8 diventa un po' più complicato. Poiché la funzione eseguire() esegue solo un passaggio anziché tutti i passaggi per la generazione dell'immagine, dovrai richiamare ogni passaggio singolarmente attraverso un loop. Dovrai anche determinare se il passaggio corrente deve essere visualizzato dall'utente. Infine, devi aggiornare lo stato della UI se deve essere visualizzata l'iterazione corrente. Ora puoi fare tutto questo.

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

A questo punto dovresti essere in grado di installare l'app, inizializzare il generatore di immagini e quindi creare una nuova immagine basata su un prompt di testo

... tranne che adesso, quando cerchi di inizializzare il generatore di immagini, l'app ha un arresto anomalo. Il motivo è che devi copiare i file del modello sul tuo dispositivo. Per ottenere le informazioni più aggiornate sui modelli di terze parti noti, convertendoli per questa attività MediaPipe e copiandoli sul tuo dispositivo, puoi consultare questa sezione della documentazione ufficiale.

Oltre a copiare i file direttamente sul dispositivo di sviluppo, puoi anche configurare Firebase Storage per scaricare i file necessari direttamente sul dispositivo dell'utente in fase di esecuzione.

5. Esegui il deployment e il test dell'app

Dopodiché, dovresti avere un'app funzionante in grado di accettare un prompt di testo e generare nuove immagini interamente sul dispositivo. Esegui il deployment dell'app su un dispositivo Android fisico per testarla, ma ricorda che ti consigliamo di provarla con un dispositivo con almeno 8 GB di memoria.

  1. Per eseguire l'app, fai clic su Esegui ( 7e15a9c9e1620fe7.png) nella barra degli strumenti di Android Studio.
  2. Seleziona il tipo di passaggi di generazione (finale o con iterazioni), quindi premi il pulsante INIZIA.
  3. Nella schermata successiva, imposta le proprietà desiderate e fai clic sul pulsante GENERA per vedere i risultati dello strumento.

e46cfaeb9d3fc235.gif

6. Complimenti

Ce l'hai fatta! In questo codelab hai imparato come aggiungere la generazione da testo a immagine sul dispositivo a un'app per Android.

Passaggi successivi

Puoi fare molto di più con l'attività di generazione di immagini, ad esempio:

  • utilizzare un'immagine di base per strutturare le immagini generate tramite plug-in o addestrare i tuoi pesi LoRA aggiuntivi tramite Vertex AI.
  • Utilizza Firebase Storage per recuperare i file del modello sul tuo dispositivo senza richiedere l'uso dello strumento ADB.

Non vediamo l'ora di scoprire tutti i vantaggi che potrai realizzare con questa attività sperimentale e tieni d'occhio altri codelab e contenuti dal team di MediaPipe.