Przewodnik po segmentacji obrazów na Androidzie

Zadanie segmentacji obrazów MediaPipe pozwala dzielić obrazy na regiony na podstawie wstępnie zdefiniowanych kategorii do stosowania efektów wizualnych, takich jak rozmycie tła. Z tych instrukcji dowiesz się, jak korzystać z segmentowania obrazów w aplikacjach na Androida. Przykładowy kod opisany w tych instrukcjach jest dostępny na GitHub. Więcej informacji o możliwościach, modelach i opcjach konfiguracji tego zadania znajdziesz w sekcji Omówienie.

Przykładowy kod

Przykład kodu MediaPipe Tasks zawiera 2 proste implementacje aplikacji Image Segmenter na Androida:

W przykładach używamy aparatu na fizycznym urządzeniu z Androidem, aby segmentować obraz na bieżąco z kamery. Możesz też wybierać obrazy i filmy z galerii urządzenia. Możesz użyć tych aplikacji jako punktu wyjścia dla własnej aplikacji na Androida lub skorzystać z nich podczas modyfikowania istniejącej aplikacji. Przykładowy kod narzędzia Image Segmenter jest hostowany na GitHub.

Podane niżej sekcje dotyczą aplikacji Narzędzie do podziału obrazów na segmenty z maską kategorii.

Pobieranie kodu

Z instrukcji poniżej dowiesz się, jak utworzyć lokalną kopię przykładowego kodu za pomocą narzędzia wiersza poleceń git.

Aby pobrać przykładowy kod:

  1. Sklonuj repozytorium git za pomocą tego polecenia:
    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. Opcjonalnie skonfiguruj instancję git tak, aby używała rozproszonego procesu płatności, aby mieć tylko pliki dla przykładowej aplikacji do segmentacji obrazów:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_segmentation/android
    

Po utworzeniu lokalnej wersji przykładowego kodu możesz zaimportować projekt do Android Studio i uruchomić aplikację. Instrukcje znajdziesz w przewodniku konfiguracji na Androida.

Kluczowe elementy

Te pliki zawierają kluczowy kod tej przykładowej aplikacji do podziału obrazów na segmenty:

Konfiguracja

W tej sekcji znajdziesz najważniejsze czynności, jakie należy wykonać, aby skonfigurować środowisko programistyczne i projekty kodu związane z korzystaniem z narzędzia do segmentowania obrazów. Ogólne informacje o konfigurowaniu środowiska programistycznego na potrzeby zadań MediaPipe, w tym o wymaganiach dotyczących wersji platformy, znajdziesz w przewodniku konfiguracji na Androida.

Zależności

Narzędzie do segmentowania obrazów korzysta z biblioteki com.google.mediapipe:tasks-vision. Dodaj tę zależność do pliku build.gradle swojego projektu na Androida. Zaimportuj wymagane zależności za pomocą tego kodu:

dependencies {
    ...
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
}

Model

Zadanie segmentacji obrazów MediaPipe wymaga wytrenowanego modelu zgodnego z tym zadaniem. Więcej informacji o dostępnych wytrenowanych modelach służących do segmentowania obrazów znajdziesz w sekcji przeglądu zadań w sekcji poświęconej modelom.

Wybierz i pobierz model, a następnie zapisz go w katalogu projektu:

<dev-project-root>/src/main/assets

Aby określić ścieżkę używaną przez model, użyj metody BaseOptions.Builder.setModelAssetPath(). Tę metodę omówimy w przykładowym kodzie w następnej sekcji.

W przykładowym kodzie do segmentacji obrazów model jest zdefiniowany w klasie ImageSegmenterHelper.kt w funkcji setupImageSegmenter().

Tworzenie zadania

Aby utworzyć zadanie, możesz użyć funkcji createFromOptions. Funkcja createFromOptions akceptuje opcje konfiguracji, w tym typy masek. Więcej informacji o konfigurowaniu zadań znajdziesz w artykule o opcjach konfiguracji.

Zadanie segmentacji obrazów obsługuje te typy danych wejściowych: obrazy, pliki wideo i strumienie wideo na żywo. Podczas tworzenia zadania musisz określić tryb działania odpowiadający typowi danych wejściowych. Wybierz kartę z typem danych wejściowych, aby zobaczyć, jak utworzyć takie zadanie.

