Przewodnik umieszczania obrazów w przypadku Androida

Zadanie MediaPipe Image Embedder pozwala przekształcić dane obrazu na reprezentację liczbową w celu realizacji zadań przetwarzania obrazu związanych z systemami uczącymi się, takich jak porównywanie podobieństwa 2 obrazów. W tych instrukcjach pokazujemy, jak korzystać z narzędzia do umieszczania obrazów w aplikacjach na Androida.

Więcej informacji o możliwościach, modelach i opcjach konfiguracji tego zadania znajdziesz w sekcji Omówienie.

Przykładowy kod

Przykładowy kod MediaPipe Tasks to prosta implementacja aplikacji do umieszczania obrazów na urządzeniu z Androidem. W tym przykładzie używamy aparatu na fizycznym urządzeniu z Androidem do ciągłego umieszczania obrazów, a także można uruchomić narzędzie do umieszczania na plikach graficznych zapisanych na urządzeniu.

Możesz użyć aplikacji jako punktu wyjścia dla własnej aplikacji na Androida lub skorzystać z niej podczas modyfikowania istniejącej aplikacji. Przykładowy kod do umieszczania obrazów jest hostowany na GitHub.

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 umieszczania obrazów:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_embedder/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 umieszczania obrazów:

  • ImageEmbedderHelper.kt: inicjuje narzędzie do umieszczania obrazów oraz obsługuje wybór modelu i przedstawicieli.
  • MainActivity.kt: implementuje aplikację i tworzy komponenty interfejsu użytkownika.

Konfiguracja

W tej sekcji znajdziesz najważniejsze czynności, jakie należy wykonać, aby skonfigurować środowisko programistyczne i określić projekty kodu, które mają korzystać z kreatora 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 dla Androida.

Zależności

Umieszczanie 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 do umieszczania obrazów MediaPipe wymaga wytrenowanego modelu zgodnego z tym zadaniem. Więcej informacji o dostępnych wytrenowanych modelach do umieszczania obrazów znajdziesz w sekcji poświęconej modelom na stronie z omówieniem zadań.

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

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

Podaj ścieżkę modelu w parametrze ModelAssetPath. W przykładowym kodzie model jest zdefiniowany w funkcji setupImageEmbedder() w pliku ImageEmbedderHelper.kt:

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.

Tworzenie zadania

Aby utworzyć zadanie, możesz użyć funkcji createFromOptions. Funkcja createFromOptions akceptuje opcje konfiguracji umożliwiające ustawienie opcji umieszczania. Więcej informacji o opcjach konfiguracji znajdziesz w artykule Omówienie konfiguracji.

Zadanie do umieszczania obrazów obsługuje 3 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ę odpowiadającą typowi danych wejściowych, aby zobaczyć, jak utworzyć zadanie i uruchomić wnioskowanie.

Obraz

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.IMAGE)
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

Wideo

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.VIDEO)
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

Transmisja na żywo

ImageEmbedderOptions options =
  ImageEmbedderOptions.builder()
    .setBaseOptions(
      BaseOptions.builder().setModelAssetPath("model.tflite").build())
    .setQuantize(true)
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setResultListener((result, inputImage) -> {
         // Process the embedding result here.
    })
    .build();
imageEmbedder = ImageEmbedder.createFromOptions(context, options);
    

Przykładowa implementacja kodu 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 znajdziesz w funkcji setupImageEmbedder() w pliku ImageEmbedderHelper.kt.

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
l2_normalize Określa, czy znormalizować zwrócony wektor cech z normą L2. Użyj tej opcji tylko wtedy, gdy model nie zawiera jeszcze natywnej operacji L2_NORMALIZATION TFLite. W większości przypadków tak się dzieje i w ten sposób można uzyskać normalizację L2 za pomocą wnioskowania TFLite bez potrzeby użycia tej opcji. Boolean False
quantize Określa, czy zwrócone umieszczenie ma zostać poddane kwantyzacji do liczby bajtów za pomocą kwantyzacji skalarnej. Osadzone elementy są domyślnie uznawane za normę jednostki, dlatego każdy wymiar na pewno ma wartość [-1,0, 1,0]. Jeśli tak nie jest, użyj opcji l2_normalize. Boolean False
resultListener Ustawia odbiornik, który asynchronicznie otrzymuje wyniki umieszczania, gdy kreator 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 ustawiono
errorListener Ustawia opcjonalny detektor błędów. Nie dotyczy Nie ustawiono

Przygotuj dane

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

Przed przekazaniem obrazu wejściowego lub ramki do obiektu com.google.mediapipe.framework.image.MPImage musisz go przekonwertować do zadania umieszczania obrazów.

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 przygotowanie danych odbywa się w pliku ImageEmbedderHelper.kt.

Uruchamianie zadania

Aby aktywować wnioskowanie, możesz wywołać funkcję embed odpowiadającą Twojemu trybowi biegowemu. Interfejs Image Embedder API zwraca wektory dystrybucyjne dla obrazu wejściowego lub ramki.

Obraz

ImageEmbedderResult embedderResult = imageEmbedder.embed(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.
ImageEmbedderResult embedderResult =
    imageEmbedder.embedForVideo(image, frameTimestampMs);
    

Transmisja na żywo


// Run inference on the frame. The embedding results will be available
// via the `resultListener` provided in the `ImageEmbedderOptions` when
// the image embedder was created.
imageEmbedder.embedAsync(image, frameTimestampMs);
    

Uwaga:

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

W przykładowym kodzie funkcja embed jest zdefiniowana w pliku ImageEmbedderHelper.kt.

Obsługa i wyświetlanie wyników

Po uruchomieniu wnioskowania zadanie umieszczania obrazów zwraca obiekt ImageEmbedderResult zawierający listę reprezentacji właściwościowych (zmiennoprzecinkowych lub kwantyzowanych) obrazu wejściowego.

Poniżej znajdziesz przykład danych wyjściowych z tego zadania:

ImageEmbedderResult:
  Embedding #0 (sole embedding head):
    float_embedding: {0.0, 0.0, ..., 0.0, 1.0, 0.0, 0.0, 2.0}
    head_index: 0

Uzyskano taki wynik przez umieszczenie następującego obrazu:

Podobieństwo 2 reprezentacji właściwościowych możesz porównać za pomocą funkcji ImageEmbedder.cosineSimilarity. Oto przykładowy kod.

// Compute cosine similarity.
double similarity = ImageEmbedder.cosineSimilarity(
  result.embeddingResult().embeddings().get(0),
  otherResult.embeddingResult().embeddings().get(0));