Przewodnik dotyczący wykrywania punktów orientacyjnych dłoni na iOS

Zadanie MediaPipe Ręka umożliwia wykrywanie punktów orientacyjnych rąk na zdjęciu. Z tych instrukcji dowiesz się, jak używać Hand Landmarker w aplikacjach na iOS. Przykład kodu 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 podstawowa implementacja aplikacji Hand Notebooker na iOS. W tym przykładzie do wykrywania punktów na dłoni w ciągłym strumieniu wideo używana jest kamera na fizycznym urządzeniu z iOS. Aplikacja może też wykrywać punkty orientacyjne dłoni na zdjęciach i filmach z galerii urządzenia.

Możesz użyć tej aplikacji jako punktu wyjścia do tworzenia własnej aplikacji na iOS lub jako odniesienia podczas modyfikowania istniejącej aplikacji. Przykładowy kod Hand 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, aby używać rzadkiego sprawdzania, dzięki czemu będziesz mieć tylko pliki przykładowej aplikacji Hand Landmarker:

    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/hand_landmarker/ios/
    

Po utworzeniu lokalnej wersji przykładowego kodu możesz zainstalować bibliotekę zadań MediaPipe, otworzyć projekt za pomocą Xcode i uruchomić aplikację. Instrukcje znajdziesz w przewodniku konfiguracji na iOS.

Kluczowe elementy

Poniższe pliki zawierają kluczowy kod przykładowej aplikacji Hand Notebooker:

  • HandLandmarkerService.swift: Inicjowanie funkcji Hand Landmarker, obsługa wyboru modelu i wykonywanie wnioskowania na podstawie danych wejściowych.
  • CameraViewController.swift: implementuje interfejs użytkownika w trybie wejścia z obrazu na żywo z kamery i wizualizuje wyniki.
  • MediaLibraryViewController.swift: implementuje interfejs użytkownika w trybie wprowadzania nieruchomych obrazów i plików wideo oraz wyświetla wyniki.

Konfiguracja

W tej sekcji opisaliśmy kluczowe kroki konfigurowania środowiska programistycznego i projektów kodu na potrzeby korzystania z Hand Landmarker. Ogólne informacje o konfigurowaniu środowiska programistycznego do korzystania z zadań MediaPipe, w tym wymagania dotyczące wersji platformy, znajdziesz w przewodniku konfiguracji dla iOS.

Zależności

Hand Notebooker korzysta z biblioteki MediaPipeTasksVision, którą trzeba zainstalować za pomocą CocoaPods. Biblioteka jest zgodna z aplikacją w języku Swift i Objective-C i nie wymaga dodatkowej konfiguracji językowej.

Instrukcje instalacji CocoaPods na macOS znajdziesz w przewodniku instalacji CocoaPods. Instrukcje tworzenia Podfile z podstawowymi komponentami potrzebnymi do działania aplikacji znajdziesz w artykule Korzystanie z CocoaPods.

Dodaj pod MediaPipeTasksVision w tabeli Podfile przy użyciu tego kodu:

target 'MyHandLandmarkerApp' do
  use_frameworks!
  pod 'MediaPipeTasksVision'
end

Jeśli Twoja aplikacja zawiera cele testów jednostkowych, dodatkowe informacje o konfigurowaniu Podfile znajdziesz w przewodniku konfiguracji na iOS.

Model

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

Wybierz i pobierz model, a potem dodaj go do katalogu projektu za pomocą Xcode. Instrukcje dodawania plików do projektu Xcode znajdziesz w artykule Zarządzanie plikami i folderami w projekcie Xcode.

Użyj właściwości BaseOptions.modelAssetPath, aby określić ścieżkę do modelu w pakiecie aplikacji. Przykład kodu znajdziesz w następnej sekcji.

Tworzenie zadania

Zadanie Hand Landmarker możesz utworzyć, wywołując jedną z jego funkcji inicjujących. Inicjalizator HandLandmarker(options:) może przyjmować wartości opcji konfiguracji.

Jeśli nie potrzebujesz narzędzia Hand Landmarker zainicjowanego za pomocą niestandardowych opcji konfiguracji, możesz użyć funkcji inicjalizującej HandLandmarker(modelPath:), aby utworzyć narzędzie Hand Landmarker z opcjami domyślnymi. Więcej informacji o opcjach konfiguracji znajdziesz w artykule Omówienie konfiguracji.