Obraz

ImageSegmenterOptions options =
  ImageSegmenterOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.IMAGE)
    .setOutputCategoryMask(true)
    .setOutputConfidenceMasks(false)
    .build();
imagesegmenter = ImageSegmenter.createFromOptions(context, options);
    

Wideo

ImageSegmenterOptions options =
  ImageSegmenterOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.VIDEO)
    .setOutputCategoryMask(true)
    .setOutputConfidenceMasks(false)
    .build();
imagesegmenter = ImageSegmenter.createFromOptions(context, options);
    

Transmisja na żywo

ImageSegmenterOptions options =
  ImageSegmenterOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setOutputCategoryMask(true)
    .setOutputConfidenceMasks(false)
    .setResultListener((result, inputImage) -> {
         // Process the segmentation result here.
    })
    .setErrorListener((result, inputImage) -> {
         // Process the segmentation errors here.
    })
    .build()
imagesegmenter = ImageSegmenter.createFromOptions(context, options)
    

Implementacja kodu przykładowego narzędzia do segmentacji obrazów pozwala użytkownikowi przełączać się między trybami przetwarzania. Takie podejście zwiększa złożoność kodu tworzenia zadania i może nie być odpowiednie w Twoim przypadku. Ten kod jest widoczny w klasie ImageSegmenterHelper przy funkcji setupImageSegmenter().

Opcje konfiguracji

To zadanie ma te opcje konfiguracji aplikacji na Androida:

Nazwa opcji Opis Zakres wartości Wartość domyślna
runningMode Ustawia tryb działania zadania. Są 3 tryby:

IMAGE: tryb wprowadzania pojedynczych obrazów.

WIDEO: tryb dekodowanych klatek filmu.

TRANSMISJA NA ŻYWO: tryb transmisji danych wejściowych na żywo, np. z kamery. W tym trybie należy wywołać metodę resultListener, aby skonfigurować odbiornik, który będzie odbierał wyniki asynchronicznie.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
outputCategoryMask Jeśli ma wartość True, dane wyjściowe zawierają maskę podziału jako obraz uint8, w którym każda wartość piksela wskazuje zwycięską kategorię. {True, False} False
outputConfidenceMasks Jeśli ustawiona jest wartość True, dane wyjściowe zawierają maskę podziału w postaci obrazu z wartością zmiennoprzecinkową, w którym każda wartość zmiennoprzecinkowa reprezentuje mapę wskaźnika ufności kategorii. {True, False} True
displayNamesLocale Ustawia język etykiet, które mają być używane w przypadku nazw wyświetlanych w metadanych modelu zadania (jeśli są dostępne). Wartość domyślna w języku angielskim to en. Za pomocą TensorFlow Lite MetadataWriter API możesz dodawać zlokalizowane etykiety do metadanych modelu niestandardowego. Kod języka en
resultListener Ustawia detektor wyników, aby asynchronicznie otrzymywać wyniki podziału na segmenty, gdy moduł do segmentacji obrazów działa w trybie transmisji na żywo. Tego ustawienia można używać tylko wtedy, gdy tryb biegowy jest ustawiony na LIVE_STREAM Nie dotyczy Nie dotyczy
errorListener Ustawia opcjonalny detektor błędów. Nie dotyczy Nie ustawiono

Przygotuj dane

Narzędzie do segmentowania obrazów obsługuje obrazy, pliki wideo i strumieniową transmisję wideo na żywo. To zadanie obsługuje wstępne przetwarzanie danych wejściowych, w tym zmianę rozmiaru, rotację i normalizację wartości.

Zanim prześlesz obraz lub ramkę do segmentacji obrazów, musisz przekonwertować go na obiekt com.google.mediapipe.framework.image.MPImage.

Obraz

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load an image on the user’s device as a Bitmap object using BitmapFactory.

// Convert an Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(bitmap).build();
    

Wideo

import com.google.mediapipe.framework.image.BitmapImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Load a video file on the user's device using MediaMetadataRetriever

// From the video’s metadata, load the METADATA_KEY_DURATION and
// METADATA_KEY_VIDEO_FRAME_COUNT value. You’ll need them
// to calculate the timestamp of each frame later.

