Przewodnik po segmentacji obrazów na iOS

Zadanie segmentacji obrazów pozwala dzielić obrazy na regiony na podstawie zdefiniowanych wstępnie kategorii i stosować efekty wizualne, takie jak rozmycie tła. Te instrukcje pokazują, jak używać narzędzia do dzielenia obrazu w przypadku aplikacji na iOS.

Przykładowy kod opisany w tych instrukcjach jest dostępny na GitHubzie.

Aby zobaczyć, jak to zadanie działa w praktyce, obejrzyj prezentację demonstracyjną w internecie. 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 zawiera prostą implementację aplikacji Image Segmenter na iOS.

Przykład wdraża segmentator obrazu, który generuje maski kategorii. Używa ona kamery na fizycznym urządzeniu z iOS, aby segmentować obrazy na podstawie obrazu na żywo z kamery lub zdjęć i filmów z galerii urządzenia.

Możesz użyć tej aplikacji jako punktu wyjścia do utworzenia własnej aplikacji na iOS lub skorzystać z niej podczas modyfikowania istniejącej aplikacji. Przykładowy kod Image Segmenter 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 skromnego sprawdzania, aby mieć tylko pliki przykładowej aplikacji Image Segmenter:

    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_segmentation/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 dla iOS.

Kluczowe komponenty

Te pliki zawierają kluczowy kod aplikacji przykładowej Image Segmenter:

Konfiguracja

Ta sekcja zawiera opis kluczowych kroków konfigurowania środowiska programistycznego i projektów kodu na potrzeby korzystania z narzędzia Image Segmenter. 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

Narzędzie do podziału obrazu korzysta z biblioteki MediaPipeTasksVision, którą należy zainstalować za pomocą CocoaPods. Biblioteka jest zgodna z aplikacjami Swift i Objective-C oraz nie wymaga dodatkowej konfiguracji języka.

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 podproces MediaPipeTasksVision w pliku Podfile, używając tego kodu:

target 'MyImageSegmenterApp' 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 Image segmenter wymaga wytrenowanego modelu, który jest zgodny z tym zadaniem. Więcej informacji o dostępnych wytrenowanych modelach narzędzia Image Segmenter 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, by określić ścieżkę do modelu w pakiecie aplikacji. Przykład kodu znajdziesz w następnej sekcji.

Tworzenie zadania

Zadanie segmentacji obrazów możesz utworzyć, wywołując jeden z jego inicjatorów. Inicjator ImageSegmenter(options:) akceptuje wartości opcji konfiguracji.

Jeśli nie potrzebujesz segmentu graficznego zainicjowanego z niestandardowymi opcjami konfiguracji, możesz użyć inicjatora ImageSegmenter(modelPath:), aby utworzyć segmenter obrazów z opcjami domyślnymi. Więcej informacji o opcjach konfiguracji znajdziesz w artykule Omówienie konfiguracji.

Zadanie segmentacji obrazu obsługuje 3 typy danych wejściowych: obrazy, pliki wideo i strumienie wideo na żywo. Domyślnie ImageSegmenter(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 ImageSegmenter(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 imageSegmenterLiveStreamDelegate, która umożliwia narzędziu Image Segmenter asynchroniczne przesyłanie wyników segmentacji obrazu do delegata.

Aby dowiedzieć się, jak utworzyć zadanie i przeprowadzić wnioskowanie, wybierz kartę odpowiadającą trybowi działania.

Swift

Obraz

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .image
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

let imageSegmenter = try ImageSegmenter(options: options)
    

Wideo

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .video
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

let imageSegmenter = try ImageSegmenter(options: options)
    

Transmisja na żywo

import MediaPipeTasksVision

// Class that conforms to the `imageSegmenterLiveStreamDelegate` protocol and
// implements the method that the image segmenter calls once it finishes
// performing segmentation of each input frame.
class ImageSegmenterResultProcessor: NSObject, ImageSegmenterLiveStreamDelegate {

  func imageSegmenter(
    _ imageSegmenter: ImageSegmenter,
    didFinishSegmentation result: ImageSegmenterResult?,
    timestampInMilliseconds: Int,
    error: Error?) {

    // Process the image segmentation result or errors here.

  }
}

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .liveStream
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

// Set `imageSegmenterLiveStreamDelegate` to the object of the class that
// confirms to the `ImageSegmenterLiveStreamDelegate` protocol.
let processor = ImageSegmenterResultProcessor()
options.imageSegmenterLiveStreamDelegate = processor

let imageSegmenter = try ImageSegmenter(options: options)
    

Objective-C

Obraz

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeImage;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

Wideo

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeVideo;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

Transmisja na żywo

@import MediaPipeTasksVision;

// Class that conforms to the `MPPImageSegmenterLiveStreamDelegate` protocol
// and implements the method that the image segmenter calls once it finishes
// performing segmentation of each input frame.

@interface APPImageSegmenterResultProcessor : NSObject 

@end

@implementation APPImageSegmenterResultProcessor

-   (void)imageSegmenter:(MPPImageSegmenter *)imageSegmenter
    didFinishSegmentationWithResult:(MPPImageSegmenterResult *)imageSegmenterResult
         timestampInMilliseconds:(NSInteger)timestampInMilliseconds
                           error:(NSError *)error {

    // Process the image segmentation result or errors here.

}

@end

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeLiveStream;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

// Set `imageSegmenterLiveStreamDelegate` to the object of the class that
// confirms to the `MPPImageSegmenterLiveStreamDelegate` protocol.
APPImageSegmenterResultProcessor *processor =
  [APPImageSegmenterResultProcessor new];
options.imageSegmenterLiveStreamDelegate = processor;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

Implementacja przykładowego kodu segmentacji obrazu 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.

Opcje konfiguracji

To zadanie zawiera te opcje konfiguracji aplikacji na iOS:

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

IMAGE: tryb używany do przesyłania pojedynczego obrazu.

FILM: tryb dekodowanych klatek filmu.

LIVE_STREAM: tryb transmisji na żywo danych wejściowych, takich jak dane z kamery. W tym trybie parametr ImageSegmenterLiveStreamDelegatemusi być ustawiony na instancję klasy, która implementuje interfejs ImageSegmenterLiveStreamDelegate, aby asynchronicznie otrzymywać wyniki podziału na segmenty.
{RunningMode.image, RunningMode.video, RunningMode.liveStream} RunningMode.image
shouldOutputCategoryMask Jeśli ustawisz tę opcję na True, dane wyjściowe będą zawierać maskę segmentacji w postaci obrazu uint8, gdzie każda wartość piksela wskazuje zwycięską kategorię. {True, False} False
shouldOutputConfidenceMasks Jeśli ustawisz tę opcję na True, dane wyjściowe będą zawierać maskę segmentacji w postaci obrazu z wartością zmiennoprzecinkową, gdzie każda wartość zmiennoprzecinkowa reprezentuje mapę współczynnika zaufania danej kategorii. {True, False} True
displayNamesLocale Określa język etykiet, które mają być używane do wyświetlanych nazw podanych w metadanych modelu zadania (jeśli są dostępne). Wartość domyślna to en w przypadku języka angielskiego. Możesz dodać zlokalizowane etykiety do metadanych modelu niestandardowego za pomocą interfejsu TensorFlow Lite Metadata Writer API. Kod języka en
result_callback Ustawia odbiornik wyników tak, aby asynchronicznie otrzymywał wyniki podziału na segmenty, gdy segmentator obrazu jest w trybie LIVE_STREAM. Można go używać tylko wtedy, gdy tryb działania ma wartość LIVE_STREAM. Nie dotyczy Nie dotyczy

Gdy tryb działania jest ustawiony na LIVE_STREAM, segmentacja obrazów wymaga dodatkowej opcji konfiguracji imageSegmenterLiveStreamDelegate, która umożliwia podziałowi obrazów asynchronicznemu dostarczanie wyników segmentacji. Delegat musi zaimplementować metodę imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:), którą ImageSegmenter wywołuje po przetworzeniu wyników podziału na segmenty każdego kadru.

Nazwa opcji Opis Zakres wartości Wartość domyślna
imageSegmenterLiveStreamDelegate Umożliwia narzędziu Image Segmenter asynchroniczne otrzymywanie wyników segmentacji obrazu w trybie transmisji na żywo. Klasa, której instancja jest ustawiona w tej właściwości, musi implementować metodę imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:). Nie dotyczy Nie ustawiono

Przygotuj dane

Przed przekazaniem do segmentatora obrazu musisz przekonwertować podany obraz lub kadr na obiekt MPImage. MPImage obsługuje różne typy formatów obrazów iOS i może używać ich w dowolnym trybie działania w celu 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 przekształcić w 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 przy użyciu dowolnej z obsługiwanych wartości UIImage.Orientation. Segmentacja obrazów nie obsługuje lustrzanych orientacji, takich jak .upMirrored, .downMirrored, .leftMirrored, .rightMirrored.

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

CVPixelBuffer

Format CVPixelBuffer jest odpowiedni dla aplikacji, które generują klatki i korzystają z ramy CoreImage na iOS do przetwarzania.

Format CVPixelBuffer sprawdza się w tych trybach biegu:

  • Obrazy: aplikacje, które po przetworzeniu przy użyciu platformy CoreImage systemu iOS generują obrazy CVPixelBuffer, mogą być wysyłane do segmentacji obrazów w trybie uruchamiania obrazów.

  • Filmy: ramki wideo można przekształcić w format CVPixelBuffer do przetwarzania, a następnie wysłać do segmentacji obrazu w trybie wideo.

  • transmisja na żywo: aplikacje używające aparatu w iOS do generowania klatek mogą być konwertowane do formatu CVPixelBuffer w celu przetworzenia, zanim trafią do segmentatora obrazu 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 CVPixelBuffer znajdziesz w dokumentacji dla deweloperów Apple dotyczącej CVPixelBuffer.

CMSampleBuffer

Format CMSampleBuffer przechowuje próbki multimediów o jednolitym typie i jest odpowiedni do uruchamiania 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ć narzędzie do podziału obrazu, użyj metody segment() odpowiedniej dla przypisanego trybu działania:

  • Statyczny obraz: segment(image:)
  • Film: segment(videoFrame:timestampInMilliseconds:)
  • Transmisja na żywo: segmentAsync(image:timestampInMilliseconds:)

Poniższe przykłady kodu pokazują proste przykłady uruchamiania narzędzia Image Segmenter w różnych trybach:

Swift

Obraz

let result = try imageSegmenter.segment(image: image)
    

Wideo

let result = try imageSegmenter.segment(
  videoFrame: image,
  timestampInMilliseconds: timestamp)
    

Transmisja na żywo

try imageSegmenter.segmentAsync(
  image: image,
  timestampInMilliseconds: timestamp)
    

Objective-C

Obraz

MPPImageSegmenterResult *result =
  [imageSegmenter segmentImage:image error:nil];
    

Wideo

MPPImageSegmenterResult *result =
  [imageSegmenter segmentVideoFrame:image
            timestampInMilliseconds:timestamp
                              error:nil];
    

Transmisja na żywo

BOOL success =
  [imageSegmenter segmentAsyncImage:image
            timestampInMilliseconds:timestamp
                              error:nil];
    

Przykładowy kod segmentacji obrazów szczegółowo pokazuje implementacje każdego z tych trybów: segment(image:), segment(videoFrame:timestampInMilliseconds:) i segmentAsync(image:timestampInMilliseconds:).

Pamiętaj:

  • Podczas uruchamiania w trybie wideo lub transmisji na żywo musisz też podać zadaniu segmentacji obrazu sygnaturę czasową ramki wejściowej.

  • W trybie obrazu lub filmu zadanie segmentacji obrazu blokuje bieżący wątek, dopóki nie zakończy przetwarzania wejściowego obrazu lub klatki. Aby uniknąć zablokowania bieżącego wątku, wykonaj przetwarzanie w wątku w tle za pomocą struktur systemu iOS Dispatch lub NSOperation.

  • W trybie transmisji na żywo zadanie segmentacji obrazu zwraca wynik natychmiast i nie blokuje bieżącego wątku. Po przetworzeniu każdego wejściowego kadru wywołuje metodę imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:) w segmencie obrazu. Narzędzie do dzielenia obrazu wywołuje tę metodę asynchronicznie na dedykowanej kolejce wysyłania sekwencyjnego. Aby wyświetlić wyniki w interfejsie, prześlij je do kolejki głównej po przetworzeniu. Jeśli funkcja segmentAsync zostanie wywołana, gdy zadanie segmentacji obrazów będzie przetwarzać inną klatkę, segmenter obrazów zignoruje nową ramkę wejściową.

Obsługa i wyświetlanie wyników

Po przeprowadzeniu wnioskowania zadanie segmentacji obrazów zwraca obiekt ImageSegmenterResult, który zawiera wyniki zadania segmentacji. Zawartość danych wyjściowych zależy od typu danych wyjściowych ustawionych podczas konfigurowania zadania.

Poniższe obrazy przedstawiają wizualizację danych wyjściowych zadania dla maski wartości kategorii. Zakres maski kategorii to [0, 255], a każda wartość piksela reprezentuje zwycięski indeks kategorii w wyniku modelu. Zwycięski indeks kategorii ma najwyższy wynik spośród kategorii rozpoznawanych przez model.

Wyjście oryginalnego obrazu i maski kategorii. Obraz źródłowy ze zbioru danych Pascal VOC 2012.

Przykładowy kod segmentacji obrazów pokazuje, jak wyświetlić wyniki narzędzia do segmentowania obrazów. Szczegóły znajdziesz w przykładowym kodzie.