Mit MediaPipe Tasks eine Android-App für handschriftliche Ziffern erstellen

1. Einführung

Was ist MediaPipe?

Mit MediaPipe Solutions können Sie Lösungen für maschinelles Lernen (ML) auf Ihre Anwendungen anwenden. Er bietet ein Framework für die Konfiguration vorgefertigter Verarbeitungspipelines, die den Nutzern eine sofortige, ansprechende und nützliche Ausgabe liefern. Sie können diese Lösungen sogar mit dem MediaPipe Model Maker anpassen, um die Standardmodelle zu aktualisieren.

Die Bildklassifizierung ist eine von mehreren ML-Visionsaufgaben, die MediaPipe Solutions zu bieten hat. MediaPipe Tasks ist für Android, iOS, Python (einschließlich Raspberry Pi!) und für das Web verfügbar.

In diesem Codelab beginnen Sie mit einer Android-App, mit der Sie Ziffern auf dem Bildschirm zeichnen können. Anschließend fügen Sie Funktionen hinzu, die die gezogenen Ziffern als einen einzigen Wert von 0 bis 9 klassifizieren.

Lerninhalte

  • Eine Aufgabe zur Bildklassifizierung in einer Android-App mit MediaPipe Tasks einbinden

Voraussetzungen

  • Eine installierte Version von Android Studio. Dieses Codelab wurde mit Android Studio Giraffe geschrieben und getestet.
  • Ein Android-Gerät oder Emulator zum Ausführen der App
  • Grundkenntnisse der Android-Entwicklung.

2. MediaPipe Tasks zur Android-App hinzufügen

Android-Starter-App herunterladen

Dieses Codelab beginnt mit einem vorgefertigten Beispiel, mit dem Sie auf dem Bildschirm zeichnen können. Eine Start-App findest du im offiziellen MediaPipe Samples-Repository. Klonen Sie das Repository oder laden Sie die ZIP-Datei herunter, indem Sie auf „Code“ klicken. ZIP herunterladen.

App in Android Studio importieren

  1. Öffnen Sie Android Studio.
  2. Wählen Sie auf dem Bildschirm Willkommen bei Android Studio oben rechts Öffnen aus.

a0b5b070b802e4ea.png

  1. Gehen Sie zu dem Speicherort, an dem Sie das Repository geklont oder heruntergeladen haben, und öffnen Sie das Verzeichnis codelabs/digitclassifier/android/start.
  2. Prüfen Sie, ob alles korrekt geöffnet wurde, indem Sie oben rechts in Android Studio auf den grünen Pfeil Ausführen ( 7e15a9c9e1620fe7.png) klicken.
  3. Die App sollte mit einem schwarzen Bildschirm, auf dem Sie zeichnen können, und der Schaltfläche Löschen erscheinen, über die der Bildschirm zurückgesetzt werden kann. Sie können zwar auf diesem Bildschirm zeichnen, das hat aber nichts anderes mehr. Wir beginnen also jetzt mit der Problembehebung.

11a0f6fe021fdc92.jpeg

Modell

Wenn Sie die App zum ersten Mal ausführen, stellen Sie möglicherweise fest, dass eine Datei namens mnist.tflite heruntergeladen und im Verzeichnis assets Ihrer App gespeichert wird. Der Einfachheit halber haben wir bereits ein bekanntes Modell, MNIST, das Ziffern klassifiziert, und hinzugefügt. Es wurde mithilfe des Skripts download_models.gradle im Projekt der App hinzugefügt. Wenn Sie ein eigenes benutzerdefiniertes Modell trainieren möchten, z. B. eines für handschriftliche Buchstaben, entfernen Sie die Datei download_models.gradle, löschen den Verweis darauf in der Datei build.gradle auf App-Ebene und ändern den Namen des Modells später im Code (insbesondere in der Datei DigitClassifierHelper.kt).

build.gradle aktualisieren

