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

1. Introduzione

Che cos'è MediaPipe?

MediaPipe Solutions ti consente di applicare soluzioni di machine learning (ML) alle tue app. Fornisce un framework per la configurazione di pipeline di elaborazione predefinite che offrono agli utenti un output immediato, coinvolgente e utile. Puoi persino personalizzare queste soluzioni con MediaPipe Model Maker per aggiornare i modelli predefiniti.

La classificazione delle immagini è una delle diverse attività di visione basata sull'IA offerte da MediaPipe Solutions. 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 le cifre disegnate 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 dispositivo o un emulatore Android per eseguire l'app.
  • Conoscenze di base dello sviluppo Android (non si tratta di "Hello World", ma non è molto lontano).

2. Aggiungere MediaPipe Tasks all'app per Android

Scaricare l'app iniziale per Android

Questo codelab inizierà con un esempio predefinito che ti consente di disegnare sullo schermo. Puoi trovare l'app di avvio nel repository ufficiale di MediaPipe Samples 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. Nella 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 directory codelabs/digitclassifier/android/start.
  2. Verifica che tutto si sia aperto correttamente facendo clic sulla freccia verde Esegui ( 7e15a9c9e1620fe7.png) in alto a destra in Android Studio.
  3. L'app dovrebbe aprirsi con una schermata nera su cui puoi disegnare, oltre a un pulsante Cancella per reimpostare la schermata. Puoi disegnare sullo schermo, ma non fa molto altro, quindi inizieremo a risolvere il problema ora.

11a0f6fe021fdc92.jpeg

Modello

Quando esegui l'app per la prima volta, 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 tramite lo script download_models.gradle nel progetto. Se decidi di addestrare il tuo modello personalizzato, ad esempio per le lettere scritte a mano, devi rimuovere il file download_models.gradle, eliminare il riferimento al file nel file build.gradle a livello di app e modificare il nome del modello in un secondo momento nel codice (in particolare nel file DigitClassifierHelper.kt).

Aggiorna build.gradle

Prima di poter iniziare a utilizzare MediaPipe Tasks, devi importare la libreria.

  1. Apri il file build.gradle nel modulo app, quindi scorri verso il basso fino al blocco dependencies.
  2. Nella parte inferiore del blocco dovresti vedere il commento // 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. Creare un helper per il classificatore di cifre di MediaPipe Tasks

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

  1. Trova il commento nella parte superiore del corso con il messaggio // PASSAGGIO 2 Crea ascoltatore
  2. Sostituisci questa riga con il seguente codice. Verrà creato un ascoltatore che verrà utilizzato per trasmettere i risultati della classe DigitClassifierHelper a qualsiasi punto in cui sono in ascolto (in questo caso sarà la classe DigitCanvasFragment, ma ci arriveremo presto).
// STEP 2 Create listener

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

// PASSAGGIO 3 definisci il classificatore

private var digitClassifier: ImageClassifier? = null
  1. Aggiungi la seguente funzione dove vedi il commento // PASSAGGIO 4 Configura il classificatore:
// 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 presenti alcuni elementi, quindi esaminiamo parti più piccole per capire cosa succede.

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. Sono inclusi il modello memorizzato all'interno dell'app (mnist.tflite) in BaseOptions e il parametro RunningMode in ImageClassifierOptions, che in questo caso è IMAGE, ma VIDEO e LIVE_STREAM sono opzioni aggiuntive disponibili. Altri parametri disponibili sono MaxResults, che limita il modello a restituire 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 la procedura di inizializzazione, verrà restituito un errore tramite 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 // PASSAGGIO 5 Crea la funzione di classificazione e aggiungi il seguente codice. Questa funzione accetta un Bitmap, che in questo caso è il numero disegnato, lo converte in un oggetto immagine MediaPipe (MPImage), quindi classifica l'immagine utilizzando ImageClassifier, oltre a registrare il tempo necessario per l'inferenza, prima di restituire i risultati tramite 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)
    }
}

È tutto per il file di assistenza. Nella sezione successiva, dovrai completare i passaggi finali per iniziare a classificare i numeri disegnati.

4. Eseguire l'inferenza con MediaPipe Tasks

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

  1. Nella parte inferiore del file dovresti vedere il commento // PASSAGGIO 6 Configura l'ascoltatore. Qui aggiungerai le funzioni onResults() e onError() associate all'ascoltatore.
// 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 perché mostra i risultati ricevuti da ImageClassifier. Poiché questo callback viene attivato da un thread in background, dovrai anche eseguire gli aggiornamenti dell'interfaccia utente sul thread dell'interfaccia utente di Android.

  1. Quando aggiungi nuove funzioni da un'interfaccia nel passaggio precedente, devi anche aggiungere la dichiarazione di implementazione nella parte superiore della classe.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Nella parte superiore del corso dovresti vedere il commento // PASSAGGIO 7a Inizializza il classificatore. Qui inserirai la dichiarazione di DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Andando a // 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 // PASSAGGIO 8a*: classify* e aggiungi il seguente codice per chiamare una nuova funzione che aggiungerai a breve. Questo blocco di codice attiverà la classificazione quando sollevi il dito dall'area di disegno nell'app.
// STEP 8a: classify
classifyDrawing()
  1. Infine, cerca il commento // PASSAGGIO 8b classifica per aggiungere la nuova funzione classificaDisegno(). Verrà estratta una bitmap dalla tela, poi verrà passata a DigitClassifierHelper per eseguire la classificazione e 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

Al termine di questa procedura, 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 un numero sul blocco da disegno e controlla se l'app è in grado di riconoscerlo. Dovrebbe mostrare sia il numero che il modello ritiene sia stato disegnato sia il tempo necessario per prevederlo.

7f37187f8f919638.gif

6. Complimenti!

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

Passaggi successivi

  • Ora che puoi 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 MediaPipe Model Maker nella pagina developers.google.com/mediapipe.
  • Scopri le altre attività MediaPipe disponibili per Android, tra cui il rilevamento dei punti di riferimento del viso, il riconoscimento dei gesti e la classificazione audio.

Non vediamo l'ora di scoprire tutte le cose fantastiche che realizzerai.