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

1. Wprowadzenie

Co to jest MediaPipe?

MediaPipe Solutions umożliwia stosowanie w aplikacjach rozwiązań systemów uczących się. Udostępnia platformę do konfigurowania gotowych potoków przetwarzania, które dostarczają użytkownikom natychmiastowe, angażujące i przydatne dane wyjściowe. Możesz nawet dostosować te rozwiązania za pomocą Kreatora modeli MediaPipe, aby zaktualizować modele domyślne.

Klasyfikacja obrazów to jedno z kilku zadań związanych z ML wizją, które oferuje MediaPipe Solutions. Lista zadań MediaPipe jest dostępna na Androida i iOS, w Pythonie (w tym na Raspberry Pi!) i w internecie.

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

Czego się nauczysz

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

Czego potrzebujesz

  • Zainstalowana wersja Android Studio (to ćwiczenie z programowania zostało napisane i przetestowane w Android Studio Giraffe).
  • Urządzenie z Androidem lub emulator do uruchamiania aplikacji.
  • Podstawowa znajomość programowania na Androida (nie jest to „Hello World”, ale nie jest to zbyt daleko).

2. Dodawanie zadań MediaPipe do aplikacji na Androida

Pobierz aplikację startową na Androida

To ćwiczenie w programowaniu rozpocznie się od gotowego przykładu, który pozwala rysować na ekranie. Aplikację początkową znajdziesz w oficjalnym repozytorium MediaPipe Samples tutaj. Skopiuj repozytorium lub pobierz plik ZIP, klikając Kod > Pobierz plik ZIP.

Importowanie aplikacji do Android Studio

  1. Otwórz Android Studio.
  2. W prawym górnym rogu ekranu Welcome to Android Studio (Witamy w Android Studio) wybierz Otwórz.

a0b5b070b802e4ea.png

  1. Przejdź do miejsca, z którego zostało sklonowane lub pobrane repozytorium, i otwórz katalog Codelabs/digitclassifier/android/start.
  2. Sprawdź, czy wszystko zostało otwarte prawidłowo – w tym celu kliknij zieloną strzałkę Uruchom ( 7E15a9c9e1620fe7.png) w prawym górnym rogu Android Studio.
  3. Po otwarciu aplikacji powinien wyświetlić się czarny ekran, na którym możesz rysować, oraz przycisk Wyczyść, który służy do resetowania tego ekranu. Możesz rysować na tym ekranie, ale nie robi on wielu innych rzeczy, dlatego teraz już zaczynamy to naprawić.

11a0f6fe021fdc92.jpeg

Model

Po pierwszym uruchomieniu aplikacji możesz zauważyć, że plik o nazwie mnist.tflite jest pobierany i zapisywany w katalogu assets aplikacji. Dla uproszczenia wykorzystaliśmy już 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, na przykład model do obsługi odręcznych liter, usuń plik download_models.gradle, usuń odwołanie do niego z pliku build.gradle na poziomie aplikacji, a następnie zmień nazwę modelu w kodzie (zwłaszcza w pliku DigitClassifierHelper.kt).

Aktualizacja pliku build.gradle

Zanim zaczniesz używać Listy zadań MediaPipe, musisz zaimportować bibliotekę.

  1. Otwórz plik build.gradle znajdujący się w module aplikacji i przewiń w dół do bloku dependencies.
  2. U dołu tego bloku powinien być widoczny komentarz // KROK 1. Import zależności.
  3. Zastąp ten wiersz tą implementacją
implementation("com.google.mediapipe:tasks-vision:latest.release")
  1. Aby pobrać tę zależność, kliknij przycisk Synchronizuj teraz widoczny na banerze u góry Android Studio.

3. Utwórz pomocnik klasyfikatora cyfr MediaPipe Tasks

W następnym kroku wypełnisz zajęcia, które wykonają najcięższą pracę związaną z klasyfikacją systemów uczących się. Otwórz plik DigitClassifierHelper.kt i zaczynajmy.

  1. U góry klasy znajdź komentarz o treści // KROK 2. Utwórz detektor
  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 do dowolnego miejsca, w którym będzie nasłuchiwać tych wyników (w tym przypadku będzie to klasa DigitCanvasFragment, ale wkrótce tam dotrzemy).
// 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. Przechodząc do wiersza // KROK 3 zdefiniuj klasyfikator, dodaj ten wiersz, by utworzyć obiekt zastępczy dla ImageClassifier, który będzie używany w tej aplikacji:

// KROK 3 zdefiniuj klasyfikator

private var digitClassifier: ImageClassifier? = null
  1. Dodaj funkcję w miejscu, w którym widzisz komentarz. // KROK 4 skonfiguruj klasyfikator:
