Génération d'images sur l'appareil sur Android avec MediaPipe

1. Introduction

Qu'est-ce que MediaPipe ?

MediaPipe Solutions vous permet d'appliquer des solutions de machine learning (ML) à vos applications. Il fournit un framework permettant de configurer des pipelines de traitement prédéfinis qui fournissent aux utilisateurs une sortie immédiate, attrayante et utile. Vous pouvez même personnaliser bon nombre de ces solutions avec MediaPipe Model Maker afin de modifier les modèles par défaut.

La génération de texte en image est l'une des nombreuses tâches de ML proposées par MediaPipe Solutions.

Dans cet atelier de programmation, vous commencerez avec une application Android presque vide, puis vous passerez par plusieurs étapes jusqu'à pouvoir générer de nouvelles images directement sur votre appareil Android.

Points abordés

  • Implémenter la génération de texte vers image en cours d'exécution en local dans une application Android avec MediaPipe Tasks

Prérequis

  • Une version installée d'Android Studio (cet atelier de programmation a été écrit et testé avec Android Studio Giraffe).
  • Un appareil Android avec au moins 8 Go de RAM
  • Connaissances de base sur le développement Android et capacité à exécuter un script Python préécrit

2. Ajouter MediaPipe Tasks à l'application Android

Télécharger l'application de démarrage Android

Cet atelier de programmation commence par un exemple prédéfini composé de l'UI qui sera utilisée pour une version de base de la génération d'images. Vous trouverez cette application de démarrage dans le dépôt officiel des exemples MediaPipe ici. Clonez le dépôt ou téléchargez le fichier ZIP en cliquant sur Code > Télécharger le fichier ZIP.

Importer l'application dans Android Studio

  1. Ouvrez Android Studio.
  2. Sur l'écran Welcome to Android Studio (Bienvenue dans Android Studio), sélectionnez Open (Ouvrir) en haut à droite.

a0b5b070b802e4ea.png

  1. Accédez à l'emplacement où vous avez cloné ou téléchargé le dépôt, puis ouvrez le répertoire codelabs/image_generation_basic/android/start.
  2. À ce stade, l'application ne doit pas compiler, car vous n'avez pas encore inclus la dépendance MediaPipe Tasks.

Pour corriger l'application et la faire fonctionner, accédez au fichier build.gradle et faites défiler la page jusqu'à // Étape 1 : Ajouter une dépendance. Incluez ensuite la ligne suivante, puis cliquez sur le bouton Sync now (Synchroniser maintenant) qui s'affiche dans la bannière en haut d'Android Studio.

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

Une fois la synchronisation terminée, vérifiez que tout s'est bien ouvert et installé en cliquant sur la flèche verte Run ( Exécuter) 7e15a9c9e1620fe7.png en haut à droite d'Android Studio. L'application devrait s'ouvrir sur un écran avec deux boutons radio et un bouton intitulé INITIALIZE (INITIALISER). Si vous cliquez sur ce bouton, vous devriez être immédiatement redirigé vers une UI distincte composée d'une requête de texte et d'autres options, ainsi que d'un bouton intitulé GENERER.

83c31de8e8a320ee.png 78b8765e832024e3.png

Malheureusement, c'est à peu près tout ce que vous pouvez faire avec l'application de démarrage. Il est donc temps d'apprendre à terminer cette application et à commencer à générer de nouvelles images sur votre appareil.

3. Configurer le générateur d'images

Pour cet exemple, la majeure partie du travail de génération d'images se déroulera dans le fichier ImageGenerationHelper.kt. Lorsque vous ouvrez ce fichier, vous remarquerez une variable en haut de la classe appelée imageGenerator. C'est l'objet Task qui effectuera la tâche principale dans votre application de génération d'images.

Juste en dessous de cet objet, vous trouverez une fonction appelée initializeImageGenerator() avec le commentaire suivant : // Étape 2 : Initialisez le générateur d'images. Comme vous pouvez le deviner, c'est là que vous allez initialiser l'objet ImageGenerator. Remplacez le corps de cette fonction par le code suivant pour définir le chemin d'accès au modèle de génération d'images et initialiser l'objet ImageGenerator:

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

imageGenerator = ImageGenerator.createFromOptions(context, options)

Vous trouverez ensuite une autre fonction appelée setInput(). Elle accepte trois paramètres: une chaîne de requête qui servira à définir l'image générée, le nombre d'itérations que la tâche doit effectuer lors de la génération de la nouvelle image et une valeur de semente qui peut être utilisée pour créer de nouvelles versions d'une image basées sur la même requête tout en générant la même image lorsque le même sésame est utilisé. L'objectif de cette fonction est de définir ces paramètres initiaux pour le générateur d'images lorsque vous essayez de créer une image qui affiche des étapes intermédiaires.

Remplacez le corps de setInput() (où vous trouverez le commentaire // Étape 3 : accepter les entrées) par cette ligne :

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

Les deux étapes suivantes correspondent à la génération. La fonction generate() accepte les mêmes entrées que setInput, mais crée une image en tant qu'appel unique qui ne renvoie aucune image d'étape intermédiaire. Vous pouvez remplacer le corps de cette fonction (qui inclut le commentaire // Étape 4 : générer sans afficher les itérations) par le code suivant :

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

Il est important de savoir que cette tâche se produit de manière synchrone. Vous devrez donc appeler la fonction à partir d'un thread en arrière-plan. Vous en apprendrez davantage à ce sujet un peu plus tard dans cet atelier de programmation.

La dernière étape de ce fichier consiste à remplir la fonction execute() (étiquetée "Étape 5"). Elle accepte un paramètre qui lui indique si elle doit renvoyer une image intermédiaire ou non pour la seule étape de génération qui sera effectuée avec la fonction execute() de ImageGenerator. Remplacez le corps de la fonction par le code suivant:

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

C'est tout pour le fichier d'assistance. Dans la section suivante, vous allez remplir le fichier ViewModel qui gère la logique de cet exemple.

4. Rassembler l'application

Le fichier MainViewModel gérera les états de l'UI et d'autres logiques liées à cet exemple d'application. N'hésitez pas à l'ouvrir maintenant.

En haut du fichier, vous devriez voir le commentaire // Étape 6 : définir le chemin d'accès au modèle. C'est ici que vous indiquerez à votre application où trouver les fichiers de modèle nécessaires à la génération d'images. Pour cet exemple, définissez la valeur sur /data/local/tmp/image_generator/bins/.

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

Faites défiler la page jusqu'à la fonction generateImage(). En bas de cette fonction, vous trouverez les étapes 7 et 8, qui seront utilisées pour générer des images avec des itérations renvoyées ou aucune, respectivement. Comme ces deux opérations se produisent de manière synchrone, vous remarquerez qu'elles sont encapsulées dans une coroutine. Vous pouvez commencer par remplacer // Étape 7 : Générer sans afficher les itérations par ce bloc de code pour appeler generate() à partir du fichier ImageGenerationHelper, puis mettre à jour l'état de l'UI.

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

L'étape 8 est un peu plus délicate. Étant donné que la fonction execute() n'exécute qu'une étape au lieu de toutes pour la génération d'images, vous devrez appeler chaque étape individuellement via une boucle. Vous devez également déterminer si l'étape en cours doit être affichée pour l'utilisateur. Enfin, vous devez mettre à jour l'état de l'UI si l'itération actuelle doit s'afficher. Vous pouvez déjà le faire.

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

À ce stade, vous devriez pouvoir installer votre application, initialiser le générateur d'images, puis créer une image à partir d'une requête textuelle.

... sauf que l'application plante lorsque vous essayez d'initialiser le générateur d'images. En effet, vous devez copier vos fichiers de modèle sur votre appareil. Pour obtenir les informations les plus récentes sur les modèles tiers connus pour fonctionner, les convertir pour cette tâche MediaPipe et les copier sur votre appareil, consultez cette section de la documentation officielle.

En plus de copier des fichiers directement sur votre appareil de développement, vous pouvez également configurer Firebase Storage pour qu'il télécharge les fichiers nécessaires directement sur l'appareil de l'utilisateur au moment de l'exécution.

5. Déployer et tester l'application

Vous devriez maintenant disposer d'une application fonctionnelle qui peut accepter une requête textuelle et générer de nouvelles images entièrement sur l'appareil. Déployez l'application sur un appareil Android physique pour la tester. N'oubliez pas que vous devez utiliser un appareil disposant d'au moins 8 Go de mémoire.

  1. Cliquez sur "Run" (Exécuter) 7e15a9c9e1620fe7.png dans la barre d'outils d'Android Studio pour exécuter l'application.
  2. Sélectionnez le type d'étapes de génération (finales ou avec itérations), puis appuyez sur le bouton INITIALISER.
  3. Sur l'écran suivant, définissez les propriétés de votre choix, puis cliquez sur le bouton GÉNÉRER pour voir ce que l'outil propose.

e46cfaeb9d3fc235.gif

6. Félicitations !

Bravo ! Dans cet atelier de programmation, vous avez appris à ajouter la génération de texte en image sur l'appareil à une application Android.

Étapes suivantes

Vous pouvez faire d'autres choses avec la tâche de génération d'images, par exemple :

  • en utilisant une image de base pour structurer les images générées via des plug-ins, ou en entraînant vos propres poids LoRA supplémentaires via Vertex AI.
  • Utilisez Firebase Storage pour récupérer les fichiers de modèle sur votre appareil sans avoir à utiliser l'outil ADB.

Nous avons hâte de découvrir toutes les choses géniales que vous allez créer avec cette tâche expérimentale. Tenez-vous prêt à découvrir encore plus d'ateliers de programmation et de contenus de la part de l'équipe MediaPipe !