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 Apps anwenden. Sie bietet ein Framework zum Konfigurieren vordefinierter Verarbeitungspipelines, die Nutzern sofortige, ansprechende und nützliche Ergebnisse 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-Aufgaben für Computer Vision, die MediaPipe Solutions bietet. MediaPipe Tasks ist für Android, iOS, Python (einschließlich Raspberry Pi) und 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 eine Funktion hinzu, die diese gezeichneten Ziffern als einzelnen Wert von 0 bis 9 klassifiziert.

Lerninhalte

  • Informationen zum Einbinden einer Bildklassifizierungsaufgabe in eine Android-App mit MediaPipe Tasks

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 in der Android-Entwicklung (dies ist kein „Hallo Welt“, aber es ist nicht weit davon entfernt).

2. MediaPipe Tasks zur Android-App hinzufügen

Android-Start-App herunterladen

Dieses Codelab beginnt mit einem vorgefertigten Beispiel, mit dem Sie auf dem Bildschirm zeichnen können. Sie finden diese Start-App im offiziellen MediaPipe-Samples-Repository. Klone das Repository oder lade die ZIP-Datei herunter, indem du auf „Code“ > „ZIP herunterladen“ klickst.

App in Android Studio importieren

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

a0b5b070b802e4ea.png

  1. Rufen Sie den Speicherort auf, an dem Sie das Repository geklont oder heruntergeladen haben, und öffnen Sie das Verzeichnis codelabs/digitclassifier/android/start.
  2. Prüfen Sie, ob alles richtig 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 geöffnet werden, auf dem Sie zeichnen können, sowie mit der Schaltfläche Löschen, um diesen Bildschirm zurückzusetzen. Sie können zwar auf diesem Bildschirm zeichnen, aber nicht viel mehr. Das werden wir jetzt ändern.

11a0f6fe021fdc92.jpeg

Modell

