Segmentacja obrazów w czasie rzeczywistym na Androidzie z użyciem LiteRT

1. Zanim zaczniesz

Wpisywanie kodu to świetny sposób na wyrobienie pamięci mięśniowej i pogłębienie wiedzy. Kopiowanie i wklejanie może oszczędzać czas, ale inwestowanie w tę praktykę może w dłuższej perspektywie zwiększyć wydajność i poprawić umiejętności kodowania.

Z tego samouczka dowiesz się, jak utworzyć aplikację na Androida, która wykonuje segmentację obrazu w czasie rzeczywistym na żywym obrazie z kamery za pomocą nowego środowiska wykonawczego Google dla TensorFlow Lite, czyli LiteRT. Zaczniesz od prostej aplikacji na Androida i dodasz do niej funkcje segmentacji obrazu. Omówimy też kroki wstępnego przetwarzania, wnioskowania i przetwarzania końcowego. W ramach ćwiczenia:

  • Utwórz aplikację na Androida, która segmentuje obrazy w czasie rzeczywistym.
  • Zintegruj wytrenowany model segmentacji obrazów LiteRT.
  • Wstępnie przetwórz obraz wejściowy na potrzeby modelu.
  • Używaj środowiska wykonawczego LiteRT do przyspieszania działania na procesorze i GPU.
  • Dowiedz się, jak przetwarzać dane wyjściowe modelu, aby wyświetlać maskę segmentacji.
  • Dowiedz się, jak dostosować ustawienia przedniego aparatu.

Na koniec uzyskasz efekt podobny do tego na obrazie poniżej:

Ukończona aplikacja

Wymagania wstępne

Te ćwiczenia z programowania są przeznaczone dla doświadczonych programistów aplikacji mobilnych, którzy chcą zdobyć doświadczenie w zakresie uczenia maszynowego. Musisz znać:

  • Tworzenie aplikacji na Androida w Kotlinie i Android Studio
  • Podstawowe pojęcia dotyczące przetwarzania obrazów

Czego się nauczysz

  • Jak zintegrować i używać środowiska wykonawczego LiteRT w aplikacji na Androida.
  • Jak przeprowadzić segmentację obrazu za pomocą wytrenowanego modelu LiteRT.
  • Jak wstępnie przetworzyć obraz wejściowy na potrzeby modelu.
  • Jak przeprowadzić wnioskowanie w przypadku modelu.
  • Jak przetwarzać dane wyjściowe modelu segmentacji, aby wizualizować wyniki.
  • Jak używać CameraX do przetwarzania obrazu z kamery w czasie rzeczywistym.

Czego potrzebujesz

  • Najnowsza wersja Android Studio (testowana w wersji 2025.1.1).
  • fizyczne urządzenie z Androidem; Najlepiej przetestować ją na urządzeniach Galaxy i Pixel.
  • przykładowy kod (z GitHuba);
  • Podstawowa wiedza na temat tworzenia aplikacji na Androida w języku Kotlin.

2. Segmentacja obrazu

Segmentacja obrazu to zadanie z zakresu widzenia komputerowego, które polega na podzieleniu obrazu na wiele segmentów lub regionów. W przeciwieństwie do wykrywania obiektów, które rysuje ramkę ograniczającą wokół obiektu, segmentacja obrazu przypisuje określoną klasę lub etykietę do każdego piksela na obrazie. Dzięki temu uzyskasz znacznie bardziej szczegółowe i dokładne informacje o zawartości obrazu, co pozwoli Ci poznać dokładny kształt i granice każdego obiektu.

Na przykład zamiast tylko wiedzieć, że w ramce znajduje się „osoba”, możesz dokładnie określić, które piksele do niej należą. Z tego samouczka dowiesz się, jak przeprowadzić segmentację obrazu w czasie rzeczywistym na urządzeniu z Androidem za pomocą wstępnie wytrenowanego modelu uczenia maszynowego.

Przykład segmentacji

LiteRT: przesuwanie granic uczenia maszynowego na urządzeniu

