Przewodnik wykrywania pozycji punktu orientacyjnego na Androidzie

Zadanie MediaPipe Pose Landmarker umożliwia wykrywanie punktów orientacyjnych ciała ludzkiego na obrazie lub filmie. Za pomocą tego zadania możesz identyfikować kluczowe części ciała, analizować postawę oraz kategoryzować ruchy. To zadanie korzysta z modeli systemów uczących się, które działają z pojedynczymi obrazami lub filmami. Zadanie wyprowadza punkty orientacyjne postawy ciała w współrzędnych obrazu i w trójwymiarowych współrzędnych świata.

Przykładowy kod opisany w tych instrukcjach jest dostępny na GitHub. Więcej informacji o możliwościach, modelach i opcjach konfiguracji związanych z tym zadaniem znajdziesz w sekcji Omówienie.

Przykładowy kod

Przykładowy kod MediaPipe Tasks to prosta implementacja aplikacji do wykrywania punktów orientacyjnych postawy na Androida. Przykład wykorzystuje kamerę na fizycznym urządzeniu z Androidem do wykrywania póz w ciągłym strumieniu wideo. Aplikacja może też wykrywać pozy w obrazach i filmach z galerii urządzenia.

Możesz użyć tej aplikacji jako punktu wyjścia do tworzenia własnej aplikacji na Androida lub skorzystać z niej podczas modyfikowania istniejącej aplikacji. Przykładowy kod usługi Pose Landmarker jest hostowany na GitHub.

Pobieranie kodu

Z tych instrukcji 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 do użycia rzadkiego sprawdzania, aby mieć tylko pliki przykładowej aplikacji Pose Landmarker:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/pose_landmarker/android
    

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

Kluczowe komponenty

W tych plikach znajduje się kod, który jest kluczowy dla przykładowej aplikacji do oznaczania punktów orientacyjnych pozy:

  • PoseLandmarkerHelper.kt – inicjalizuje punkt orientacyjny pozy i obsługuje model oraz wybór delegata.
  • CameraFragment.kt – obsługuje kamerę urządzenia i przetwarza dane wejściowe obrazu i wideo.
  • GalleryFragment.kt – współpracuje z OverlayView, aby wyświetlić obraz lub film wyjściowy.
  • OverlayView.kt – implementuje wyświetlanie wykrytych póz.

Konfiguracja

W tej sekcji opisaliśmy kluczowe kroki konfigurowania środowiska programistycznego i projektów kodu w celu używania narzędzia Pose Landmarker. Ogólne informacje o konfigurowaniu środowiska programistycznego do korzystania z zadań MediaPipe, w tym wymagania dotyczące wersji platformy, znajdziesz w przewodniku konfiguracji na Androida.

Zależności

Zadanie polegające na wyznaczaniu punktów orientacyjnych w pozie korzysta z biblioteki com.google.mediapipe:tasks-vision. Dodaj tę zależność do pliku build.gradle aplikacji na Androida:

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

Model

Zadanie MediaPipe Pose Landmarker wymaga przetestowanego modelu, który jest zgodny z tym zadaniem. Więcej informacji o dostępnych wytrenowanych modelach usługi Pose Landmarker znajdziesz w sekcji Modele w omówieniu zadania.

Wybierz i pobierz model, a potem zapisz go w katalogu projektu:

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

W parametrze ModelAssetPath podaj ścieżkę do modelu. W przykładowym kodzie model jest zdefiniowany w pliku PoseLandmarkerHelper.kt:

val modelName = "pose_landmarker_lite.task"
baseOptionsBuilder.setModelAssetPath(modelName)

Tworzenie zadania

Do konfiguracji zadania MediaPipe Pose Landmarker służy funkcja createFromOptions(). Funkcja createFromOptions() akceptuje wartości opcji konfiguracji. Więcej informacji o opcjach konfiguracji znajdziesz w artykule Opcje konfiguracji.

Narzędzie Pose Landmarker obsługuje te typy danych wejściowych: obrazy, pliki wideo i transmisje wideo na żywo. Podczas tworzenia zadania musisz określić tryb wykonywania odpowiadający typowi danych wejściowych. Aby dowiedzieć się, jak utworzyć zadanie, wybierz kartę odpowiadającą typowi danych wejściowych.

Obraz

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(modelName)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder = 
    poseLandmarker.poseLandmarkerOptions.builder()
        .setBaseOptions(baseOptionsBuilder.build())
        .setMinPoseDetectionConfidence(minPoseDetectionConfidence)
        .setMinTrackingConfidence(minPoseTrackingConfidence)
        .setMinPosePresenceConfidence(minposePresenceConfidence)
        .setNumPoses(maxNumPoses)
        .setRunningMode(RunningMode.IMAGE)

val options = optionsBuilder.build()
poseLandmarker = poseLandmarker.createFromOptions(context, options)
    

Wideo

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(modelName)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder = 
    poseLandmarker.poseLandmarkerOptions.builder()
        .setBaseOptions(baseOptionsBuilder.build())
        .setMinPoseDetectionConfidence(minPoseDetectionConfidence)
        .setMinTrackingConfidence(minPoseTrackingConfidence)
        .setMinPosePresenceConfidence(minposePresenceConfidence)
        .setNumPoses(maxNumPoses)
        .setRunningMode(RunningMode.VIDEO)

val options = optionsBuilder.build()
poseLandmarker = poseLandmarker.createFromOptions(context, options)
    

Transmisja na żywo

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(modelName)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder = 
    poseLandmarker.poseLandmarkerOptions.builder()
        .setBaseOptions(baseOptionsBuilder.build())
        .setMinPoseDetectionConfidence(minPoseDetectionConfidence)
        .setMinTrackingConfidence(minPoseTrackingConfidence)
        .setMinPosePresenceConfidence(minposePresenceConfidence)
        .setNumPoses(maxNumPoses)
        .setResultListener(this::returnLivestreamResult)
        .setErrorListener(this::returnLivestreamError)
        .setRunningMode(RunningMode.LIVE_STREAM)

val options = optionsBuilder.build()
poseLandmarker = poseLandmarker.createFromOptions(context, options)
    

Implementacja przykładowego kodu usługi Pose Landmarker umożliwia użytkownikowi przełączanie się między trybami przetwarzania. Takie podejście skomplikuje kod tworzący zadanie i może nie być odpowiednie w Twoim przypadku. Ten kod znajdziesz w funkcji setupPoseLandmarker() w pliku PoseLandmarkerHelper.kt.

Opcje konfiguracji

W tym zadaniu dostępne są te opcje konfiguracji aplikacji na Androida:

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

OBRAZ: tryb dla pojedynczych obrazów wejściowych.

FILM: tryb dekodowanych klatek filmu.

LIVE_STREAM: tryb transmisji na żywo danych wejściowych, takich jak dane z kamery. W tym trybie należy wywołać metodę resultListener, aby skonfigurować odbiornik, który będzie asynchronicznie odbierał wyniki.
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
numposes Maksymalna liczba poz, które może wykryć Landmarker poz. Integer > 0 1
minPoseDetectionConfidence Minimalna wartość ufności wykrywania pozycji, która jest uznawana za prawidłową. Float [0.0,1.0] 0.5
minPosePresenceConfidence Minimalny wynik ufności obecności pozycji w wykrywaniu punktów orientacyjnych postawy. Float [0.0,1.0] 0.5
minTrackingConfidence Minimalny wynik ufności śledzenia pozycji ciała, aby uznać go za udany. Float [0.0,1.0] 0.5
outputSegmentationMasks Określa, czy narzędzie Pose Landmarker wygeneruje maskę segmentacji dla wykrytej postawy. Boolean False
resultListener Ustawia odbiornik wyników tak, aby asynchronicznie otrzymywał wyniki wyszukiwania punktów orientacyjnych, gdy punkt orientacyjny w ramach funkcji Pose Landmarker jest w trybie transmisji na żywo. Można go używać tylko wtedy, gdy tryb działania ma wartość LIVE_STREAM. ResultListener N/A
errorListener Ustawia opcjonalny odbiornik błędów. ErrorListener N/A

Przygotuj dane

Pose Landmarker działa z obrazami, plikami wideo i transmisjami wideo na żywo. Zadanie to obsługuje wstępną obróbkę danych wejściowych, w tym zmianę rozmiaru, obrót i normalizację wartości.

Poniższy kod pokazuje, jak przekazywać dane do przetwarzania. Te przykłady zawierają szczegółowe informacje o obsługiwaniu danych z obrazów, plików wideo i transmisji na żywo.

Obraz

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

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(image).build()
    

Wideo

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

val argb8888Frame =
    if (frame.config == Bitmap.Config.ARGB_8888) frame
    else frame.copy(Bitmap.Config.ARGB_8888, false)

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(argb8888Frame).build()
    

Transmisja na żywo

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

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
    

W przykładowym kodzie usługi Pose Landmarker przygotowanie danych jest obsługiwane w pliku PoseLandmarkerHelper.kt.

Uruchamianie zadania

W zależności od typu danych, z którymi pracujesz, użyj metody poseLandmarker.detect...() odpowiedniej dla tego typu danych. Użyj wartości detect() dla pojedynczych obrazów, detectForVideo() dla klatek w plikach wideo oraz detectAsync() dla strumieni wideo. Podczas wykrywania treści w strumieniu wideo pamiętaj, aby uruchamiać wykrywanie na osobnym wątku, aby nie blokować wątku interpolacji użytkownika.

Poniższe przykłady kodu pokazują proste przykłady uruchamiania usługi Pose Landmarker w różnych trybach danych:

Obraz

val result = poseLandmarker.detect(mpImage)
    

Wideo

val timestampMs = i * inferenceIntervalMs

poseLandmarker.detectForVideo(mpImage, timestampMs)
    .let { detectionResult ->
        resultList.add(detectionResult)
    }
    

Transmisja na żywo

val mpImage = BitmapImageBuilder(rotatedBitmap).build()
val frameTime = SystemClock.uptimeMillis()

poseLandmarker.detectAsync(mpImage, frameTime)
    

Pamiętaj:

  • W trybie wideo lub trybie transmisji na żywo zadanie wyodrębniania punktów orientacyjnych w pozie musi zawierać sygnaturę czasową ramki wejściowej.
  • W trybie obrazu lub filmu zadanie wyszukiwania punktów orientacyjnych w pozie blokuje bieżący wątek, dopóki nie zakończy przetwarzania obrazu wejściowego lub klatki. Aby uniknąć blokowania interfejsu użytkownika, przeprowadź przetwarzanie w procesie w tle.
  • W trybie transmisji na żywo zadanie dotyczące wyznaczenia punktów orientacyjnych pozycji powraca natychmiast i nie blokuje bieżącego wątku. Za każdym razem, gdy skończy przetwarzać daną klatkę wejściową, wywoła komponent odbiorczy z wynikiem wykrywania.

W przykładowym kodzie funkcji do wyznaczania punktów orientacyjnych w położeniu funkcje detect, detectForVideodetectAsync są zdefiniowane w pliku PoseLandmarkerHelper.kt.

Obsługa i wyświetlanie wyników

Funkcja Pose Landmarker zwraca obiekt poseLandmarkerResult dla każdego przebiegu wykrywania. Obiekt wyniku zawiera współrzędne każdego punktu orientacyjnego pozy.

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

PoseLandmarkerResult:
  Landmarks:
    Landmark #0:
      x            : 0.638852
      y            : 0.671197
      z            : 0.129959
      visibility   : 0.9999997615814209
      presence     : 0.9999984502792358
    Landmark #1:
      x            : 0.634599
      y            : 0.536441
      z            : -0.06984
      visibility   : 0.999909
      presence     : 0.999958
    ... (33 landmarks per pose)
  WorldLandmarks:
    Landmark #0:
      x            : 0.067485
      y            : 0.031084
      z            : 0.055223
      visibility   : 0.9999997615814209
      presence     : 0.9999984502792358
    Landmark #1:
      x            : 0.063209
      y            : -0.00382
      z            : 0.020920
      visibility   : 0.999976
      presence     : 0.999998
    ... (33 world landmarks per pose)
  SegmentationMasks:
    ... (pictured below)

Dane wyjściowe zawierają zarówno znormalizowane współrzędne (Landmarks), jak i współrzędne globalne (WorldLandmarks) dla każdego punktu orientacyjnego.

Dane wyjściowe zawierają te sformatowane współrzędne (Landmarks):

  • xy: współrzędne punktu orientacyjnego znormalizowane w zakresie od 0,0 do 1,0 przez szerokość (x) i wysokość (y) obrazu.

  • z: głębokość punktu orientacyjnego, w której głębokość w środku bioder jest traktowana jako punkt początkowy. Im mniejsza wartość, tym obiekt jest bliżej kamery. Wielkość z używa mniej więcej tej samej skali co x.

  • visibility: prawdopodobieństwo, że punkt orientacyjny jest widoczny na zdjęciu.

Dane wyjściowe zawierają te współrzędne świata (WorldLandmarks):

  • x, yz: rzeczywiste współrzędne 3D w metrach, z środkiem bioder jako punktem początkowym.

  • visibility: prawdopodobieństwo, że punkt orientacyjny jest widoczny na zdjęciu.

Na ilustracji poniżej widać wynik wykonania zadania:

Kobieta w położeniu medytacji. Jej pozę wyróżnia szkielet, który wskazuje położenie kończyn i tułowia

Opcjonalna maska podziału na segmenty reprezentuje prawdopodobieństwo, że dany piksel należy do wykrytej osoby. Następujący obraz przedstawia maskę podziału na segmenty danych wyjściowych zadania:

Maska segmentacji poprzedniego obrazu, która wyznacza kształt kobiety

Przykładowy kod usługi do wyznaczania punktów orientacyjnych na podstawie pozy demonstruje, jak wyświetlać wyniki zwrócone przez zadanie. Więcej informacji znajdziesz w klasie OverlayView.