Crea un'app Android per la classificazione delle cifre scritte a mano libera con MediaPipe Tasks

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 queste soluzioni con MediaPipe Model Maker per aggiornare i modelli predefiniti.

La classificazione delle immagini è una delle diverse attività di visione ML che le soluzioni MediaPipe ha da offrire. MediaPipe Tasks è disponibile per Android, iOS, Python (incluso Raspberry Pi!) e il web.

In questo codelab, inizierai con un'app per Android che ti consente di disegnare cifre numeriche sullo schermo, quindi aggiungerai una funzionalità che classifica queste cifre come un singolo valore da 0 a 9.

Obiettivi didattici

  • Come incorporare un'attività di classificazione delle immagini in un'app per Android con MediaPipe Tasks.

Che cosa ti serve

  • Una versione installata di Android Studio (questo codelab è stato scritto e testato con Android Studio Giraffe).
  • Un emulatore o un dispositivo Android per eseguire l'app.
  • Conoscenza di base dello sviluppo Android (non si tratta di "Hello World", ma non è troppo lontano).

2. Aggiungi Attività MediaPipe all'app per Android

Scarica l'app iniziale per Android

Questo codelab inizia con un esempio predefinito che ti consente di disegnare sullo schermo. Puoi trovare l'app iniziale nel repository ufficiale dei campioni 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. Vai alla posizione in cui hai clonato o scaricato il repository e apri la codelabs/digitclassifier/android/start.
  2. Verifica che tutti i contenuti siano stati aperti correttamente facendo clic sulla freccia verde Esegui ( 7e15a9c9e1620fe7.png) nell'angolo in alto a destra di Android Studio.
  3. Dovresti vedere l'app aperta con una schermata nera su cui puoi disegnare e un pulsante Cancella per reimpostare la schermata. Anche se puoi disegnare su quello schermo, non serve a molto altro, quindi iniziamo a risolvere il problema ora.

11a0f6fe021fdc92.jpeg

Modello

Alla prima esecuzione dell'app, potresti notare che un file denominato mnist.tflite viene scaricato e archiviato nella directory assets dell'app. Per semplicità, abbiamo già preso un modello noto, MNIST, che classifica le cifre e lo abbiamo aggiunto all'app utilizzando lo script download_models.gradle nel progetto. Se decidi di addestrare un modello personalizzato, ad esempio uno per le lettere scritte a mano, devi rimuovere il file download_models.gradle, eliminare il riferimento al file build.gradle a livello di app e modificare il nome del modello più avanti nel codice (in particolare nel file DigitClassifierHelper.kt).

Aggiorna build.gradle

Prima di poter iniziare a utilizzare le attività di MediaPipe, devi importare la libreria.

  1. Apri il file build.gradle che si trova nel modulo app, quindi scorri verso il basso fino al blocco dependencies.
  2. Nella parte inferiore del blocco dovresti vedere un commento che dice // PASSAGGIO 1 Importazione delle dipendenze.
  3. Sostituisci questa riga con la seguente implementazione
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Fai clic sul pulsante Sincronizza ora visualizzato nel banner nella parte superiore di Android Studio per scaricare questa dipendenza.

3. Crea un aiuto per la classificazione delle cifre di Attività MediaPipe

Nel passaggio successivo, dovrai compilare un corso che si occuperà della parte più impegnativa della classificazione del machine learning. Apri DigitClassifierHelper.kt e iniziamo.

  1. Individua il commento nella parte superiore della classe con la dicitura // PASSAGGIO 2 Crea listener.
  2. Sostituiscila con il codice riportato di seguito. Verrà creato un listener che verrà utilizzato per ritrasmettere i risultati dalla classe DigitClassifierHelper a dove li ascolta (in questo caso sarà la tua classe DigitCanvasFragment, ma ci arriveremo presto).
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Dovrai inoltre accettare un DigitClassifierListener come parametro facoltativo per la classe:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Scendendo con la riga // STEP 3 define classificatore, aggiungi la riga seguente per creare un segnaposto per ImageClassifier che verrà utilizzato per questa app:

// PASSAGGIO 3 definisce il classificatore

private var digitClassifier: ImageClassifier? = null
  1. Aggiungi la funzione seguente nel punto in cui vedi il commento // PASSAGGIO 4 imposta la categoria di classificazione:
// 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)
    }
}

Nella sezione precedente sono descritti alcuni aspetti, pertanto diamo un'occhiata alle parti più piccole per comprenderne meglio la situazione.

val baseOptionsBuilder = BaseOptions.builder()
    .setModelAssetPath("mnist.tflite")

// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
    .setRunningMode(RunningMode.IMAGE)
    .setBaseOptions(baseOptionsBuilder.build())

Questo blocco definisce i parametri utilizzati da ImageClassifier. Ciò include il modello archiviato all'interno dell'app (mnist.tflite) in BaseOptions e RunningMode in ImageClassifierOptions, che in questo caso è IMAGE, ma VIDEO e LIVE_STREAM sono ulteriori opzioni disponibili. Altri parametri disponibili sono MaxResults, che limita la restituzione del modello per un numero massimo di risultati, e ScoreThreshold, che imposta la confidenza minima che il modello deve avere in un risultato prima di restituirlo.

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)
}

Dopo aver creato le opzioni di configurazione, puoi creare il nuovo ImageClassifier passando un contesto e le opzioni. Se si verifica un problema con il processo di inizializzazione, verrà restituito un errore tramite il DigitClassifierListener.

  1. Poiché vogliamo inizializzare ImageClassifier prima che venga utilizzato, puoi aggiungere un blocco di inizializzazione per chiamare setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Infine, scorri verso il basso fino al commento che dice // STEP 5 create classify function e aggiungere il codice seguente. Questa funzione accetterà una Bitmap, che in questo caso è la cifra disegnata, la convertirà in un oggetto Immagine MediaPipe (MPImage) e quindi classificherà l'immagine utilizzando ImageClassifier, oltre a registrare il tempo necessario per l'inferenza, prima di restituire i risultati al 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)
    }
}

Questo è tutto per il file helper. Nella sezione successiva completerai i passaggi finali per iniziare a classificare i numeri disegnati.

4. Esegui l'inferenza con le attività di MediaPipe

Puoi iniziare questa sezione aprendo la classe DigitCanvasFragment in Android Studio, dove si svolgerà tutto il lavoro.

  1. In fondo al file dovresti vedere un commento che dice // PASSAGGIO 6 Imposta listener. In questo caso, aggiungerai le funzioni onResults() e onError() associate al listener.
// 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() è particolarmente importante in quanto visualizza i risultati ricevuti da ImageClassifier. Poiché questo callback viene attivato da un thread in background, devi anche eseguire gli aggiornamenti della UI sul thread dell'interfaccia utente di Android.

  1. Poiché nel passaggio precedente aggiungi nuove funzioni da un'interfaccia, dovrai anche aggiungere la dichiarazione di implementazione all'inizio della classe.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Nella parte superiore della classe dovrebbe essere visualizzato un commento con la dicitura // PASSAGGIO 7a Inizializza classificatore. È qui che inserirai la dichiarazione per DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Passando in basso in // PASSAGGIO 7b Inizializza il classificatore,puoi inizializzare digitClassifierHelper all'interno della funzione 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
)
  1. Per gli ultimi passaggi, trova il commento // STEP 8a*: classify* e aggiungi il seguente codice per chiamare una nuova funzione che aggiungerai tra poco. Questo blocco di codice attiva la classificazione quando sollevi il dito dall'area di disegno nell'app.
// STEP 8a: classify
classifyDrawing()
  1. Infine, cerca il commento // STEP 8b classify per aggiungere la nuova funzione classifyDrawing(). Questa operazione estrarrà una bitmap dal canvas, quindi la passerà a DigitClassifierHelper per eseguire la classificazione al fine di ricevere i risultati nella funzione di interfaccia onResults().
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Esegui il deployment e il test dell'app

Dopodiché dovresti avere un'app funzionante in grado di classificare le cifre disegnate sullo schermo. Esegui il deployment dell'app su un emulatore Android o su un dispositivo Android fisico per testarla.

  1. Fai clic su Esegui ( 7e15a9c9e1620fe7.png) nella barra degli strumenti di Android Studio per eseguire l'app.
  2. Disegna una cifra qualsiasi sul riquadro da disegno e verifica se l'app è in grado di riconoscerla. Deve mostrare sia la cifra che il modello ritiene sia stata disegnata, oltre al tempo necessario per prevederla.

7f37187f8f919638.gif

6. Complimenti!

Ce l'hai fatta! In questo codelab hai imparato come aggiungere la classificazione delle immagini a un'app per Android e in particolare come classificare le cifre disegnate a mano utilizzando il modello MNIST.

Passaggi successivi

  • Ora che sei in grado di classificare le cifre, potresti voler addestrare il tuo modello per classificare le lettere disegnate, gli animali o un numero infinito di altri elementi. Puoi trovare la documentazione per l'addestramento di un nuovo modello di classificazione delle immagini con Model Maker MediaPipe alla pagina developers.google.com/mediapipe.
  • Scopri le altre attività di MediaPipe disponibili per Android, tra cui il rilevamento dei punti di riferimento dei volti, il riconoscimento dei gesti e la classificazione audio.

Non vediamo l'ora di condividere le tue fantastiche cose.