Wenn Sie die App zum ersten Mal ausführen, wird möglicherweise eine Datei namens mnist.tflite heruntergeladen und im Verzeichnis assets Ihrer App gespeichert. Zur Vereinfachung haben wir bereits ein bekanntes Modell, MNIST, verwendet, das Ziffern klassifiziert, und es der App über das Script download_models.gradle im Projekt hinzugefügt. Wenn Sie ein eigenes benutzerdefiniertes Modell trainieren möchten, z. B. für handgeschriebene Buchstaben, entfernen Sie die Datei download_models.gradle, löschen Sie den Verweis darauf in der Datei build.gradle auf App-Ebene und ändern Sie später im Code den Namen des Modells (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 Modul app und scrollen Sie zum Block dependencies.
  2. Unten in diesem Block sollte der Kommentar // STEP 1 Dependency Import (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 im Banner oben in Android Studio, um diese Abhängigkeit herunterzuladen.

3. MediaPipe Tasks-Hilfsklassifikator für Ziffern erstellen

Im nächsten Schritt füllen Sie einen Class-Block aus, der die Hauptarbeit für die Klassifizierung mithilfe von maschinellem Lernen übernimmt. Öffnen Sie DigitClassifierHelper.kt und legen Sie los.

  1. Suchen Sie oben im Code den Kommentar // STEP 2 Create listener (Schritt 2: Listener erstellen).
  2. Ersetzen Sie diese Zeile durch den folgenden Code. Dadurch wird ein Listener erstellt, mit dem Ergebnisse aus der Klasse „DigitClassifierHelper“ an die Stelle zurückgegeben werden, an der auf diese Ergebnisse gewartet wird. In diesem Fall ist das die Klasse DigitCanvasFragment, aber wir kommen gleich dazu.
// 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 zur Zeile // SCHRITT 3: Klassifikator definieren und fügen Sie die folgende Zeile hinzu, um einen Platzhalter für den 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 an der Stelle ein, an der der Kommentar // SCHRITT 4: Klassifikator einrichten steht:
// 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 passiert einiges. Sehen wir uns die einzelnen Teile genauer an, um zu verstehen, was vor sich geht.

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

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

In diesem Block werden die Parameter definiert, die von der Bildklassifizierung verwendet werden. Dazu gehören das in Ihrer App gespeicherte Modell (mnist.tflite) unter „BaseOptions“ und der „RunningMode“ unter „ImageClassifierOptions“, in diesem Fall „IMAGE“. Zusätzlich sind auch „VIDEO“ und „LIVE_STREAM“ verfügbar. Weitere verfügbare Parameter sind „MaxResults“, mit dem das Modell auf die Rückgabe einer maximalen Anzahl von Ergebnissen beschränkt wird, und „ScoreThreshold“, mit dem die Mindestkonfidenz festgelegt wird, die das Modell für ein 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. Wenn bei der Initialisierung ein Fehler auftritt, wird über Ihren DigitClassifierListener ein Fehler zurückgegeben.

  1. Da wir die ImageClassifier-Instanz vor der Verwendung initialisieren möchten, können Sie einen Init-Block hinzufügen, um setupDigitClassifier() aufzurufen.
init {
    setupDigitClassifier()
}
  1. Scrollen Sie abschließend zum Kommentar // SCHRITT 5: Funktion „classify“ erstellen und fügen Sie den folgenden Code hinzu. Diese Funktion nimmt eine Bitmap an, die in diesem Fall die gezeichnete Ziffer ist, wandelt sie in ein MediaPipe-Bildobjekt (MPImage) um und klassifiziert dieses Bild dann mit dem ImageClassifier. Außerdem wird aufgezeichnet, wie lange die Inferenz dauert, bevor die 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 auch schon mit der Hilfsdatei. Im nächsten Abschnitt führen Sie die letzten Schritte aus, um mit der Klassifizierung Ihrer gezogenen Zahlen zu beginnen.

4. Inferenz mit MediaPipe Tasks ausführen

Öffnen Sie dazu in Android Studio die Klasse DigitCanvasFragment, in der die gesamte Arbeit ausgeführt wird.

  1. Ganz unten in dieser Datei sollte der Kommentar // SCHRITT 6: Listener einrichten zu sehen sein. Hier fügen Sie die Funktionen „onResults()“ und „onError()“ hinzu, die mit dem Listener verknüpft sind.
// 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 hier die vom ImageClassifier empfangenen Ergebnisse angezeigt werden. Da dieser Callback von einem Hintergrund-Thread ausgelöst wird, müssen Sie Ihre UI-Aktualisierungen auch im UI-Thread von Android ausführen.

  1. Wenn Sie im vorherigen Schritt neue Funktionen aus einer Schnittstelle hinzufügen, müssen Sie auch die Implementierungsdeklaration oben in der Klasse hinzufügen.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. Oben im Code sollte der Kommentar // SCHRITT 7a Klassifikator initialisieren zu sehen sein. Hier platzieren Sie die Deklaration für die DigitClassifierHelper-Klasse.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Gehen Sie zu // SCHRITT 7b Klassifikator initialisieren. Sie können digitClassifierHelper in der Funktion „onViewCreated()“ 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. Dieser Codeblock löst die Klassifizierung aus, wenn Sie den Finger vom Zeichenbereich in der App heben.
// STEP 8a: classify
classifyDrawing()
  1. Suchen Sie abschließend nach dem Kommentar // STEP 8b classify, um die neue Funktion „classifyDrawing()“ hinzuzufügen. Dadurch wird eine Bitmap aus dem Canvas extrahiert und an die DigitClassifierHelper-Funktion übergeben, um die Klassifizierung durchzuführen und die Ergebnisse in der Schnittstellenfunktion „onResults()“ zu erhalten.
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. App bereitstellen und testen

Danach sollten Sie eine funktionierende App haben, die gezeichnete Ziffern auf dem Bildschirm klassifizieren kann. Sie können die App jetzt in einem Android-Emulator oder auf einem physischen Android-Gerät bereitstellen, 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 das Zeichenpad und prüfen Sie, ob die App sie erkennt. Es sollte sowohl die Ziffer anzeigen, die das Modell Ihrer Meinung nach gezeichnet hat, als auch, wie lange es gedauert hat, diese Ziffer vorherzusagen.

7f37187f8f919638.gif

6. Glückwunsch!

Geschafft! In diesem Codelab haben Sie gelernt, wie Sie einer Android-App die Bildklassifizierung hinzufügen und insbesondere, wie Sie mit dem MNIST-Modell handgeschriebene Ziffern klassifizieren.

Nächste Schritte

  • Nachdem Sie Ziffern klassifizieren können, können Sie Ihr eigenes Modell trainieren, um gezeichnete Buchstaben, Tiere oder eine unendliche Anzahl anderer Elemente zu klassifizieren. Die Dokumentation zum Trainieren eines neuen Bildklassifizierungsmodells mit dem MediaPipe-Modeller finden Sie auf der Seite developers.google.com/mediapipe.
  • Weitere Informationen zu den anderen MediaPipe-Aufgaben, die für Android verfügbar sind, wie die Erkennung von Gesichtslandmarken, die Gestenerkennung und die Audioklassifizierung.

Wir freuen uns schon auf die coolen Dinge, die ihr damit erstellt!