Zadanie Hand Landmarker obsługuje 3 typy danych wejściowych: zdjęcia, pliki wideo i transmisje wideo na żywo. Domyślnie HandLandmarker(modelPath:) inicjuje zadanie dotyczące obrazów statycznych. Jeśli chcesz, aby zadanie zostało zainicjowane w celu przetwarzania plików wideo lub transmisji na żywo, użyj parametru HandLandmarker(options:), aby określić tryb działania związany z odtwarzaniem filmów lub transmisji na żywo. Tryb transmisji na żywo wymaga też dodatkowej opcji konfiguracji handLandmarkerLiveStreamDelegate, która umożliwia narzędziu Hand Landmarker asynchroniczne przesyłanie wyników do delegowanego.

Wybierz kartę odpowiadającą Twojemu trybowi działania, aby zobaczyć, jak utworzyć zadanie i uruchomić wnioskowanie.

Swift

Obraz

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "hand_landmarker",
                                      ofType: "task")

let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .image
options.minHandDetectionConfidence = minHandDetectionConfidence
options.minHandPresenceConfidence = minHandPresenceConfidence
options.minTrackingConfidence = minHandTrackingConfidence
options.numHands = numHands

let handLandmarker = try HandLandmarker(options: options)
    

Wideo

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "hand_landmarker",
                                      ofType: "task")

let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .video
options.minHandDetectionConfidence = minHandDetectionConfidence
options.minHandPresenceConfidence = minHandPresenceConfidence
options.minTrackingConfidence = minHandTrackingConfidence
options.numHands = numHands

let handLandmarker = try HandLandmarker(options: options)
    

Transmisja na żywo

import MediaPipeTasksVision

// Class that conforms to the `HandLandmarkerLiveStreamDelegate` protocol and
// implements the method that the hand landmarker calls once it finishes
// performing landmarks detection in each input frame.
class HandLandmarkerResultProcessor: NSObject, HandLandmarkerLiveStreamDelegate {

  func handLandmarker(
    _ handLandmarker: HandLandmarker,
    didFinishDetection result: HandLandmarkerResult?,
    timestampInMilliseconds: Int,
    error: Error?) {

    // Process the hand landmarker result or errors here.

  }
}

let modelPath = Bundle.main.path(
  forResource: "hand_landmarker",
  ofType: "task")

let options = HandLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .liveStream
options.minHandDetectionConfidence = minHandDetectionConfidence
options.minHandPresenceConfidence = minHandPresenceConfidence
options.minTrackingConfidence = minHandTrackingConfidence
options.numHands = numHands

// Assign an object of the class to the `handLandmarkerLiveStreamDelegate`
// property.
let processor = HandLandmarkerResultProcessor()
options.handLandmarkerLiveStreamDelegate = processor

let handLandmarker = try HandLandmarker(options: options)
    

Objective-C

Obraz

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeImage;
options.minHandDetectionConfidence = minHandDetectionConfidence;
options.minHandPresenceConfidence = minHandPresenceConfidence;
options.minTrackingConfidence = minHandTrackingConfidence;
options.numHands = numHands;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

Wideo

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeVideo;
options.minHandDetectionConfidence = minHandDetectionConfidence;
options.minHandPresenceConfidence = minHandPresenceConfidence;
options.minTrackingConfidence = minHandTrackingConfidence;
options.numHands = numHands;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

Transmisja na żywo

@import MediaPipeTasksVision;

// Class that conforms to the `MPPHandLandmarkerLiveStreamDelegate` protocol
// and implements the method that the hand landmarker calls once it finishes
// performing landmarks detection in each input frame.

@interface APPHandLandmarkerResultProcessor : NSObject 

@end

@implementation APPHandLandmarkerResultProcessor

-   (void)handLandmarker:(MPPHandLandmarker *)handLandmarker
    didFinishDetectionWithResult:(MPPHandLandmarkerResult *)handLandmarkerResult
         timestampInMilliseconds:(NSInteger)timestampInMilliseconds
                           error:(NSError *)error {

    // Process the hand landmarker result or errors here.

}

@end

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"hand_landmarker"
                                                      ofType:@"task"];

