Utwórz odręczną aplikację do klasyfikacji cyfr na Androida przy użyciu Listy zadań MediaPipe

1. Wprowadzenie

Czym jest MediaPipe?

MediaPipe Solutions umożliwia stosowanie w aplikacjach rozwiązań wykorzystujących uczenie maszynowe. Zapewnia on platformę do konfigurowania gotowych potoków przetwarzania, które dostarczają użytkownikom natychmiastowe, angażujące i przydatne wyniki. Możesz nawet dostosować te rozwiązania za pomocą MediaPipe Model Maker, aby zaktualizować modele domyślne.

Klasyfikacja obrazów to jedno z kilku zadań związanych z uczeniem maszynowym w zakresie widzenia, które oferuje MediaPipe Solutions. Biblioteka MediaPipe Tasks jest dostępna na Androida, iOS, Pythona (w tym Raspberry Pi) i w internecie.

W tym ćwiczeniu w Codelabs zaczniesz od aplikacji na Androida, która umożliwia rysowanie cyfr na ekranie. Następnie dodasz funkcję klasyfikującą narysowane cyfry jako pojedynczą wartość od 0 do 9.

Czego się nauczysz

  • Jak dodać zadanie klasyfikacji obrazów do aplikacji na Androida za pomocą zadań MediaPipe.

Czego potrzebujesz

  • Zainstalowana wersja Android Studio (ten codelab został napisany i przetestowany w Android Studio Giraffe).
  • Urządzenie z Androidem lub emulator do uruchamiania aplikacji.
  • Podstawowa wiedza na temat tworzenia aplikacji na Androida (to nie jest „Hello World”, ale niewiele brakuje!).

2. Dodawanie zadań MediaPipe do aplikacji na Androida

Pobieranie aplikacji startowej na Androida

W tym ćwiczeniu zaczniesz od gotowego przykładu, który umożliwia rysowanie na ekranie. Aplikację startową znajdziesz w oficjalnym repozytorium przykładów MediaPipe tutaj. Sklonuj repozytorium lub pobierz plik ZIP, klikając kolejno Code (Kod) > Download ZIP (Pobierz ZIP).

Importowanie aplikacji do Android Studio

  1. Otwórz Android Studio.
  2. Na ekranie Witamy w Android Studio w prawym górnym rogu kliknij Otwórz.

a0b5b070b802e4ea.png

  1. Przejdź do miejsca, w którym sklonowano lub pobrano repozytorium, i otwórz katalog codelabs/digitclassifier/android/start.
  2. Sprawdź, czy wszystko otworzyło się prawidłowo, klikając zieloną strzałkę uruchamiania ( 7e15a9c9e1620fe7.png) w prawym górnym rogu Android Studio.
  3. Aplikacja powinna się otworzyć z czarnym ekranem, na którym możesz rysować, oraz przyciskiem Wyczyść, który umożliwia zresetowanie ekranu. Możesz rysować na tym ekranie, ale nie ma on wielu innych funkcji, więc zaczniemy to naprawiać.

11a0f6fe021fdc92.jpeg

Model

Gdy uruchomisz aplikację po raz pierwszy, zauważysz, że plik o nazwie mnist.tflite jest pobierany i przechowywany w katalogu assets aplikacji. Dla uproszczenia wzięliśmy znany model MNIST, który klasyfikuje cyfry, i dodaliśmy go do aplikacji za pomocą skryptu download_models.gradle w projekcie. Jeśli zdecydujesz się wytrenować własny model niestandardowy, np. model do rozpoznawania liter pisanych odręcznie, usuń plik download_models.gradle, usuń odwołanie do niego w pliku build.gradle na poziomie aplikacji i później zmień nazwę modelu w kodzie (zwłaszcza w pliku DigitClassifierHelper.kt).

Aktualizacja pliku build.gradle

Zanim zaczniesz korzystać z MediaPipe Tasks, musisz zaimportować bibliotekę.

  1. Otwórz plik build.gradle znajdujący się w module app, a następnie przewiń w dół do bloku dependencies.
  2. U dołu tego bloku powinien pojawić się komentarz // STEP 1 Dependency Import.
  3. Zastąp tę linię tym kodem:
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Aby pobrać tę zależność, kliknij przycisk Sync Now (Synchronizuj teraz) widoczny na banerze u góry Android Studio.

3. Tworzenie pomocniczego klasyfikatora cyfr zadań MediaPipe

W następnym kroku wypełnisz klasę, która wykona większość pracy związanej z klasyfikacją uczenia maszynowego. Otwórz plik DigitClassifierHelper.kt i zacznijmy!

  1. Znajdź u góry zajęć komentarz // STEP 2 Create listener (// KROK 2 Utwórz odbiornik).
  2. Zastąp ten wiersz poniższym kodem. Spowoduje to utworzenie detektora, który będzie używany do przekazywania wyników z klasy DigitClassifierHelper z powrotem do miejsca, w którym są one nasłuchiwane (w tym przypadku będzie to klasa DigitCanvasFragment, ale do tego dojdziemy wkrótce).
// STEP 2 Create listener

interface DigitClassifierListener {
    fun onError(error: String)
    fun onResults(
        results: ImageClassifierResult,
        inferenceTime: Long
    )
}
  1. Musisz też zaakceptować DigitClassifierListener jako opcjonalny parametr klasy:
class DigitClassifierHelper(
    val context: Context,
    val digitClassifierListener: DigitClassifierListener?
) {
  1. Przejdź do wiersza // STEP 3 define classifier i dodaj ten wiersz, aby utworzyć zmienną zastępczą dla klasyfikatora obrazów, który będzie używany w tej aplikacji:

// KROK 3. Zdefiniuj klasyfikator

private var digitClassifier: ImageClassifier? = null
  1. Dodaj poniższą funkcję w miejscu, w którym widzisz komentarz // STEP 4 set up classifier:
// 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)
    }
}

W powyższej sekcji dzieje się kilka rzeczy, więc przyjrzyjmy się mniejszym fragmentom, aby naprawdę zrozumieć, co się dzieje.

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

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

Ten blok określa parametry używane przez klasyfikator obrazów. Obejmuje to model przechowywany w aplikacji (mnist.tflite) w sekcji BaseOptions i RunningMode w sekcji ImageClassifierOptions, który w tym przypadku ma wartość IMAGE, ale dostępne są też opcje VIDEO i LIVE_STREAM. Inne dostępne parametry to MaxResults, który ogranicza model do zwracania maksymalnej liczby wyników, oraz ScoreThreshold, który określa minimalny poziom ufności modelu wymagany do zwrócenia wyniku.

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

Po utworzeniu opcji konfiguracji możesz utworzyć nowy klasyfikator obrazów, przekazując kontekst i opcje. Jeśli podczas tego procesu inicjowania wystąpi błąd, zostanie on zwrócony przez DigitClassifierListener.

  1. Chcemy zainicjować klasyfikator obrazów przed jego użyciem, więc możesz dodać blok init, aby wywołać funkcję setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Na koniec przewiń w dół do komentarza // STEP 5 create classify function i dodaj ten kod. Ta funkcja przyjmuje obiekt Bitmap, który w tym przypadku jest narysowaną cyfrą, przekształca go w obiekt MediaPipe Image (MPImage), a następnie klasyfikuje ten obraz za pomocą klasy ImageClassifier. Rejestruje też czas trwania wnioskowania, po czym zwraca wyniki za pomocą interfejsu 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)
    }
}

To wszystko, co musisz wiedzieć o pliku pomocniczym. W następnej sekcji wykonasz ostatnie kroki, aby rozpocząć klasyfikowanie narysowanych cyfr.

4. Uruchamianie wnioskowania za pomocą zadań MediaPipe

Aby rozpocząć tę sekcję, otwórz w Android Studio klasę DigitCanvasFragment, w której będziesz pracować.

  1. Na samym dole tego pliku powinien znajdować się komentarz // STEP 6 Set up listener. W tym miejscu dodasz funkcje onResults() i onError() powiązane z detektorem.
// 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())
    }
}

Funkcja onResults() jest szczególnie ważna, ponieważ wyświetla wyniki otrzymane z klasyfikatora obrazów. Ponieważ to wywołanie zwrotne jest wywoływane z wątku w tle, musisz też uruchamiać aktualizacje interfejsu w wątku interfejsu Androida.

  1. Podczas dodawania nowych funkcji z interfejsu w kroku powyżej musisz też dodać deklarację implementacji u góry klasy.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. U góry klasy powinien być widoczny komentarz // STEP 7a Initialize classifier. W tym miejscu umieść deklarację klasy DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Przejdź do // STEP 7b Initialize classifier (Zainicjuj klasyfikator). W funkcji onViewCreated() możesz zainicjować digitClassifierHelper.
// 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. W ostatnich krokach znajdź komentarz // STEP 8a*: classify* i dodaj ten kod, aby wywołać nową funkcję, którą za chwilę dodasz. Ten blok kodu spowoduje klasyfikację, gdy uniesiesz palec z obszaru rysowania w aplikacji.
// STEP 8a: classify
classifyDrawing()
  1. Na koniec znajdź komentarz // STEP 8b classify, aby dodać nową funkcję classifyDrawing(). Spowoduje to wyodrębnienie mapy bitowej z obszaru rysowania, a następnie przekazanie jej do klasy DigitClassifierHelper w celu przeprowadzenia klasyfikacji i otrzymania wyników w funkcji interfejsu onResults().
// STEP 8b classify
private fun classifyDrawing() {
    val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
    digitClassifierHelper.classify(bitmap)
}

5. Wdrażanie i testowanie aplikacji

Po wykonaniu tych czynności powinna działać aplikacja, która potrafi klasyfikować narysowane na ekranie cyfry. Aby przetestować aplikację, wdróż ją w emulatorze Androida lub na fizycznym urządzeniu z Androidem.

  1. Aby uruchomić aplikację, na pasku narzędzi Android Studio kliknij Uruchom ( 7e15a9c9e1620fe7.png).
  2. Narysuj dowolną cyfrę na tablecie graficznym i sprawdź, czy aplikacja ją rozpozna. Powinien wyświetlać cyfrę, która według modelu została narysowana, a także czas potrzebny na jej rozpoznanie.

7f37187f8f919638.gif

6. Gratulacje!

Udało się! W tym ćwiczeniu dowiesz się, jak dodać klasyfikację obrazów do aplikacji na Androida, a w szczególności jak klasyfikować odręcznie narysowane cyfry za pomocą modelu MNIST.

Dalsze kroki

  • Teraz, gdy możesz klasyfikować cyfry, możesz wytrenować własny model do klasyfikowania narysowanych liter, zwierząt lub nieskończonej liczby innych elementów. Dokumentację dotyczącą trenowania nowego modelu klasyfikacji obrazów za pomocą MediaPipe Model Maker znajdziesz na stronie developers.google.com/mediapipe.
  • Dowiedz się więcej o innych zadaniach MediaPipe dostępnych na Androida, w tym o wykrywaniu punktów charakterystycznych twarzy, rozpoznawaniu gestów i klasyfikacji dźwięku.

Czekamy na Twoje ciekawe projekty.