Bevor Sie MediaPipe Tasks verwenden können, müssen Sie die Bibliothek importieren.

  1. Öffnen Sie die Datei build.gradle im app-Modul und scrollen Sie nach unten zum Block dependencies.
  2. Am Ende des Blocks sollte der Kommentar // SCHRITT 1: Abhängigkeitsimport zu sehen sein.
  3. Ersetzen Sie diese Zeile durch die folgende Implementierung:
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Klicken Sie auf die Schaltfläche Jetzt synchronisieren, die im Banner oben in Android Studio erscheint, um diese Abhängigkeit herunterzuladen.

3. Hilfe für Ziffernklassifikatoren von MediaPipe Tasks erstellen

Im nächsten Schritt füllen Sie einen Kurs aus, der die schwierigen Aufgaben zur Klassifizierung des maschinellen Lernens erledigt. Öffnen Sie DigitClassifierHelper.kt und legen Sie los.

  1. Suchen Sie oben in der Klasse den Kommentar // SCHRITT 2: Listener erstellen.
  2. Ersetzen Sie diese Zeile durch den folgenden Code. Dadurch wird ein Listener erstellt, mit dem Ergebnisse aus der DigitClassifierHelper-Klasse zurück an den Ort übergeben werden, an dem auf diese Ergebnisse gewartet wird (in diesem Fall ist dies Ihre DigitCanvasFragment-Klasse, aber wir kommen bald darauf)
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Außerdem müssen Sie einen DigitClassifierListener als optionalen Parameter für die Klasse akzeptieren:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Gehen Sie nach unten zur Zeile // SCHRITT 3: Klassifikator definieren und fügen Sie die folgende Zeile hinzu, um einen Platzhalter für ImageClassifier zu erstellen, der für diese App verwendet wird:

// SCHRITT 3: Klassifikator definieren

private var digitClassifier: ImageClassifier? = null
  1. Fügen Sie die folgende Funktion hinzu, in der Sie den Kommentar sehen: // SCHRITT 4: Klassifikator einrichten:
// 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)
    }
}

Im obigen Abschnitt spielen einige Dinge eine Rolle. Schauen wir uns also die kleineren Teile an, um wirklich zu verstehen, was passiert.

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

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

Dieser Block definiert die von ImageClassifier verwendeten Parameter. Dazu gehören das in Ihrer App gespeicherte Modell (mnist.tflite) unter „BaseOptions“ und „RunningMode“ unter „ImageClassifierOptions“, in diesem Fall „IMAGE“, aber „VIDEO“ und „LIVE_STREAM“ sind zusätzliche verfügbare Optionen. Weitere verfügbare Parameter sind MaxResults, mit denen das Modell auf die Rückgabe einer maximalen Anzahl von Ergebnissen beschränkt ist, und ScoreThreshold, mit dem die Mindestzuverlässigkeit festgelegt wird, die das Modell in einem Ergebnis haben muss, bevor es zurückgegeben wird.

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

Nachdem Sie die Konfigurationsoptionen erstellt haben, können Sie Ihren neuen ImageClassifier erstellen, indem Sie einen Kontext und die Optionen übergeben. Sollte bei diesem Initialisierungsprozess ein Fehler auftreten, wird über den DigitClassifierListener ein Fehler zurückgegeben.

  1. Da der ImageClassifier vor seiner Verwendung initialisiert werden soll, können Sie einen Init-Block für den Aufruf von "setupDigitClassifier()" hinzufügen.
init {
    setupDigitClassifier()
}
  1. Scrollen Sie schließlich nach unten zum Kommentar // SCHRITT 5 Klassifizierungsfunktion erstellen und fügen Sie den folgenden Code hinzu. Diese Funktion akzeptiert eine Bitmap (in diesem Fall die gezeichnete Ziffer), konvertiert sie in ein MediaPipe Image-Objekt (MPImage), klassifiziert dieses Bild mithilfe von ImageClassifier und zeichnet auf, wie lange die Inferenz dauert, bevor diese Ergebnisse über den DigitClassifierListener zurückgegeben werden.
// 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)
    }
}