Kluczową technologią umożliwiającą segmentację w czasie rzeczywistym i wysokiej jakości na urządzeniach mobilnych jest LiteRT. LiteRT to środowisko wykonawcze TensorFlow Lite nowej generacji o wysokiej wydajności. Zostało zaprojektowane tak, aby w pełni wykorzystywać możliwości sprzętu.

Osiąga to dzięki inteligentnemu i zoptymalizowanemu wykorzystaniu akceleratorów sprzętowych, takich jak GPU (procesor graficzny) i NPU (procesor sieci neuronowych). Przenosząc intensywne obliczenia modelu segmentacji z procesora ogólnego przeznaczenia na te wyspecjalizowane procesory, LiteRT znacznie skraca czas wnioskowania. To przyspieszenie umożliwia płynne działanie złożonych modeli na żywo w aplikacji Aparat, co poszerza możliwości uczenia maszynowego bezpośrednio na telefonie. Bez tego poziomu wydajności segmentacja w czasie rzeczywistym byłaby zbyt wolna i niestabilna, aby zapewnić użytkownikom wygodę.

3. Konfiguracja

Klonowanie repozytorium

Najpierw sklonuj repozytorium LiteRT:

git clone https://github.com/google-ai-edge/LiteRT.git

LiteRT/litert/samples/image_segmentation to katalog ze wszystkimi potrzebnymi zasobami. W tym ćwiczeniu potrzebny będzie tylko projekt kotlin_cpu_gpu/android_starter. Jeśli utkniesz, możesz sprawdzić gotowy projekt: kotlin_cpu_gpu/android

Uwaga dotycząca ścieżek do plików

W tym samouczku ścieżki plików są podane w formacie Linux/macOS. Jeśli korzystasz z systemu Windows, musisz odpowiednio dostosować ścieżki.

Warto też zwrócić uwagę na różnicę między widokiem projektu w Android Studio a standardowym widokiem systemu plików. Widok projektu w Android Studio to uporządkowana reprezentacja plików projektu, zorganizowana pod kątem tworzenia aplikacji na Androida. Ścieżki plików w tym samouczku odnoszą się do ścieżek w systemie plików, a nie do ścieżek w widoku projektu w Android Studio.

Importowanie aplikacji startowej

Zacznijmy od zaimportowania aplikacji początkowej do Androida Studio.

  1. Otwórz Android Studio i kliknij Otwórz.

Otwórz Android Studio

  1. Przejdź do katalogu kotlin_cpu_gpu/android_starter i otwórz go.

Android Starter

Aby mieć pewność, że wszystkie zależności są dostępne dla aplikacji, po zakończeniu procesu importowania zsynchronizuj projekt z plikami Gradle.

  1. Na pasku narzędzi Android Studio wybierz Sync Project with Gradle Files (Synchronizuj projekt z plikami Gradle).

Synchronizacja menu

  1. Nie pomijaj tego kroku – jeśli nie zadziała, reszta samouczka nie będzie miała sensu.

Uruchom aplikację startową

Po zaimportowaniu projektu do Android Studio możesz po raz pierwszy uruchomić aplikację.

Podłącz urządzenie z Androidem do komputera przez USB i na pasku narzędzi Androida Studio kliknij Uruchom.

Przycisk Uruchom

Aplikacja powinna się uruchomić na urządzeniu. Zobaczysz obraz na żywo z kamery, ale segmentacja nie będzie jeszcze działać. Wszystkie zmiany w plikach, które wprowadzisz w tym samouczku, będą dotyczyć katalogu LiteRT/litert/samples/image_segmentation/kotlin_cpu_gpu/android_starter/app/src/main/java/com/google/aiedge/examples/image_segmentation (teraz już wiesz, dlaczego Android Studio zmienia jego strukturę 😃).

Project Dir

Zobaczysz też TODO komentarze w plikach ImageSegmentationHelper.kt, MainViewModel.ktview/SegmentationOverlay.kt. W kolejnych krokach zaimplementujesz funkcję segmentacji obrazu, wypełniając te pola TODO.

4. Informacje o aplikacji startowej