MPPHandLandmarkerOptions *options = [[MPPHandLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeLiveStream;
options.minHandDetectionConfidence = minHandDetectionConfidence;
options.minHandPresenceConfidence = minHandPresenceConfidence;
options.minTrackingConfidence = minHandTrackingConfidence;
options.numHands = numHands;

// Assign an object of the class to the `handLandmarkerLiveStreamDelegate`
// property.
APPHandLandmarkerResultProcessor *processor =
  [APPHandLandmarkerResultProcessor new];
options.handLandmarkerLiveStreamDelegate = processor;

MPPHandLandmarker *handLandmarker =
  [[MPPHandLandmarker alloc] initWithOptions:options error:nil];
    

Opcje konfiguracji

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

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

IMAGE (Obraz): tryb do wprowadzania pojedynczych obrazów.

FILM: tryb dekodowanych klatek filmu.

TRANSMISJA NA ŻYWO: tryb transmisji danych wejściowych, np. z kamery. W tym trybie należy wywołać metodę resultListener, aby skonfigurować odbiornik, który będzie asynchronicznie odbierał wyniki. W tym trybie handLandmarkerLiveStreamDelegate musi być ustawiony jako instancja klasy, która implementuje HandLandmarkerLiveStreamDelegate, aby asynchronicznie otrzymywać wyniki wykrywania punktów orientacyjnych dłoni.
{RunningMode.image, RunningMode.video, RunningMode.liveStream} RunningMode.image
numHands Maksymalna liczba rąk wykrywanych przez detektor punktów orientacyjnych dłoni. Any integer > 0 1
minHandDetectionConfidence Minimalny wynik ufności wykrywania dłoni, który jest uznawany za udany w przypadku modelu wykrywania dłoni. 0.0 - 1.0 0.5
minHandPresenceConfidence Minimalny wynik ufności dla wyniku obecności ręki w modelu wykrywania punktów orientacyjnych ręki. W trybie wideo i w trybie transmisji na żywo, jeśli wskaźnik ufności obecności ręki z modelu punktów orientacyjnych ręki jest poniżej tego progu, narzędzie Hand Landmarker uruchamia model wykrywania dłoni. W przeciwnym razie lekki algorytm śledzenia dłoni określa położenie dłoni na potrzeby wykrywania kolejnych punktów orientacyjnych. 0.0 - 1.0 0.5
minTrackingConfidence Minimalny wynik ufności śledzenia dłoni, który jest uznawany za udany. To próg współczynnika podobieństwa ramki ograniczającej między dłońmi w bieżącej i ostatniej ramie. W trybie wideo i trybie strumieniowania w Hand Landmarker, jeśli śledzenie się nie powiedzie, Hand Landmarker uruchamia wykrywanie dłoni. W przeciwnym razie pomija wykrywanie dłoni. 0.0 - 1.0 0.5
result_listener Ustawia odbiornik wyników tak, aby asynchronicznie otrzymywał wyniki wykrywania, gdy punkt odniesienia ręki jest w trybie transmisji na żywo. Ma zastosowanie tylko wtedy, gdy tryb działania ma wartość LIVE_STREAM Nie dotyczy Nie dotyczy

Gdy tryb działania jest ustawiony na transmisję na żywo, narzędzie Hand celem wymaga dodatkowej opcji konfiguracji handLandmarkerLiveStreamDelegate, która umożliwia asynchroniczne dostarczanie wyników wykrywania punktów orientacyjnych rąk. Przedstawiciel musi wdrożyć metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:), którą wywołuje narzędzie do punktu orientacyjnego „Ręka” po przetworzeniu wyników wykrywania punktów orientacyjnych ręcznych dla każdej klatki.

Nazwa opcji Opis Zakres wartości Wartość domyślna
handLandmarkerLiveStreamDelegate Umożliwia ręcznemu narzędziu do wykrywania punktów orientacyjnych ręcznego obrazu w trybie transmisji na żywo asynchroniczne otrzymywanie wyników wykrywania punktów orientacyjnych. Klasa, której instancja jest ustawiona w tej właściwości, musi implementować metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:). Nie dotyczy Nie ustawiono

Przygotuj dane

Przed przekazaniem do usługi Hand Landmarker musisz przekonwertować podany obraz lub ramkę na obiekt MPImage. MPImage obsługuje różne typy formatów obrazów iOS i może ich używać w dowolnym trybie działania do wnioskowania. Więcej informacji o MPImage znajdziesz w dokumentacji interfejsu MPImage API.

Wybierz format obrazu iOS na podstawie przypadku użycia i trybułu działania wymaganego przez aplikację. MPImage obsługuje formaty obrazów iOS UIImage, CVPixelBufferCMSampleBuffer.

UIImage

Format UIImage jest odpowiedni do tych trybów działania:

  • Obrazy: obrazy z pakietu aplikacji, galerii użytkownika lub systemu plików sformatowane jako obrazy UIImage można przekonwertować na obiekt MPImage.

  • Filmy: użyj narzędzia AVAssetImageGenerator, aby wyodrębnić klatki wideo do formatu CGImage, a następnie przekonwertuj je na obrazy UIImage.

Swift

// Load an image on the user's device as an iOS `UIImage` object.

// Convert the `UIImage` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(uiImage: image)
    

Objective-C

// Load an image on the user's device as an iOS `UIImage` object.

// Convert the `UIImage` object to a MediaPipe's Image object having the default
// orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
    

Przykład inicjalizuje MPImage z domyślnym ułożeniem UIImage.Orientation.Up. Możesz zainicjować MPImage dowolną z obsługiwanych wartości UIImage.Orientation. Ręczny punkt orientacyjny nie obsługuje odbicia lustrzanego orientacji, np. .upMirrored, .downMirrored, .leftMirrored i .rightMirrored.

Więcej informacji o UIImage znajdziesz w dokumentacji UIImage dla deweloperów Apple.

CVPixelBuffer

Format CVPixelBuffer sprawdza się w aplikacjach, które generują ramki i wykorzystują do przetwarzania platformę CoreImage na iOS.

Format CVPixelBuffer jest odpowiedni do tych trybów działania:

  • Obrazy: aplikacje, które generują obrazy CVPixelBuffer po przetworzeniu ich za pomocą frameworka CoreImage na iOS, mogą być wysyłane do Hand Landmarker w trybie uruchamiania obrazu.

  • Filmy: klatki wideo można przekonwertować na format CVPixelBuffer podczas przetwarzania, a potem wysłać do rękodzieła w trybie wideo.

  • transmisja na żywo: aplikacje korzystające z kamery iOS do generowania klatek mogą być konwertowane do formatu CVPixelBuffer na potrzeby przetwarzania, zanim zostaną wysłane do Hand Landmarker w trybie transmisji na żywo.

Swift

// Obtain a CVPixelBuffer.

// Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(pixelBuffer: pixelBuffer)
    

Objective-C

// Obtain a CVPixelBuffer.

// Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the
// default orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
    

Więcej informacji o funkcji CVPixelBuffer znajdziesz w dokumentacji dla deweloperów Apple (CVPixelBuffer).

CMSampleBuffer

Format CMSampleBuffer przechowuje próbki multimediów w ramach jednolitego typu multimediów i sprawdza się w trybie biegu transmisji na żywo. Ramki na żywo z kamer iOS są asynchronicznie dostarczane w formacie CMSampleBuffer przez AVCaptureVideoDataOutput.

Swift

// Obtain a CMSampleBuffer.

// Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(sampleBuffer: sampleBuffer)
    

Objective-C

// Obtain a `CMSampleBuffer`.

// Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the
// default orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithSampleBuffer:sampleBuffer error:nil];
    

Więcej informacji o CMSampleBuffer znajdziesz w dokumentacji CMSampleBuffer dla deweloperów Apple.

Uruchamianie zadania

Aby uruchomić punkt orientacyjny dłoni, użyj metody detect() specyficznej dla przypisanego trybu uruchamiania:

  • Statyczny obraz: detect(image:)
  • Film: detect(videoFrame:timestampInMilliseconds:)
  • Transmisja na żywo: detectAsync(image:timestampInMilliseconds:)

Swift

Obraz

let result = try handLandmarker.detect(image: image)
    

Wideo

let result = try handLandmarker.detect(
    videoFrame: image,
    timestampInMilliseconds: timestamp)
    

Transmisja na żywo

try handLandmarker.detectAsync(
  image: image,
  timestampInMilliseconds: timestamp)
    

Objective-C

Obraz