// 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 omówiliśmy kilka spraw, więc przyjrzyjmy się mniejszej liczbie fragmentów, aby lepiej 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 definiuje parametry używane przez obiekt ImageClassifier. Obejmuje to model zapisany w aplikacji (mnist.tflite) w BaseOptions i w trybie RunningMode w elemencie ImageClassifierOptions, czyli w tym przypadku jako „IMAGE”, ale dodatkowe dostępne są opcje WIDEO i TRANSMISJA_NA ŻYWO. Inne dostępne parametry to MaxResults, który ogranicza model do zwracania maksymalnej liczby wyników, oraz Próg wyniku, który określa minimalny poziom ufności, jaki model musi mieć dla wyniku przed jego zwróceniem.

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 konfiguracyjnych możesz utworzyć nowy obiekt ImageClassifier, podając kontekst i opcje. Jeśli podczas tego procesu inicjowania coś pójdzie nie tak, funkcja DigitClassifierListener zwróci błąd.

  1. Ponieważ chcemy zainicjować obiekt ImageClassifier, zanim zostanie użyty, możesz dodać blok init, aby wywołać metodę setupDigitClassifier().
init {
    setupDigitClassifier()
}
  1. Przewiń w dół do komentarza // KROK 5 utwórz funkcję klasyfikacji i dodaj poniższy kod. Ta funkcja akceptuje Bitmap, czyli w tym przypadku narysowaną cyfrę, przekonwertuje ją na obiekt MediaPipe Image (MPImage), a następnie sklasyfikuje obraz przy użyciu ImageClassifier, a także zarejestruje czas wnioskowania, zanim zwróci wyniki przez odbiornik 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, jeśli chodzi o plik pomocniczy. W następnej sekcji przeprowadzisz ostatnie kroki, aby rozpocząć klasyfikowanie rysowanych liczb.

4. Uruchamianie wnioskowania za pomocą zadań MediaPipe

Możesz rozpocząć tę sekcję od otwarcia klasy DigitCanvasFragment w Android Studio, gdzie wykonujesz całą pracę.

  1. Na samym dole pliku powinien być widoczny komentarz o treści // KROK 6. Skonfiguruj detektor. 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())
    }
}

Metoda onResults() jest szczególnie ważna, ponieważ służy do wyświetlania wyników otrzymanych z klasy ImageClassifier. To wywołanie zwrotne jest wywoływane z wątku w tle, dlatego musisz też uruchamiać aktualizacje interfejsu w wątku interfejsu Androida.

  1. Gdy dodajesz nowe funkcje z poziomu interfejsu (patrz krok powyżej), musisz też dodać deklarację implementacji na górze klasy.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
  1. U góry klasy powinien być widoczny komentarz // KROK 7a Zainicjuj klasyfikator. Tutaj umieszczasz deklarację funkcji DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
  1. Przejdźmy do // KROK 7b Zainicjuj klasyfikator. Możesz zainicjować klasę digitClassifierHelper w ramach funkcji 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. Przejdź do ostatnich kroków i znajdź komentarz // KROK 8a*: classify* i dodaj ten kod, aby wywołać nową funkcję, którą za chwilę dodasz. Ten blok kodu aktywuje klasyfikację, gdy uniesiesz palec z obszaru rysowania w aplikacji.
// STEP 8a: classify
classifyDrawing()
  1. Na koniec poszukaj komentarza // KROK 8b classify, aby dodać nową funkcję classifyRysunków(). Spowoduje to wyodrębnienie bitmapy z obszaru roboczego, a następnie przekazanie jej do funkcji DigitClassifierHelper w celu przeprowadzenia klasyfikacji w celu 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 tym wszystkim powinna być już działająca aplikacja, która klasuje narysowane cyfry na ekranie. Aby przetestować aplikację, wdróż ją w emulatorze Androida lub na fizycznym urządzeniu z Androidem.

  1. Aby uruchomić aplikację, kliknij Uruchom ( 7E15a9c9e1620fe7.png) na pasku narzędzi Android Studio.
  2. Narysuj dowolną cyfrę na klawiaturze do rysowania i sprawdź, czy aplikacja ją rozpozna. Powinien zawierać cyfrę, która według modelu została wyrysowana, oraz czas potrzebny na przewidzenie tej cyfry.

7f37187f8f919638.gif

6. Gratulacje!

Udało się! Dzięki temu ćwiczeniu w Codelabs wiesz, jak dodać klasyfikację obrazów do aplikacji na Androida, a w szczególności jak klasyfikować ręcznie rysowane cyfry przy użyciu modelu MNIST.

Dalsze kroki

  • Skoro już potrafisz klasyfikować cyfry, możesz wytrenować własny model do klasyfikowania narysowanych liter lub klasyfikowania zwierząt bądź nieskończonej liczby innych przedmiotów. Dokumentację dotyczącą trenowania nowego modelu klasyfikacji obrazów za pomocą Kreatora modeli MediaPipe znajdziesz na stronie developers.google.com/mediapipe.
  • Poznaj inne zadania MediaPipe dostępne na Androida, w tym wykrywanie twarzy, rozpoznawanie gestów i klasyfikację dźwięku.

Czekamy na wszystkie Twoje niesamowite rzeczy!