// Loop through the video and load each frame as a Bitmap object.

// Convert the Android’s Bitmap object to a MediaPipe’s Image object.
Image mpImage = new BitmapImageBuilder(frame).build();
    

Transmisja na żywo

import com.google.mediapipe.framework.image.MediaImageBuilder;
import com.google.mediapipe.framework.image.MPImage;

// Create a CameraX’s ImageAnalysis to continuously receive frames
// from the device’s camera. Configure it to output frames in RGBA_8888
// format to match with what is required by the model.

// For each Android’s ImageProxy object received from the ImageAnalysis,
// extract the encapsulated Android’s Image object and convert it to
// a MediaPipe’s Image object.
android.media.Image mediaImage = imageProxy.getImage()
Image mpImage = new MediaImageBuilder(mediaImage).build();
    

W przykładowym kodzie narzędzia do segmentacji obrazów przygotowywanie danych jest obsługiwane w klasie ImageSegmenterHelper przez funkcję segmentLiveStreamFrame().

Uruchamianie zadania

Wywołujesz inną funkcję segment w zależności od używanego trybu biegania. Funkcja segmentowania obrazów zwraca zidentyfikowane regiony segmentów w obrazie lub ramce wejściowej.

Obraz

ImageSegmenterResult segmenterResult = imagesegmenter.segment(image);
    

Wideo

// Calculate the timestamp in milliseconds of the current frame.
long frame_timestamp_ms = 1000 * video_duration * frame_index / frame_count;

// Run inference on the frame.
ImageSegmenterResult segmenterResult =
    imagesegmenter.segmentForVideo(image, frameTimestampMs);
    

Transmisja na żywo

// Run inference on the frame. The segmentations results will be available via
// the `resultListener` provided in the `ImageSegmenterOptions` when the image
// segmenter was created.
imagesegmenter.segmentAsync(image, frameTimestampMs);
    

Uwaga:

  • Gdy urządzenie działa w trybie wideo lub transmisji na żywo, musisz też podać w zadaniu segmentacji obrazów sygnaturę czasową klatki wejściowej.
  • W trybie obrazu lub wideo zadanie segmentowania obrazów blokuje bieżący wątek do momentu zakończenia przetwarzania obrazu wejściowego lub klatki. Aby uniknąć zablokowania interfejsu, przetwarzanie odbywa się w wątku w tle.
  • W trybie transmisji na żywo zadanie segmentowania obrazów nie blokuje bieżącego wątku, ale wraca natychmiast. Wywołuje on detektor wyników z wynikiem wykrywania za każdym razem, gdy zakończy przetwarzanie ramki wejściowej. Jeśli funkcja segmentAsync zostanie wywołana, gdy zadanie segmentowania obrazów jest zajęte przetwarzaniem innej ramki, zadanie zignoruje nową ramkę wejściową.

W przykładowym kodzie narzędzia do segmentacji obrazów funkcje segment są zdefiniowane w pliku ImageSegmenterHelper.kt.

Obsługa i wyświetlanie wyników

Po uruchomieniu wnioskowania zadanie Segmentowania obrazów zwraca obiekt ImageSegmenterResult zawierający wyniki zadania podziału na segmenty. Zawartość danych wyjściowych zależy od parametru outputType ustawionego podczas konfigurowania zadania.

W sekcjach poniżej znajdziesz przykłady danych wyjściowych z tego zadania:

Poziom ufności kategorii

Na poniższych obrazach pokazano wizualizację wyników zadania dla maski ufności kategorii. Dane wyjściowe maski ufności zawierają wartości zmiennoprzecinkowe między [0, 1].

Oryginalny obraz i wyjście maski ufności kategorii. Obraz źródłowy ze zbioru danych Pascal VOC 2012.

Wartość kategorii

Na poniższych obrazach pokazano wizualizację danych wyjściowych zadania w przypadku maski wartości kategorii. Zakres maski kategorii to [0, 255], a każda wartość w pikselu reprezentuje zwycięski indeks kategorii danych wyjściowych modelu. Indeks zwycięskiej kategorii ma najwyższy wynik spośród kategorii rozpoznawanych przez model.

Oryginalny obraz i maska kategorii. Obraz źródłowy ze zbioru danych Pascal VOC 2012.