Aplikacja startowa ma już podstawowy interfejs i logikę obsługi aparatu. Oto krótkie omówienie najważniejszych plików:

  • app/src/main/java/com/google/aiedge/examples/image_segmentation/MainActivity.kt: to główny punkt wejścia do aplikacji. Konfiguruje interfejs za pomocą Jetpack Compose i zarządza uprawnieniami dostępu do aparatu.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/MainViewModel.kt: ten ViewModel zarządza stanem interfejsu i koordynuje proces segmentacji obrazu.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/ImageSegmentationHelper.kt: w tym miejscu dodamy podstawową logikę segmentacji obrazu. Zajmie się wczytywaniem modelu, przetwarzaniem klatek z kamery i przeprowadzaniem wnioskowania.
  • app/src/main/java/com/google/aiedge/examples/image_segmentation/view/CameraScreen.kt: ta funkcja Composable wyświetla podgląd z kamery i nakładkę segmentacji.
  • app/src/main/assets/selfie_multiclass.tflite: jest to wstępnie wytrenowany model segmentacji obrazu TensorFlow Lite, którego będziemy używać.

5. Informacje o LiteRT i dodawanie zależności

Teraz dodajmy do aplikacji startowej funkcję segmentacji obrazu.

1. Dodaj zależność LiteRT

Najpierw musisz dodać bibliotekę LiteRT do projektu. Jest to kluczowy pierwszy krok, który umożliwia uczenie maszynowe na urządzeniu z użyciem zoptymalizowanego środowiska wykonawczego Google.

Otwórz plik app/build.gradle.kts i dodaj ten wiersz do bloku dependencies:

// LiteRT for on-device ML
implementation(libs.litert)

Po dodaniu zależności zsynchronizuj projekt z plikami Gradle, klikając przycisk Synchronizuj teraz, który pojawi się w prawym górnym rogu Android Studio.

Synchronizuj teraz

2. Poznaj kluczowe interfejsy API LiteRT

OtwórzImageSegmentationHelper.kt

Zanim napiszesz kod implementacji, musisz poznać podstawowe komponenty interfejsu LiteRT API, których będziesz używać. Upewnij się, że importujesz z pakietu com.google.ai.edge.litert. Dodaj te importy na początku pliku ImageSegmentationHelper.kt:

import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
  • CompiledModel: jest to główna klasa do interakcji z modelem TFLite. Jest to model, który został wstępnie skompilowany i zoptymalizowany pod kątem konkretnego akceleratora sprzętowego (np. procesora lub GPU). Wstępna kompilacja to kluczowa funkcja LiteRT, która zapewnia szybsze i wydajniejsze wnioskowanie.
  • CompiledModel.Options: za pomocą tej klasy narzędzia do tworzenia możesz skonfigurować CompiledModel. Najważniejsze ustawienie to określenie akceleratora sprzętowego, którego chcesz używać do uruchamiania modelu.
  • Accelerator: ten wyliczenie umożliwia wybór sprzętu do wnioskowania. Projekt startowy jest już skonfigurowany do obsługi tych opcji:
    • Accelerator.CPU: do uruchamiania modelu na procesorze urządzenia. Jest to najbardziej uniwersalna opcja.
    • Accelerator.GPU: do uruchamiania modelu na procesorze graficznym urządzenia. W przypadku modeli opartych na obrazach jest to często znacznie szybsze niż w przypadku procesora.
  • Bufory wejściowe i wyjściowe (TensorBuffer): LiteRT używa TensorBuffer do danych wejściowych i wyjściowych modelu. Dzięki temu masz szczegółową kontrolę nad pamięcią i unikasz niepotrzebnych kopii danych. Te bufory uzyskasz bezpośrednio z instancji CompiledModel za pomocą funkcji model.createInputBuffers()model.createOutputBuffers(), a następnie zapiszesz w nich dane wejściowe i odczytasz wyniki.
  • model.run(): ta funkcja wykonuje wnioskowanie. Przekazujesz do niego bufory wejściowe i wyjściowe, a LiteRT zajmuje się złożonym zadaniem uruchomienia modelu na wybranym akceleratorze sprzętowym.

6. Kończenie wstępnej implementacji ImageSegmentationHelper

Teraz czas napisać kod. Przeprowadzisz wstępną implementację ImageSegmentationHelper.kt. Obejmuje to skonfigurowanie Segmenter klasy prywatnej do przechowywania modelu LiteRT i wdrożenie funkcji cleanup(), która umożliwia jego prawidłowe zwalnianie.

  1. Ukończ Segmenter klasę i cleanup() funkcję: w pliku ImageSegmentationHelper.kt znajdziesz szkielet klasy prywatnej o nazwie Segmenter i funkcji o nazwie cleanup(). Najpierw uzupełnij klasę Segmenter, definiując jej konstruktor do przechowywania modelu, tworząc właściwości buforów wejściowych i wyjściowych oraz dodając metodę close() do zwalniania modelu. Następnie zaimplementuj funkcję cleanup(), aby wywoływała nową metodę close().Zastąp istniejącą klasę Segmenter i funkcję cleanup() tym kodem: (~wiersz 83)
    private class Segmenter(
        // Add this argument
        private val model: CompiledModel,
        private val coloredLabels: List<ColoredLabel>,
    ) {
        // Add these private vals
        private val inputBuffers: = model.createInputBuffers()
        private val outputBuffers: = model.createOutputBuffers()
    
        fun cleanup() {
          // cleanup buffers
          inputBuffers.forEach { it.close() }
          outputBuffers.forEach { it.close() }
          // cleanup model
          model.close()
        }
    }
    
  2. Zdefiniuj metodę toAccelerator: ta metoda mapuje zdefiniowane wyliczenia akceleratora z menu akceleratora na wyliczenia akceleratora specyficzne dla zaimportowanych modułów LiteRT (~wiersz 225):
    fun toAccelerator(acceleratorEnum: AcceleratorEnum): Accelerator {
      return when (acceleratorEnum) {
        AcceleratorEnum.CPU -> Accelerator.CPU
        AcceleratorEnum.GPU -> Accelerator.GPU
      }
    }
    
  3. Zainicjuj CompiledModel: teraz znajdź funkcję initSegmenter. W tym miejscu utworzysz CompiledModel instancję i użyjesz jej do utworzenia instancji zdefiniowanej klasy Segmenter. Ten kod konfiguruje model z określonym akceleratorem (CPU lub GPU) i przygotowuje go do wnioskowania. Zastąp znak TODOinitSegmenter tym kodem (naciśnij Cmd/Ctrl+f „initSegmenter” lub przejdź do wiersza 62):
    cleanup()
    try {
      withContext(singleThreadDispatcher) {
        val model =
          CompiledModel.create(
            context.assets,
            "selfie_multiclass.tflite",
            CompiledModel.Options(toAccelerator(acceleratorEnum)),
            null,
          )
        segmenter = Segmenter(model, coloredLabels)
        Log.d(TAG, "Created an image segmenter")
      }
    } catch (e: Exception) {
      Log.i(TAG, "Create LiteRT from selfie_multiclass is failed: ${e.message}")
      _error.emit(e)
    }
    

7. Rozpocznij segmentację i przetwarzanie wstępne

Teraz, gdy mamy już model, musimy uruchomić proces segmentacji i przygotować dane wejściowe dla modelu.

Aktywowanie segmentacji

Proces segmentacji rozpoczyna się w MainViewModel.kt, które otrzymuje klatki z kamery.

OtwórzMainViewModel.kt

  1. Wywoływanie segmentacji z klatek kamery: funkcje segmentMainViewModel są punktem wejścia dla naszego zadania segmentacji. Są one wywoływane, gdy z aparatu jest dostępne nowe zdjęcie lub gdy zostanie ono wybrane z galerii. Te funkcje wywołują następnie metodę segment w naszym ImageSegmentationHelper. Zastąp symbole TODO w obu funkcjach segment tymi wartościami (wiersz ~107):
    // For ImageProxy (from CameraX)
    fun segment(imageProxy: ImageProxy) {
        segmentJob =
            viewModelScope.launch {
                imageSegmentationHelper.segment(imageProxy.toBitmap(), imageProxy.imageInfo.rotationDegrees)
                imageProxy.close()
            }
    }
    
    // For Bitmaps (from gallery)
    fun segment(bitmap: Bitmap, rotationDegrees: Int) {
        segmentJob =
            viewModelScope.launch {
                val argbBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
                imageSegmentationHelper.segment(argbBitmap, rotationDegrees)
            }
    }
    

