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

Zadanie MediaPipe Hand Pointer umożliwia wykrywanie punktów orientacyjnych dłoni na zdjęciu. Te instrukcje pokazują, jak korzystać z Podręcznego punktu orientacyjnego w aplikacjach na iOS. 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ładowy kod MediaPipe Tasks to podstawowa implementacja aplikacji Hand MTAer na iOS. W tym przykładzie użyto aparatu w fizycznym urządzeniu z iOS, aby wykryć punkty orientacyjne w ciągłym strumieniu wideo. Aplikacja może też wykrywać punkty orientacyjne na obrazach i filmach z galerii urządzenia.

Możesz użyć aplikacji jako punktu wyjścia dla własnej aplikacji na iOS lub skorzystać z niej podczas modyfikowania istniejącej aplikacji. Przykładowy kod obszaru orientacyjnego 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 w taki sposób, aby używała rozproszonego procesu płatności, aby mieć tylko pliki dla przykładowej aplikacji Notatnik Handlowy:

    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 po konfiguracji na iOS.

Kluczowe elementy

Te pliki zawierają kluczowy kod dla przykładowej aplikacji „Hang Pointer”:

  • HandLandmarkerService.swift: inicjuje narzędzie do tworzenia punktów orientacyjnych, obsługuje wybór modelu i uruchamia wnioskowanie na danych wejściowych.
  • CameraViewController.swift: implementuje interfejs użytkownika w trybie przesyłania obrazu z kamery na żywo i wizualizuje wyniki.
  • MediaLibraryViewController.swift: implementuje interfejs użytkownika w trybie wprowadzania plików nieruchomych obrazów i filmów oraz wizualizuje wyniki.

Konfiguracja

W tej sekcji opisujemy najważniejsze czynności, jakie należy wykonać, by skonfigurować środowisko programistyczne i projekt kodu, aby móc korzystać z Wskaźnika ręcznego. Ogólne informacje o konfigurowaniu środowiska programistycznego na potrzeby zadań MediaPipe, w tym o wymaganiach dotyczących wersji platformy, znajdziesz w przewodniku konfiguracji dla iOS.

Zależności

Narzędzie do rysowania ręczne korzysta z biblioteki MediaPipeTasksVision, która musi być zainstalowana za pomocą CocoaPods. Biblioteka jest zgodna z aplikacjami Swift i Objective-C i nie wymaga dodatkowej konfiguracji pod kątem określonego języka.

Instrukcje instalowania CocoaPods w systemie macOS znajdziesz w przewodniku instalacji CocoaPods. Instrukcje tworzenia Podfile z podami niezbędnymi do działania aplikacji znajdziesz w artykule Korzystanie z CocoaPods.

Dodaj pod MediaPipeTasksVision w narzędziu Podfile za pomocą tego kodu:

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

Jeśli aplikacja zawiera cele testu jednostkowego, dodatkowe informacje o konfigurowaniu Podfile znajdziesz w przewodniku konfiguracji na iOS.

Model

Zadanie MediaPipe Hand Pointer wymaga wytrenowanego modelu zgodnego z tym zadaniem. Więcej informacji o dostępnych wytrenowanych modelach na potrzeby ręcznego punktu orientacyjnego znajdziesz w sekcji przeglądu zadań w sekcji „Modele”.

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

Aby określić ścieżkę do modelu w pakiecie aplikacji, użyj właściwości BaseOptions.modelAssetPath. Przykładowy kod znajdziesz w następnej sekcji.

Tworzenie zadania

Zadanie Notatnika ręcznego możesz utworzyć, wywołując jeden z jego inicjatorów. Inicjator HandLandmarker(options:) akceptuje wartości opcji konfiguracji.

Jeśli nie potrzebujesz zainicjowanego ręcznego punktu orientacyjnego zainicjowanego z niestandardowymi opcjami konfiguracji, możesz użyć inicjatora HandLandmarker(modelPath:), aby utworzyć ręcznie punkt orientacyjny z opcjami domyślnymi. Więcej informacji o opcjach konfiguracji znajdziesz w artykule Omówienie konfiguracji.

