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 ces solutions avec MediaPipe Model Maker afin de modifier les modèles par défaut.
La classification d'images est l'une des nombreuses tâches de vision ML proposées par MediaPipe Solutions. MediaPipe Tasks est disponible pour Android, iOS, Python (y compris Raspberry Pi) et le Web.
Dans cet atelier de programmation, vous commencerez par une application Android qui vous permet de dessiner des chiffres sur l'écran, puis vous ajouterez une fonctionnalité qui classe ces chiffres en une seule valeur comprise entre 0 et 9.
Points abordés
- Intégrer une tâche de classification d'images 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 ou un émulateur Android pour exécuter l'application.
- Connaissances de base en développement Android (ce n'est pas "Hello World", mais ce n'est pas loin de là !)
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 qui vous permet de dessiner à l'écran. 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
- Ouvrez Android Studio.
- Sur l'écran Welcome to Android Studio (Bienvenue dans Android Studio), sélectionnez Open (Ouvrir) en haut à droite.
- Accédez à l'emplacement où vous avez cloné ou téléchargé le dépôt, puis ouvrez le répertoire codelabs/digitclassifier/android/start.
- Vérifiez que tout s'est bien ouvert en cliquant sur la flèche verte Run ( Exécuter)
en haut à droite d'Android Studio.
- L'application devrait s'ouvrir sur un écran noir sur lequel vous pouvez dessiner, ainsi qu'un bouton Effacer pour réinitialiser cet écran. Vous pouvez dessiner sur cet écran, mais il ne sert pas à grand-chose d'autre. Nous allons donc commencer à le corriger dès maintenant.
Modèle
Lorsque vous exécutez l'application pour la première fois, vous pouvez remarquer qu'un fichier nommé mnist.tflite est téléchargé et stocké dans le répertoire assets de votre application. Par souci de simplicité, nous avons déjà pris un modèle connu, MNIST, qui classe les chiffres, et l'avons ajouté à l'application à l'aide du script download_models.gradle du projet. Si vous décidez d'entraîner votre propre modèle personnalisé, par exemple pour les lettres manuscrites, vous devez supprimer le fichier download_models.gradle, supprimer la référence à celui-ci dans le fichier build.gradle au niveau de l'application, puis modifier le nom du modèle plus tard dans le code (plus précisément dans le fichier DigitClassifierHelper.kt).
Mettre à jour le fichier build.gradle
Avant de pouvoir utiliser MediaPipe Tasks, vous devez importer la bibliothèque.
- Ouvrez le fichier build.gradle situé dans votre module app, puis faites défiler la page jusqu'au bloc dependencies.
- Un commentaire // ÉTAPE 1 Importation de dépendances doit s'afficher en bas de ce bloc.
- Remplacez cette ligne par l'implémentation suivante :
implementation("com.google.mediapipe:tasks-vision:latest.release")
- Cliquez sur le bouton Sync Now (Synchroniser) qui s'affiche dans la bannière en haut d'Android Studio pour télécharger cette dépendance.
3. Créer un assistant de classification des chiffres MediaPipe Tasks
À l'étape suivante, vous allez remplir une classe qui effectuera la majeure partie du travail de classification par machine learning. Ouvrez DigitClassifierHelper.kt et commençons !
- Recherchez le commentaire en haut de la classe qui indique // ÉTAPE 2 : Créer un écouteur.
- Remplacez cette ligne par le code suivant. Cela crée un écouteur qui sera utilisé pour transmettre les résultats de la classe DigitClassifierHelper à l'endroit où ils sont écoutés (dans ce cas, il s'agira de votre classe DigitCanvasFragment, mais nous y reviendrons bientôt).
// STEP 2 Create listener
interface DigitClassifierListener {
fun onError(error: String)
fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
)
}
- Vous devrez également accepter un DigitClassifierListener en tant que paramètre facultatif pour la classe:
class DigitClassifierHelper(
val context: Context,
val digitClassifierListener: DigitClassifierListener?
) {
- Descendez jusqu'à la ligne // ÉTAPE 3 : définir le classificateur, puis ajoutez la ligne suivante pour créer un espace réservé pour l'ImageClassifier qui sera utilisé pour cette application :
// ÉTAPE 3 : définir le classificateur
private var digitClassifier: ImageClassifier? = null
- Ajoutez la fonction suivante à l'emplacement du commentaire // ÉTAPE 4 : configurer le classificateur :
// STEP 4 set up classifier
private fun setupDigitClassifier() {
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
}
La section ci-dessus comporte plusieurs éléments. Examinons-les plus en détail pour comprendre ce qui se passe.
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
Ce bloc définit les paramètres utilisés par ImageClassifier. Cela inclut le modèle stocké dans votre application (mnist.tflite) sous BaseOptions et le RunningMode sous ImageClassifierOptions, qui dans ce cas est IMAGE, mais VIDEO et LIVE_STREAM sont des options supplémentaires disponibles. Les autres paramètres disponibles sont MaxResults, qui limite le modèle à renvoyer un nombre maximal de résultats, et ScoreThreshold, qui définit le niveau de confiance minimal que le modèle doit avoir dans un résultat avant de le renvoyer.
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
Après avoir créé vos options de configuration, vous pouvez créer votre ImageClassifier en transmettant un contexte et les options. Si un problème survient lors de ce processus d'initialisation, une erreur est renvoyée via votre DigitClassifierListener.
- Comme nous souhaitons initialiser ImageClassifier avant de l'utiliser, vous pouvez ajouter un bloc d'initialisation pour appeler setupDigitClassifier().
init {
setupDigitClassifier()
}
- Enfin, faites défiler la page jusqu'au commentaire // ÉTAPE 5 : Créer la fonction de classification, puis ajoutez le code suivant. Cette fonction accepte un bitmap, qui dans ce cas est le chiffre dessiné, le convertit en objet Image MediaPipe (MPImage), puis le classe à l'aide de l'ImageClassifier, ainsi que la durée de l'inférence, avant de renvoyer ces résultats via le DigitClassifierListener.
// STEP 5 create classify function
fun classify(image: Bitmap) {
if (digitClassifier == null) {
setupDigitClassifier()
}
// Convert the input Bitmap object to an MPImage object to run inference.
// Rotating shouldn't be necessary because the text is being extracted from
// a view that should always be correctly positioned.
val mpImage = BitmapImageBuilder(image).build()
// Inference time is the difference between the system time at the start and finish of the
// process
val startTime = SystemClock.uptimeMillis()
// Run image classification using MediaPipe Image Classifier API
digitClassifier?.classify(mpImage)?.also { classificationResults ->
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
}
}
C'est tout pour le fichier d'assistance. Dans la section suivante, vous allez effectuer les dernières étapes pour commencer à classer les numéros tirés.
4. Exécuter des inférences avec MediaPipe Tasks
Vous pouvez commencer cette section en ouvrant la classe DigitCanvasFragment dans Android Studio, où tout le travail se fera.
- Tout en bas de ce fichier, vous devriez voir un commentaire indiquant // ÉTAPE 6 Configurer l'écouteur. Vous allez y ajouter les fonctions onResults() et onError() associées à l'écouteur.
// STEP 6 Set up listener
override fun onError(error: String) {
activity?.runOnUiThread {
Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
fragmentDigitCanvasBinding.tvResults.text = ""
}
}
override fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
) {
activity?.runOnUiThread {
fragmentDigitCanvasBinding.tvResults.text = results
.classificationResult()
.classifications().get(0)
.categories().get(0)
.categoryName()
fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
.getString(R.string.inference_time, inferenceTime.toString())
}
}
onResults() est particulièrement important, car il affiche les résultats reçus de l'ImageClassifier. Comme ce rappel est déclenché à partir d'un thread en arrière-plan, vous devez également exécuter vos mises à jour d'interface utilisateur sur le thread UI d'Android.
- Comme vous ajoutez des fonctions à partir d'une interface à l'étape précédente, vous devez également ajouter la déclaration d'implémentation en haut de la classe.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- En haut de la classe, vous devriez voir un commentaire indiquant // ÉTAPE 7a Initialiser le classificateur. C'est ici que vous placerez la déclaration de DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- En descendant jusqu'à // ÉTAPE 7b Initialiser le classificateur,vous pouvez initialiser digitClassifierHelper dans la fonction onViewCreated().
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
context = requireContext(), digitClassifierListener = this
)
- Pour les dernières étapes, recherchez le commentaire // ÉTAPE 8a : classify* et ajoutez le code suivant pour appeler une nouvelle fonction que vous ajouterez sous peu. Ce bloc de code déclenche la classification lorsque vous retirez votre doigt de la zone de dessin de l'application.
// STEP 8a: classify
classifyDrawing()
- Enfin, recherchez le commentaire // ÉTAPE 8b classifier pour ajouter la nouvelle fonction classifyDrawing(). Un bitmap est extrait du canevas, puis transmis à DigitClassifierHelper pour effectuer la classification et recevoir les résultats dans la fonction d'interface onResults().
// STEP 8b classify
private fun classifyDrawing() {
val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
digitClassifierHelper.classify(bitmap)
}
5. Déployer et tester l'application
Vous devriez maintenant disposer d'une application fonctionnelle capable de classer les chiffres dessinés à l'écran. Déployez l'application sur un émulateur Android ou un appareil Android physique pour la tester.
- Cliquez sur "Run" (Exécuter)
dans la barre d'outils d'Android Studio pour exécuter l'application.
- Dessinez un chiffre sur le pavé de dessin et vérifiez si l'application peut le reconnaître. Il doit afficher à la fois le chiffre que le modèle pense avoir été dessiné et le temps qu'il a fallu pour le prédire.
6. Félicitations !
Bravo ! Dans cet atelier de programmation, vous avez appris à ajouter la classification d'images à une application Android, et plus précisément à classer des chiffres dessinés à la main à l'aide du modèle MNIST.
Étapes suivantes
- Maintenant que vous pouvez classer des chiffres, vous pouvez entraîner votre propre modèle pour classer des lettres dessinées, des animaux ou un nombre infini d'autres éléments. Vous trouverez la documentation sur l'entraînement d'un nouveau modèle de classification d'images avec MediaPipe Model Maker sur la page developers.google.com/mediapipe.
- Découvrez les autres tâches MediaPipe disponibles pour Android, y compris la détection des points de repère du visage, la reconnaissance des gestes et la classification audio.
Nous avons hâte de découvrir toutes les choses géniales que vous allez créer !