Wstępne przetwarzanie obrazu

Wróćmy teraz do ImageSegmentationHelper.kt, aby zająć się wstępnym przetwarzaniem obrazu.

OtwórzImageSegmentationHelper.kt

  1. Wdróż funkcję Public segment Function: ta funkcja służy jako otoka, która wywołuje prywatną funkcję segment w klasie Segmenter. Zastąp TODO (~wiersz 95):
    try {
      withContext(singleThreadDispatcher) {
        segmenter?.segment(bitmap, rotationDegrees)?.let { if (isActive) _segmentation.emit(it) }
      }
    } catch (e: Exception) {
      Log.i(TAG, "Image segment error occurred: ${e.message}")
      _error.emit(e)
    }
    
  2. Wdrożenie wstępnego przetwarzania: prywatna segment funkcja w klasie Segmenter to miejsce, w którym przeprowadzimy niezbędne przekształcenia obrazu wejściowego, aby przygotować go do modelu. Obejmuje to skalowanie, obracanie i normalizowanie obrazu. Ta funkcja wywoła następnie inną prywatną funkcję segment, aby przeprowadzić wnioskowanie. Zastąp TODO w funkcji segment(bitmap: Bitmap, ...) tym ciągiem znaków (~line 121):
    val totalStartTime = SystemClock.uptimeMillis()
    val rotation = -rotationDegrees / 90
    val (h, w) = Pair(256, 256)
    
    // Preprocessing
    val preprocessStartTime = SystemClock.uptimeMillis()
    var image = bitmap.scale(w, h, true)
    image = rot90Clockwise(image, rotation)
    val inputFloatArray = normalize(image, 127.5f, 127.5f)
    Log.d(TAG, "Preprocessing time: ${SystemClock.uptimeMillis() - preprocessStartTime} ms")
    
    // Inference
    val inferenceStartTime = SystemClock.uptimeMillis()
    val segmentResult = segment(inputFloatArray)
    Log.d(TAG, "Inference time: ${SystemClock.uptimeMillis() - inferenceStartTime} ms")
    
    Log.d(TAG, "Total segmentation time: ${SystemClock.uptimeMillis() - totalStartTime} ms")
    return SegmentationResult(segmentResult, SystemClock.uptimeMillis() - inferenceStartTime)
    

8. Podstawowe wnioskowanie z użyciem LiteRT

Po wstępnym przetworzeniu danych wejściowych możemy uruchomić podstawowe wnioskowanie za pomocą LiteRT.

OtwórzImageSegmentationHelper.kt

  1. Implementacja wykonywania modelu: prywatna funkcja segment(inputFloatArray: FloatArray) to miejsce, w którym bezpośrednio wchodzimy w interakcję z metodą LiteRT run(). Zapisujemy wstępnie przetworzone dane w buforze wejściowym, uruchamiamy model i odczytujemy wyniki z bufora wyjściowego. Zastąp TODO w tej funkcji tym kodem (~wiersz 188):
    val (h, w, c) = Triple(256, 256, 6)
    
    // MODEL EXECUTION PHASE
    val modelExecStartTime = SystemClock.uptimeMillis()
    
    // Write input data - measure time
    val bufferWriteStartTime = SystemClock.uptimeMillis()
    inputBuffers[0].writeFloat(inputFloatArray)
    val bufferWriteTime = SystemClock.uptimeMillis() - bufferWriteStartTime
    Log.d(TAG, "Buffer write time: $bufferWriteTime ms")
    
    // Optional tensor inspection
    logTensorStats("Input tensor", inputFloatArray)
    
    // Run model inference - measure time
    val modelRunStartTime = SystemClock.uptimeMillis()
    model.run(inputBuffers, outputBuffers)
    val modelRunTime = SystemClock.uptimeMillis() - modelRunStartTime
    Log.d(TAG, "Model.run() time: $modelRunTime ms")
    
    // Read output data - measure time
    val bufferReadStartTime = SystemClock.uptimeMillis()
    val outputFloatArray = outputBuffers[0].readFloat()
    val outputBuffer = FloatBuffer.wrap(outputFloatArray)
    val bufferReadTime = SystemClock.uptimeMillis() - bufferReadStartTime
    Log.d(TAG, "Buffer read time: $bufferReadTime ms")
    
    val modelExecTime = SystemClock.uptimeMillis() - modelExecStartTime
    Log.d(TAG, "Total model execution time: $modelExecTime ms")
    
    // Optional tensor inspection
    logTensorStats("Output tensor", outputFloatArray)
    
    // POSTPROCESSING PHASE
    val postprocessStartTime = SystemClock.uptimeMillis()
    
    // Process mask from model output
    val inferenceData = InferenceData(width = w, height = h, channels = c, buffer = outputBuffer)
    val mask = processImage(inferenceData)
    
    val postprocessTime = SystemClock.uptimeMillis() - postprocessStartTime
    Log.d(TAG, "Postprocessing time (mask creation): $postprocessTime ms")
    
    return Segmentation(
      listOf(Mask(mask, inferenceData.width, inferenceData.height)),
      coloredLabels,
    )
    

9. Przetwarzanie końcowe i wyświetlanie nakładki

Po przeprowadzeniu wnioskowania otrzymujemy surowe dane wyjściowe z modelu. Musimy przetworzyć te dane wyjściowe, aby utworzyć wizualną maskę segmentacji, a następnie wyświetlić ją na ekranie.

OtwórzImageSegmentationHelper.kt

  1. Wdrożenie przetwarzania danych wyjściowych: funkcja processImage konwertuje surowe dane wyjściowe zmiennoprzecinkowe z modelu na wartość ByteBuffer, która reprezentuje maskę segmentacji. W tym celu wyszukuje klasę o najwyższym prawdopodobieństwie dla każdego piksela. Zastąp jego TODO (~wiersz 238):
    val mask = ByteBuffer.allocateDirect(inferenceData.width * inferenceData.height)
    for (i in 0 until inferenceData.height) {
        for (j in 0 until inferenceData.width) {
            val offset = inferenceData.channels * (i * inferenceData.width + j)
    
            var maxIndex = 0
            var maxValue = inferenceData.buffer.get(offset)
    
            for (index in 1 until inferenceData.channels) {
                if (inferenceData.buffer.get(offset + index) > maxValue) {
                    maxValue = inferenceData.buffer.get(offset + index)
                    maxIndex = index
                }
            }
            mask.put(i * inferenceData.width + j, maxIndex.toByte())
        }
    }
    return mask
    

OtwórzMainViewModel.kt

  1. Zbieranie i przetwarzanie wyników segmentacji: wracamy teraz do MainViewModel, aby przetworzyć wyniki segmentacji z ImageSegmentationHelper. segmentationUiShareFlow zbiera SegmentationResult, przekształca maskę w kolorową Bitmap i przekazuje ją do interfejsu. Zastąp TODO w właściwości segmentationUiShareFlow (~wiersz 63) – nie zastępuj kodu, który już tam jest, tylko wypełnij treść:
    viewModelScope.launch {
      imageSegmentationHelper.segmentation
        .filter { it.segmentation.masks.isNotEmpty() }
        .map {
          val segmentation = it.segmentation
          val mask = segmentation.masks[0]
          val maskArray = mask.data
          val width = mask.width
          val height = mask.height
          val pixelSize = width * height
          val pixels = IntArray(pixelSize)
    
          val colorLabels =
            segmentation.coloredLabels.mapIndexed { index, coloredLabel ->
              ColorLabel(index, coloredLabel.label, coloredLabel.argb)
            }
          // Set color for pixels
          for (i in 0 until pixelSize) {
            val colorLabel = colorLabels[maskArray[i].toInt()]
            val color = colorLabel.getColor()
            pixels[i] = color
          }
          // Get image info
          val overlayInfo = OverlayInfo(pixels = pixels, width = width, height = height)
    
          val inferenceTime = it.inferenceTime
          Pair(overlayInfo, inferenceTime)
        }
        .collect { flow.emit(it) }
    }
    

Otwórzview/SegmentationOverlay.kt

Ostatnim elementem jest prawidłowe zorientowanie nakładki segmentacji, gdy użytkownik przełączy się na przedni aparat. Obraz z przedniego aparatu jest naturalnie odbity lustrzanie, więc musimy zastosować to samo odbicie w poziomie do naszej nakładki Bitmap, aby była ona prawidłowo dopasowana do podglądu z aparatu.

  1. Obsługa orientacji nakładki: znajdź TODO w pliku SegmentationOverlay.kt i zastąp go tym kodem. Ten kod sprawdza, czy przedni aparat jest aktywny, a jeśli tak, stosuje do nakładki Bitmap poziome odwrócenie przed narysowaniem jej na Canvas. (~line 42):
    val orientedBitmap =
      if (lensFacing == CameraSelector.LENS_FACING_FRONT) {
        // Create a matrix for horizontal flipping
        val matrix = Matrix().apply { preScale(-1f, 1f) }
        Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false).also {
          image.recycle()
        }
      } else {
        image
      }
    

10. Uruchamianie i używanie gotowej aplikacji

Wszystkie niezbędne zmiany w kodzie zostały wprowadzone. Czas uruchomić aplikację i zobaczyć efekty swojej pracy.

  1. Uruchom aplikację: podłącz urządzenie z Androidem i kliknij Uruchom na pasku narzędzi Android Studio.

Przycisk Uruchom

  1. Przetestuj funkcje: po uruchomieniu aplikacji powinien pojawić się obraz z kamery na żywo z kolorową nakładką segmentacji.
    • Przełączanie aparatów: kliknij ikonę zmiany aparatu u góry, aby przełączać się między przednim a tylnym aparatem. Zwróć uwagę, jak nakładka prawidłowo się orientuje.
    • Zmień akcelerator: kliknij przycisk „CPU” lub „GPU” u dołu, aby przełączyć akcelerator sprzętowy. Obserwuj zmianę czasu wnioskowania wyświetlanego u dołu ekranu. GPU powinien być znacznie szybszy.
    • Użyj obrazu z galerii: u góry kliknij kartę „Galeria”, aby wybrać obraz z galerii zdjęć na urządzeniu. Aplikacja przeprowadzi segmentację wybranego obrazu statycznego.

Inne interfejsy

Masz teraz w pełni funkcjonalną aplikację do segmentacji obrazu w czasie rzeczywistym opartą na LiteRT.

11. Zaawansowane (opcjonalnie): korzystanie z procesora NPU

W tym repozytorium znajduje się też wersja aplikacji zoptymalizowana pod kątem jednostek przetwarzania neuronowego (NPU). Wersja NPU może znacznie zwiększyć wydajność na urządzeniach z kompatybilnym procesorem NPU.

Aby wypróbować wersję NPU, otwórz projekt kotlin_npu/android w Android Studio. Kod jest bardzo podobny do wersji na CPU/GPU i jest skonfigurowany do używania delegata NPU.

Aby korzystać z delegata NPU, musisz zarejestrować się w programie wcześniejszego dostępu.

12. Gratulacje!

Udało Ci się utworzyć aplikację na Androida, która wykonuje segmentację obrazu w czasie rzeczywistym za pomocą biblioteki LiteRT. Wiesz już, jak:

  • Zintegruj środowisko wykonawcze LiteRT z aplikacją na Androida.
  • Wczytywanie i uruchamianie modelu segmentacji obrazów TFLite.
  • przetwarzanie wstępne danych wejściowych modelu;
  • Przetwórz dane wyjściowe modelu, aby utworzyć maskę segmentacji.
  • Użyj CameraX w aplikacji aparatu działającej w czasie rzeczywistym.

Następne kroki

  • Spróbuj użyć innego modelu segmentacji obrazu.
  • Eksperymentuj z różnymi delegatami LiteRT (CPU, GPU, NPU).

Więcej informacji