Generowanie obrazów na urządzeniu z Androidem za pomocą 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. Wiele z tych rozwiązań można nawet dostosować za pomocą Kreatora modeli MediaPipe, aby zaktualizować modele domyślne.

Generowanie tekstu na obraz to jedno z kilku zadań związanych z systemami uczącymi się, które oferuje MediaPipe Solutions.

W tym ćwiczeniu w Codelabs zaczniesz od aplikacji na Androida, która w większości nagości, przez kolejne etapy, aż będziesz w stanie generować nowe obrazy bezpośrednio na urządzeniu z Androidem.

Czego się nauczysz

  • Jak wdrożyć generowanie tekstu na obraz, które działa lokalnie w aplikacji na Androida za pomocą Listy 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 i co najmniej 8 GB pamięci RAM.
  • Podstawowa wiedza z zakresu programowania na Androida i umiejętność uruchamiania gotowego skryptu w języku Python.

2. Dodawanie zadań MediaPipe do aplikacji na Androida

Pobierz aplikację startową na Androida

To ćwiczenie w Codelabs zacznie od gotowego przykładu zawierającego interfejs użytkownika, który będzie używany do podstawowej wersji generowania obrazów. 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 repozytorium zostało skopiowane lub pobrane, i otwórz codelabs/image_generation_basic/android/start.
  2. Na tym etapie aplikacja nie powinna się skompilować, ponieważ nie dołączono jeszcze zależności MediaPipe Tasks.

Poprawisz aplikację i uruchomisz ją. Aby to zrobić, otwórz plik build.gradle i przewiń w dół do sekcji // Krok 1. Dodaj zależność. Dodaj w nim poniższy wiersz, a potem kliknij przycisk Synchronizuj teraz, który pojawi się na banerze u góry Android Studio.

// Step 1 - Add dependency
implementation 'com.google.mediapipe:tasks-vision-image-generator:latest.release'

Po zakończeniu synchronizacji sprawdź, czy wszystko zostało poprawnie otwarte i zainstalowane, klikając zieloną strzałkę Uruchom ( 7E15a9c9e1620fe7.png) w prawym górnym rogu Android Studio. Aplikacja powinna się otworzyć na ekranie z 2 przyciskami i przyciskiem INITIALIZE (ZAinicjuj). Gdy klikniesz ten przycisk, od razu otworzy się oddzielny interfejs zawierający prompt tekstowy i inne opcje oraz przycisk GENERUJ.

83c31de8e8a320ee.png 78b8765e832024e3.png

Niestety to już wszystko w zakresie aplikacji startowej, więc czas dowiedzieć się, jak ją dokończyć i zacząć generować nowe obrazy na urządzeniu.

3. Konfigurowanie generatora obrazów

W tym przykładzie większość czynności związanych z generowaniem obrazów będzie wykonywana w pliku ImageGenerationHelper.kt. Po otwarciu tego pliku w górnej części klasy zobaczysz zmienną o nazwie imageGenerator. To jest obiekt Task, który wykona najcięższą pracę w aplikacji do generowania obrazów.

Tuż pod tym obiektem będzie widoczna funkcja o nazwie initializeImageGenerator() z następującym komentarzem: // Krok 2 – zainicjowanie generatora obrazów. Jak możesz się domyślić, w tym miejscu zainicjujesz obiekt ImageGenerator. Zastąp treść tej funkcji tym kodem, aby ustawić ścieżkę modelu generowania obrazów i zainicjować obiekt ImageGenerator:

// Step 2 - initialize the image generator
val options = ImageGeneratorOptions.builder()
    .setImageGeneratorModelDirectory(modelPath)
    .build()

imageGenerator = ImageGenerator.createFromOptions(context, options)

Poniżej widoczna jest inna funkcja o nazwie setInput(). Akceptuje 3 parametry: ciąg promptu, który będzie używany do zdefiniowania wygenerowanego obrazu, liczbę iteracji, które Zadanie powinno wykonać podczas generowania nowego obrazu, oraz wartość seed, której można użyć do utworzenia nowych wersji obrazu na podstawie tego samego promptu podczas generowania tego samego obrazu, gdy używane jest to samo źródło. Ta funkcja służy do ustawiania tych parametrów początkowych dla generatora obrazów, gdy próbujesz utworzyć obraz, który wyświetla kroki pośrednie.

Zastąp treść setInput() (w której znajdziesz komentarz // Krok 3 – zaakceptuj dane wejściowe) tym wierszem:

// Step 3 - accept inputs
imageGenerator.setInputs(prompt, iteration, seed)

Kolejne 2 kroki to miejsce, w którym odbywa się generacja. Funkcja generate() akceptuje te same dane wejściowe co funkcja setInput, ale tworzy obraz jako jednorazowe wywołanie, które nie zwraca żadnych pośrednich obrazów kroków. Treść tej funkcji (obejmującą komentarz // Krok 4 – wygeneruj bez wyświetlania iteracji) możesz zastąpić następującym fragmentem:

// Step 4 - generate without showing iterations
val result = imageGenerator.generate(prompt, iteration, seed)
val bitmap = BitmapExtractor.extract(result?.generatedImage())
return bitmap

Pamiętaj, że to zadanie jest wykonywane synchronicznie, więc musisz je wywołać z wątku w tle. Więcej informacji na ten temat znajdziesz w dalszej części tego ćwiczenia z programowania.

Ostatnim krokiem w tym pliku jest wypełnienie funkcji generate() (oznaczonej jako Krok 5). Będzie on przyjmował parametr informujący o tym, czy powinien zwrócić obraz pośredniczący, czy nie w przypadku pojedynczego kroku generowania, które zostanie wykonane przy użyciu funkcji generate() ImageGenerator. Zastąp treść funkcji tym kodem:

// Step 5 - generate with iterations
val result = imageGenerator.execute(showResult)

if (result == null || result.generatedImage() == null) {
    return Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888)
        .apply {
            val canvas = Canvas(this)
            val paint = Paint()
            paint.color = Color.WHITE
            canvas.drawPaint(paint)
        }
}

val bitmap =
    BitmapExtractor.extract(result.generatedImage())

return bitmap

To wszystko, jeśli chodzi o plik pomocniczy. W następnej sekcji wypełnisz plik ViewModel, który obsługuje logikę tego przykładu.

4. Połączenie aplikacji

Plik MainViewModel będzie obsługiwać stany interfejsu i inne funkcje logiczne związane z tą przykładową aplikacją. Otwórz ją teraz.

U góry pliku powinien być widoczny komentarz // Krok 6 – ustaw ścieżkę modelu. W tym miejscu wskażesz aplikacji, gdzie ma znaleźć pliki modelu, które są niezbędne do wygenerowania obrazu. W tym przykładzie ustawisz wartość /data/local/tmp/image_generator/bins/.

// Step 6 - set model path
private val MODEL_PATH = "/data/local/tmp/image_generator/bins/"

Następnie przewiń w dół do funkcji generateImage(). U dołu tej funkcji zobaczysz zarówno krok 7, jak i krok 8, które będą służyć do generowania obrazów odpowiednio ze zwróconymi iteracjami lub bez żadnej. Ponieważ obie te operacje są realizowane synchronicznie, zauważysz, że są umieszczone w korutynie. Możesz zacząć od zastąpienia // Krok 7 – Wygeneruj bez pokazywania iteracji tym blokiem kodu, by wywołać generate() z pliku ImageGenerationHelper, a następnie zaktualizować stan interfejsu.

// Step 7 - Generate without showing iterations
val result = helper?.generate(prompt, iteration, seed)
_uiState.update {
    it.copy(outputBitmap = result)
}

Krok 8 staje się nieco trudniejszy. Ponieważ do generowania obrazu funkcja Wykonaj() wykonuje tylko jeden krok, a nie wszystkie kroki, trzeba będzie wywołać każdy z nich osobno w pętli. Musisz też określić, czy bieżący krok powinien być wyświetlany użytkownikowi. Jeśli chcesz wyświetlić bieżącą iterację, zaktualizujesz stan UI. Teraz możesz to wszystko zrobić.

// Step 8 - Generate with showing iterations
helper?.setInput(prompt, iteration, seed)
for (step in 0 until iteration) {
    isDisplayStep =
        (displayIteration > 0 && ((step + 1) % displayIteration == 0))
    val result = helper?.execute(isDisplayStep)

    if (isDisplayStep) {
        _uiState.update {
            it.copy(
                outputBitmap = result,
                generatingMessage = "Generating... (${step + 1}/$iteration)",
            )
        }
    }
}

W tym momencie powinno być możliwe zainstalowanie aplikacji, zainicjowanie generatora obrazów i utworzenie nowego obrazu na podstawie promptu tekstowego.

... ale teraz aplikacja ulega awarii przy próbie zainicjowania generatora obrazów. Dzieje się tak, ponieważ musisz skopiować pliki modelu na urządzenie. Aby uzyskać najbardziej aktualne informacje o sprawdzonych modelach innych firm, przekonwertować je na potrzeby tego zadania MediaPipe i skopiować na urządzenie, zapoznaj się z tą sekcją w oficjalnej dokumentacji.

Oprócz kopiowania plików bezpośrednio na urządzenie, którego używasz do programowania, możesz też skonfigurować Firebase Storage, aby pobierał niezbędne pliki bezpośrednio na urządzenie użytkownika w czasie działania aplikacji.

5. Wdrażanie i testowanie aplikacji

W końcu powinna być już działająca aplikacja, która akceptuje prompty tekstowe i generuje nowe obrazy całkowicie na urządzeniu. Możesz wdrożyć aplikację na fizycznym urządzeniu z Androidem, by ją przetestować. Warto jednak wypróbować ją na urządzeniu z co najmniej 8 GB pamięci.

  1. Aby uruchomić aplikację, kliknij Uruchom ( 7E15a9c9e1620fe7.png) na pasku narzędzi Android Studio.
  2. Wybierz typ kroków generowania (końcowe lub z iteracjami), a następnie kliknij przycisk INITIALIZE (ZAinicjuj).
  3. Na następnym ekranie ustaw dowolne właściwości i kliknij przycisk WYGENERUJ, aby zobaczyć, co wyświetli narzędzie.

e46cfaeb9d3fc235.gif

6. Gratulacje!

Udało się! Dzięki tym ćwiczeniom w Codelabs wiesz, jak dodać do aplikacji na Androida generowanie tekstu na obraz na urządzeniu.

Dalsze kroki

Zadanie generowania obrazów pozwala wykonywać więcej czynności, na przykład

  • za pomocą obrazu podstawowego do struktury wygenerowanych obrazów za pomocą wtyczek lub wytrenowanie własnych dodatkowych wag LoRA za pomocą Vertex AI.
  • Pamięć Firebase umożliwia pobieranie plików modelu z urządzenia bez konieczności użycia narzędzia ADB.

Nie możemy się doczekać, aby poznać wszystkie fajne rzeczy, które stworzysz w tym eksperymentalnym zadaniu. Wkrótce pojawi się jeszcze więcej ćwiczeń z programowania i materiałów od zespołu MediaPipe.