MPPHandLandmarkerResult *result =
  [handLandmarker detectInImage:image error:nil];
    

Wideo

MPPHandLandmarkerResult *result =
  [handLandmarker detectInVideoFrame:image
             timestampInMilliseconds:timestamp
                               error:nil];
    

Transmisja na żywo

BOOL success =
  [handLandmarker detectAsyncInImage:image
             timestampInMilliseconds:timestamp
                               error:nil];
    

Przykład kodu Hand Landmarker zawiera bardziej szczegółowe informacje o implementacji każdego z tych trybów. Przykładowy kod pozwala użytkownikowi przełączać się między trybami przetwarzania, co może nie być wymagane w Twoim przypadku użycia.

Pamiętaj:

  • W przypadku działania w trybie wideo lub w trybie transmisji na żywo musisz też podać do zadania Hand orientacyjnego sygnaturę czasową klatki wejściowej.

  • W trybie obrazu lub filmu zadanie Hand Landmarker blokuje bieżący wątek, dopóki nie zakończy przetwarzania wejściowego obrazu lub ramki. Aby uniknąć blokowania bieżącego wątku, przeprowadź przetwarzanie w wątku tła za pomocą frameworków iOS Dispatch lub NSOperation.

  • W trybie transmisji na żywo zadanie Hand Landmarker zwraca dane natychmiast i nie blokuje bieżącego wątku. Po przetworzeniu każdego wejściowego obrazu wywołuje metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) z wynikiem funkcji wykrywania punktów orientacyjnych dłoni. Wskaźnik Hand Landmarker wywołuje tę metodę asynchronicznie w ramach dedykowanej kolejki wysyłania sekwencyjnego. Aby wyświetlić wyniki w interfejsie, prześlij je do kolejki głównej po przetworzeniu. Jeśli funkcja detectAsync jest wywoływana, gdy zadanie Hand Landmarker jest zajęte przetwarzaniem innego kadru, Hand Landmarker ignoruje nowy klatka wejściowa.

Obsługa i wyświetlanie wyników

Po przeprowadzeniu wnioskowania zadanie Hand Landmarker zwraca HandLandmarkerResult, które zawiera punkty orientacyjne dłoni w koordynatach obrazu, punkty orientacyjne dłoni w koordynatach świata oraz rękę(lewą lub prawą) uchwytu.

Poniżej znajdziesz przykładowe dane wyjściowe tego zadania:

Dane wyjściowe HandLandmarkerResult zawierają 3 komponenty. Każdy komponent to tablica, której każdy element zawiera te wyniki dla jednej wykrytej ręki:

  • Ręka dominująca

    Ręka określa, czy wykryta ręka jest lewą czy prawą.

  • Punkty orientacyjne

    Dostępnych jest 21 punktów orientacyjnych rozmieszczonych na dłoni, a każdy z nich składa się ze współrzędnych x, y i z. współrzędne xy są normalizowane do zakresu [0,0, 1,0] odpowiednio według szerokości i wysokości obrazu. Współrzędna z reprezentuje głębokość punktu orientacyjnego, przy czym punktem wyjścia jest głębokość na wysokości nadgarstka. Im mniejsza wartość, tym obiektyw jest bliżej zabytku. Wielkość z używa mniej więcej tej samej skali co x.

  • Punkty orientacyjne na świecie

    21 punktów orientacyjnych dłoni jest też przedstawionych w współrzędnych światowych. Każdy punkt orientacyjny składa się z wartości x, yz, które reprezentują rzeczywiste współrzędne 3D w metrach z początkiem w geometrycznym środku dłoni.

HandLandmarkerResult:
  Handedness:
    Categories #0:
      index        : 0
      score        : 0.98396
      categoryName : Left
  Landmarks:
    Landmark #0:
      x            : 0.638852
      y            : 0.671197
      z            : -3.41E-7
    Landmark #1:
      x            : 0.634599
      y            : 0.536441
      z            : -0.06984
    ... (21 landmarks for a hand)
  WorldLandmarks:
    Landmark #0:
      x            : 0.067485
      y            : 0.031084
      z            : 0.055223
    Landmark #1:
      x            : 0.063209
      y            : -0.00382
      z            : 0.020920
    ... (21 world landmarks for a hand)

Na ilustracji poniżej widać wizualizację wyniku zadania: