1. Wprowadzenie
Czym jest MediaPipe?
MediaPipe Solutions umożliwia stosowanie w aplikacjach rozwiązań wykorzystujących systemy uczące się. Udostępnia ona ramy do konfigurowania gotowych przepływów przetwarzania, które dostarczają użytkownikom natychmiastowych, atrakcyjnych i przydatnych danych wyjściowych. 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ń wizyjnych opartych na uczeniu maszynowym, które oferuje MediaPipe Solutions. MediaPipe Tasks jest dostępny na platformach Android, iOS, Python (w tym Raspberry Pi) oraz w internecie.
W tym Codelab zaczniesz od aplikacji na Androida, która umożliwia rysowanie cyfr na ekranie. Następnie dodasz funkcję, która klasyfikuje rysowane cyfry jako pojedynczą wartość od 0 do 9.
Czego się nauczysz
- Jak uwzględnić zadanie klasyfikacji obrazu w aplikacji na Androida za pomocą MediaPipe Tasks.
Czego potrzebujesz
- Zainstalowana wersja Android Studio (ten warsztat został napisany i przetestowany za pomocą Android Studio Giraffe).
- urządzenie z Androidem lub jego emulator, na którym można uruchomić aplikację.
- podstawowa wiedza o programowaniu na Androida (nie jest to „Witaj, światku”, ale nie jest też zbyt odległa od tego)
2. Dodawanie zadań MediaPipe do aplikacji na Androida
Pobierz aplikację startową na Androida
To ćwiczenie w Codelab rozpocznie się od gotowego przykładu, który umożliwia rysowanie na ekranie. Tę aplikację startową znajdziesz w oficjalnym repozytorium przykładów MediaPipe tutaj. Skopiuj repozytorium lub pobierz plik zip, klikając Kod > Pobierz plik ZIP.
Zaimportuj aplikację do Android Studio
- Otwórz Android Studio.
- Na ekranie Witamy w Android Studio w prawym górnym rogu kliknij Otwórz.
- Przejdź do folderu, do którego sklonowano lub pobierano repozytorium, i otwórz katalog codelabs/digitclassifier/android/start.
- Aby sprawdzić, czy wszystko zostało otwarte prawidłowo, kliknij zieloną strzałkę uruchomienia (
) w prawym górnym rogu Android Studio.
- Aplikacja powinna otworzyć się na czarnym ekranie, na którym możesz rysować, oraz z przyciskiem Wyczyść, aby zresetować ekran. Możesz na nim rysować, ale nie ma ono innych funkcji. Zaczniemy to naprawiać.
Model
Podczas pierwszego uruchomienia aplikacji możesz zauważyć, że plik o nazwie mnist.tflite został pobrany i zapisany w katalogu assets aplikacji. W celu uproszczenia procesu użyliśmy już znanego modelu MNIST, który klasyfikuje cyfry, i dodaliśmy go do aplikacji za pomocą skryptu download_models.gradle w projekcie. Jeśli zdecydujesz się na wytrenowanie własnego modelu niestandardowego, np. do rozpoznawania pisma odręcznego, usuń plik download_models.gradle, usuń odwołanie do niego w pliku build.gradle na poziomie aplikacji i zmieniaj nazwę modelu w późniejszych częściach kodu (w szczególności w pliku DigitClassifierHelper.kt).
Aktualizacja pliku build.gradle
Zanim zaczniesz korzystać z MediaPipe Tasks, musisz zaimportować bibliotekę.
- Otwórz plik build.gradle w module aplikacji, a potem przewiń w dół do bloku dependencies.
- U dołu tego bloku powinien pojawić się komentarz // KROK 1. Import zależności.
- Zastąp ten wiersz następującą implementacją
implementation("com.google.mediapipe:tasks-vision:latest.release")
- Aby pobrać tę zależność, kliknij przycisk Synchronizuj teraz widoczny w banerze u góry Android Studio.
3. Tworzenie pomocnika klasyfikatora cyfr MediaPipe Tasks
W następnym kroku wypełnisz klasę, która wykona najcięższą pracę w przypadku klasyfikacji za pomocą uczenia maszynowego. Otwórz plik DigitClassifierHelper.kt i zacznij.
- Znajdź u góry zajęć komentarz // KROK 2. Utwórz słuchacza.
- Zastąp ten wiersz tym kodem. Spowoduje to utworzenie listenera, który będzie używany do przekazywania wyników z klasy DigitClassifierHelper z powrotem do miejsca, które na nie czeka (w tym przypadku będzie to klasa DigitCanvasFragment, ale o tym za chwilę).
// STEP 2 Create listener
interface DigitClassifierListener {
fun onError(error: String)
fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
)
}
- Musisz też zaakceptować klasę DigitClassifierListener jako opcjonalny parametr klasy:
class DigitClassifierHelper(
val context: Context,
val digitClassifierListener: DigitClassifierListener?
) {
- Przejdź do wiersza // KROK 3. Zdefiniuj klasyfikator i dodaj ten wiersz, aby utworzyć miejsce zastępcze dla ImageClassifier, który będzie używany w tej aplikacji:
// KROK 3. Definiowanie klasyfikatora
private var digitClassifier: ImageClassifier? = null
- Dodaj tę funkcję w miejscu komentarza // 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żej opisanej sekcji występuje kilka różnych działań, więc przyjrzyjmy się im bliżej, 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 ImageClassifier. Obejmuje to model zapisany w aplikacji (mnist.tflite) w sekcji BaseOptions oraz tryb działania w sekcji ImageClassifierOptions, który w tym przypadku to 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 przed zwróceniem 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 coś pójdzie nie tak podczas procesu inicjalizacji, zostanie zwrócony błąd za pomocą klasy DigitClassifierListener.
- Chcemy zainicjować klasyfikator obrazów przed jego użyciem, więc możesz dodać blok init, aby wywołać funkcję setupDigitClassifier().
init {
setupDigitClassifier()
}
- Na koniec przewiń w dół do komentarza // KROK 5. Utwórz funkcję klasyfikacji i dodaj ten kod. Ta funkcja przyjmie Bitmapę, która w tym przypadku jest narysowaną cyfrą, przekształci ją w obiekt MediaPipe Image (MPImage), a potem sklasyfikuje ten obraz za pomocą klasyfikatora obrazu (ImageClassifier), a także zarejestruje, ile czasu zajmuje wnioskowanie, zanim zwróci te wyniki za pomocą klasy 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 dotyczy pliku pomocniczego. W następnej sekcji wykonasz ostatnie czynności, aby rozpocząć klasyfikowanie narysowanych cyfr.
4. Wykonywanie wnioskowania za pomocą Listy zadań MediaPipe
Aby rozpocząć tę sekcję, otwórz w Android Studio klasę DigitCanvasFragment, w której będziesz wykonywać wszystkie czynności.
- U dołu tego pliku powinien znajdować się komentarz // KROK 6. Skonfiguruj odsłuchiwanie. Tutaj dodasz powiązane z odbiorem funkcje onResults() i onError().
// 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 ImageClassifier. Ponieważ ten wywołanie zwrotne jest wywoływane z wątku w tle, musisz też uruchamiać aktualizacje interfejsu użytkownika na wątku interfejsu użytkownika Androida.
- Gdy w powyższym kroku dodajesz nowe funkcje z interfejsu, musisz też umieścić deklarację implementacji u góry klasy.
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- U góry zajęć powinien pojawić się komentarz // KROK 7a Inicjalizuj klasyfikator. Tutaj umieścisz deklarację dla funkcji DigitClassifierHelper.
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- W sekcji // KROK 7b Inicjalizacja klasyfikatora możesz zainicjować funkcję digitClassifierHelper w 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
)
- W ostatnich krokach odszukaj komentarz // KROK 8a*: classify* i dodaj podany niżej kod, aby wywołać nową funkcję, którą dodasz za chwilę. Ten blok kodu spowoduje uruchomienie klasyfikacji, gdy uniesiesz palec z obszaru rysunku w aplikacji.
// STEP 8a: classify
classifyDrawing()
- Na koniec odszukaj komentarz // KROK 8b classify, aby dodać nową funkcję classifyDrawing(). Spowoduje to wyodrębnienie bitmapy z płótna, a następnie przekazanie jej do funkcji 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 wszystkich tych czynności powinnaś mieć działającą aplikację, która potrafi klasyfikować rysowane na ekranie cyfry. Przeprowadź wdrożenie aplikacji w emulatorze Androida lub na fizycznym urządzeniu z Androidem, aby ją przetestować.
- Aby uruchomić aplikację, na pasku narzędzi Android Studio kliknij Uruchom (
).
- Narysuj dowolną cyfrę na bloku rysunkowym i sprawdź, czy aplikacja ją rozpozna. Powinien on zawierać cyfrę, którą według modelu została wylosowana, oraz czas potrzebny na jej przewidzenie.
6. Gratulacje!
Udało się! Z tego ćwiczenia z programowania dowiesz się, jak dodać do aplikacji na Androida klasyfikację obrazów, a w szczególności jak klasyfikować rysowane odręcznie cyfry za pomocą modelu MNIST.
Dalsze kroki
- Teraz, gdy potrafisz klasyfikować cyfry, możesz wytrenować własny model do klasyfikowania narysowanych liter, zwierząt lub nieskończonej liczby innych obiektó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, takich jak wykrywanie punktów orientacyjnych twarzy, rozpoznawanie gestów i klasyfikacja dźwięku.
Nie możemy się doczekać, aż zobaczymy Twoje projekty.