Das war's mit der Hilfsdatei! Im nächsten Abschnitt führen Sie die letzten Schritte zur Klassifizierung der gezeichneten Zahlen aus.

4. Inferenz mit MediaPipe-Aufgaben ausführen

Sie können diesen Abschnitt beginnen, indem Sie die Klasse DigitCanvasFragment in Android Studio öffnen. Dort werden alle Arbeiten ausgeführt.

  1. Ganz unten in der Datei sollten Sie den Kommentar // SCHRITT 6: Listener einrichten sehen. Die Funktionen onResults() und onError(), die mit dem Listener verknüpft sind, fügen Sie hier hinzu.
// 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() ist besonders wichtig, da damit die von ImageClassifier empfangenen Ergebnisse angezeigt werden. Da dieser Callback von einem Hintergrundthread ausgelöst wird, müssen Sie Ihre UI-Updates auch im Android-UI-Thread ausführen.

  1. Da Sie im Schritt oben über eine Schnittstelle neue Funktionen hinzufügen, müssen Sie auch die Implementierungsdeklaration oben in der Klasse hinzufügen.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Am Anfang der Klasse sollten Sie einen Kommentar mit folgendem Text sehen: // SCHRITT 7a Klassifikator initialisieren. Hier platzieren Sie die Deklaration für DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Gehen Sie nach unten zu // SCHRITT 7b Klassifikator initialisieren. Hier können Sie DikClassifierHelper innerhalb der onViewCreated()-Funktion initialisieren.
// 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. Suchen Sie für die letzten Schritte den Kommentar // SCHRITT 8a*: classify* und fügen Sie den folgenden Code hinzu, um eine neue Funktion aufzurufen, die Sie gleich hinzufügen werden. Dieser Codeblock löst die Klassifizierung aus, wenn Sie den Finger vom Zeichnungsbereich der App nehmen.
// STEP 8a: classify
classifyDrawing()
  1. Suchen Sie abschließend nach dem Kommentar // SCHRITT 8b classify, um die neue Funktion classifyDrawing() hinzuzufügen. Dadurch wird eine Bitmap aus dem Canvas extrahiert und an DigitClassifierHelper übergeben, um eine Klassifizierung durchzuführen und die Ergebnisse in der onResults()-Oberflächenfunktion zu erhalten.
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Anwendung bereitstellen und testen

Danach sollten Sie eine funktionierende App haben, die gezeichnete Ziffern auf dem Bildschirm klassifizieren kann! Stellen Sie die App nun entweder in einem Android-Emulator oder auf einem physischen Android-Gerät bereit, um sie zu testen.

  1. Klicken Sie in der Android Studio-Symbolleiste auf „Ausführen“ (7e15a9c9e1620fe7.png), um die App auszuführen.
  2. Zeichnen Sie eine beliebige Ziffer auf den Zeichenblock und prüfen Sie, ob die App sie erkennt. Sie sollte sowohl die Ziffer anzeigen, die nach Ansicht des Modells gezeichnet wurde, als auch die Zeit bis zur Vorhersage dieser Ziffer.

7f37187f8f919638.gif

6. Glückwunsch!

Geschafft! In diesem Codelab haben Sie gelernt, wie Sie einer Android-App die Bildklassifizierung hinzufügen und insbesondere handgezeichnete Ziffern mithilfe des MNIST-Modells klassifizieren.

Nächste Schritte

  • Da Sie jetzt Ziffern klassifizieren können, möchten Sie vielleicht Ihr eigenes Modell trainieren, um gezeichnete Buchstaben oder Tiere oder eine endlose Anzahl anderer Elemente zu klassifizieren. Die Dokumentation zum Trainieren eines neuen Bildklassifizierungsmodells mit dem MediaPipe Model Maker finden Sie auf der Seite developers.google.com/mediapipe.
  • Weitere Informationen zu weiteren für Android verfügbaren MediaPipe-Aufgaben, z. B. zur Erkennung von Gesichtspunkten, zur Gestenerkennung und zur Audioklassifizierung

Wir freuen uns auf all eure coolen Sachen!