Zadanie tworzenia punktów orientacyjnych rąk obsługuje 3 typy danych wejściowych: obrazy, pliki wideo i strumienie wideo na żywo. Domyślnie HandLandmarker(modelPath:) inicjuje zadanie dotyczące nieruchomych obrazów. Jeśli chcesz zainicjować zadanie na potrzeby przetwarzania plików wideo lub strumieni wideo na żywo, użyj funkcji HandLandmarker(options:), aby określić tryb transmisji wideo lub transmisji na żywo. Tryb transmisji na żywo wymaga też dodatkowej opcji konfiguracji handLandmarkerLiveStreamDelegate, która umożliwia asynchronicznie ręcznemu dostarczaniu przez niego wyników punktów orientacyjnych.

Wybierz kartę odpowiadającą Twojemu trybowi biegowemu, 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

To zadanie ma te opcje konfiguracji w przypadku aplikacji na iOS:

Nazwa opcji Opis Zakres wartości Wartość domyślna
running_mode 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. W tym trybie handLandmarkerLiveStreamDelegate musi być ustawiony na instancję 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 wykrytych przez detektor punktów orientacyjnych dłoni. Any integer > 0 1
minHandDetectionConfidence Minimalny stopień pewności, że wykrywanie dłoni zostanie uznane za udane w modelu wykrywania dłoni. 0.0 - 1.0 0.5
minHandPresenceConfidence Minimalny wskaźnik 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 dla modelu punktu orientacyjnego dłoni jest poniżej tego progu, komponent wskazuje rękę ręcznie uruchamia model wykrywania dłoni. W przeciwnym razie do wykrywania punktów orientacyjnych potrzebny jest uproszczony algorytm śledzenia dłoni. 0.0 - 1.0 0.5
minTrackingConfidence Minimalny wynik pewności, że śledzenie dłoni zostanie uznane za udane. Jest to próg interfejsu użytkownika w ramce ograniczającej między rękami w bieżącej a ostatniej klatce. W trybie wideo i trybie strumienia Wskaźnika ręcznego, jeśli śledzenie nie powiedzie się, wskazujesz rękę ręcznie. W przeciwnym razie pomijane jest wykrywanie rąk. 0.0 - 1.0 0.5
result_listener Konfiguruje detektor wyników, aby asynchronicznie otrzymywać wyniki wykrywania, gdy punkt orientacyjny z ręczną ręką działa w trybie transmisji na żywo. Ma zastosowanie tylko wtedy, gdy tryb działania jest ustawiony na LIVE_STREAM Nie dotyczy Nie dotyczy

Gdy tryb biegowy jest ustawiony na transmisję na żywo, Znacznik orientacyjny ręcznie wymaga dodatkowej opcji konfiguracji handLandmarkerLiveStreamDelegate, która umożliwia asynchronicznie wyświetlanie wyników wykrywania punktów orientacyjnych dłoni. Osoba, której przekazano dostęp, musi wdrożyć metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:), która jest wywoływana przez punkt orientacyjny ręcznie po przetworzeniu wyników wykrywania punktów orientacyjnych dla każdej klatki.

Nazwa opcji Opis Zakres wartości Wartość domyślna
handLandmarkerLiveStreamDelegate Umożliwia asynchronicznie odbieranie wyników wykrywania punktów orientacyjnych dłoni w trybie transmisji na żywo. Klasa, której instancja jest ustawiona na tę właściwość, musi implementować metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:). Nie dotyczy Nie ustawiono

Przygotuj dane

Przed przekazaniem obrazu wejściowego lub ramki do obiektu MPImage musisz go przekonwertować do Punktu orientacyjnego Ręka. MPImage obsługuje różne typy formatów obrazów na iOS i może ich używać w każdym trybie działania, aby wnioskować. Więcej informacji na temat MPImage znajdziesz w interfejsie MPImage API

Wybierz format obrazu na iOS zależnie od swojego przypadku użycia i trybu uruchamiania, którego wymaga Twoja aplikacja.MPImage akceptuje formaty obrazów UIImage, CVPixelBuffer i CMSampleBuffer (iOS).

UIImage

Format UIImage dobrze sprawdza się w tych trybach biegania:

  • 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];
    

W tym przykładzie inicjuje się MPImage z domyślną orientacją UIImage.Orientation.Up. Możesz zainicjować MPImage przy użyciu dowolnej z obsługiwanych wartości UIImage.Orientation. Odbicie lustrzane w orientacji poziomej nie obsługuje odbicia lustrzanego, np. .upMirrored, .downMirrored, .leftMirrored i .rightMirrored.

Więcej informacji na temat UIImage znajdziesz w dokumentacji dla programistów Apple dotyczącej UIImage (w języku angielskim).

CVPixelBuffer

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

Format CVPixelBuffer dobrze sprawdza się w tych trybach biegania:

  • Obrazy: aplikacje generujące obrazy CVPixelBuffer po przetworzeniu przy użyciu platformy CoreImage systemu iOS mogą być wysyłane do Kreatora map ręcznie w trybie wyświetlania obrazów.

  • Filmy: klatki wideo można przekonwertować do formatu CVPixelBuffer w celu przetworzenia, a następnie wysłać do narzędzia ręcznego w trybie wideo.

  • transmisja na żywo: aplikacje używające kamery systemu iOS do generowania klatek można skonwertować do formatu CVPixelBuffer w celu przetworzenia, zanim zostaną przesłane do narzędzia ręcznego 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 na temat CVPixelBuffer znajdziesz w dokumentacji dla programistów Apple CVPixelBuffer.

CMSampleBuffer

Format CMSampleBuffer przechowuje próbki multimediów jednolitego typu i dobrze sprawdza się w trybie transmisji na żywo. Klatki na żywo z kamer na iOS są przesyłane asynchronicznie w formacie CMSampleBuffer przez system iOS 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 na temat CMSampleBuffer znajdziesz w dokumentacji dla programistów Apple CMSampleBuffer.

Uruchamianie zadania

Aby uruchomić Punkt orientacyjny ręcznie, użyj metody detect() specyficznej dla przypisanego trybu biegowego:

  • Nieruchomy 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];
    

W przykładowym kodzie obszaru orientacyjnym „ręcznym” przedstawiono bardziej szczegółowo implementacje każdego z tych trybów. Przykładowy kod pozwala użytkownikowi przełączać się między trybami przetwarzania, które w Twoim przypadku mogą nie być wymagane.

Uwaga:

  • W trybie wideo lub w trybie transmisji na żywo musisz też podać sygnaturę czasową ramki wejściowej w zadaniu Wskażnika dłoni.

  • Gdy działa w trybie graficznym lub wideo, zadanie Wskażnik dłoni blokuje 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 za pomocą platform Dispatch lub NSOperation w systemie iOS.

  • W trybie transmisji na żywo zadanie wskazana ręcznie wraca od razu i nie blokuje bieżącego wątku. Po przetworzeniu każdej ramki wejściowej wywołuje metodę handLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) z wynikiem ręcznego tworzenia punktów orientacyjnych. Znak orientacyjny wyzwala tę metodę asynchronicznie w dedykowanej szeregowej kolejce wysyłki. W przypadku wyświetlania wyników w interfejsie po ich przetworzeniu wyślij do głównej kolejki. Jeśli funkcja detectAsync zostanie wywołana, gdy zadanie Mapa witryny jest zajęte przetwarzaniem innej ramki, ta funkcja zignoruje nową ramkę wejściową.

Obsługa i wyświetlanie wyników

Po uruchomieniu wnioskowania zadanie Punkt orientacyjny do ręcznego zwraca wartość HandLandmarkerResult, która zawiera punkty orientacyjne na zdjęciu, punkty orientacyjne we współrzędnych świata i ręce(lewa/prawa ręka) wykrytych rąk.

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

Dane wyjściowe HandLandmarkerResult zawierają 3 komponenty. Każdy komponent jest tablicą, w której każdy element zawiera te wyniki dotyczące pojedynczej wykrytej dłoni:

  • Ręka dominująca

    Ręka wskazuje, czy wykryte ręce są lewą czy prawą ręką.

  • Punkty orientacyjne

    Jest 21 punktów orientacyjnych wskazujących dłonie, a każdy z nich składa się ze współrzędnych x, y i z. Współrzędne x i y są normalizowane do wartości [0,0, 1,0] odpowiednio do szerokości i wysokości obrazu. Współrzędna z reprezentuje głębokość punktu orientacyjnego, przy czym głębokość na nadgarstku jest punktem początkowym. Im mniejsza wartość, tym zbliża się punkt orientacyjny do aparatu. Siła działania z jest mniej więcej zbliżona do skali x.

  • Punkty orientacyjne na świecie

    We współrzędnych świata są również przedstawione 21 punktów orientacyjnych. Każdy punkt orientacyjny składa się z elementów x, y i z, które reprezentują rzeczywiste współrzędne 3D w metrach z punktem początkowym w środku geometrycznym 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)

Poniższy obraz przedstawia wizualizację wyników zadania: