Wprowadzenie
Witaj świecie! samouczek korzysta z MediaPipe Framework do programowania na iOS aplikacja uruchamiająca graf MediaPipe w systemie iOS.
Co utworzysz
Prosta aplikacja do wykrywania krawędzi Sobela w czasie rzeczywistym do wideo na żywo na urządzeniu z iOS.
Konfiguracja
- Zainstaluj w systemie MediaPipe Framework – patrz Instalacja Framework. .
- Skonfiguruj urządzenie z iOS na potrzeby programowania.
- Skonfiguruj w systemie Bazel, aby skompilować i wdrożyć aplikację na iOS.
Wykres wykrywania krawędzi
Użyjemy tego wykresu: edge_detection_mobile_gpu.pbtxt
:
# MediaPipe graph that performs GPU Sobel edge detection on a live video stream.
# Used in the examples
# mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic:helloworld
# and mediapipe/examples/ios/helloworld.
# Images coming into and out of the graph.
input_stream: "input_video"
output_stream: "output_video"
# Converts RGB images into luminance images, still stored in RGB format.
node: {
calculator: "LuminanceCalculator"
input_stream: "input_video"
output_stream: "luma_video"
}
# Applies the Sobel filter to luminance images stored in RGB format.
node: {
calculator: "SobelEdgesCalculator"
input_stream: "luma_video"
output_stream: "output_video"
}
Wizualizacja wykresu znajduje się poniżej:
Ten wykres ma jeden strumień wejściowy o nazwie input_video
dla wszystkich przychodzących klatek
udostępniane przez aparat urządzenia.
Pierwszy węzeł na wykresie, LuminanceCalculator
, przyjmuje pojedynczy pakiet (obraz
) i stosuje zmianę luminancji przy użyciu cieniowania OpenGL. W wyniku
ramka obrazu jest wysyłana do strumienia wyjściowego luma_video
.
Drugi węzeł (SobelEdgesCalculator
) stosuje wykrywanie brzegowych do przychodzących
pakiety w strumieniu luma_video
i wyprowadzają w wyniku output_video
.
Nasza aplikacja na iOS wyświetli wyjściowe ramki obrazu output_video
.
Wstępna minimalna konfiguracja aplikacji
Zaczynamy od prostej aplikacji na iOS i pokazujemy, jak używać bazel
aby go stworzyć.
Najpierw utwórz projekt XCode. W tym celu wybierz Plik > Nowe > Aplikacja widoku pojedynczego.
Ustaw nazwę usługi na „HelloWorld” i użyj odpowiedniej organizacji
identyfikatora, np. com.google.mediapipe
. Identyfikator organizacji
wraz z nazwą produktu będą właściwościami bundle_id
dla aplikacji, np.
com.google.mediapipe.HelloWorld
Ustaw język na Objective-C.
Zapisz projekt w odpowiedniej lokalizacji. Nazwijmy to
$PROJECT_TEMPLATE_LOC
Twój projekt będzie więc
Katalog $PROJECT_TEMPLATE_LOC/HelloWorld
. Ten katalog będzie zawierać
inny katalog o nazwie HelloWorld
i plik HelloWorld.xcodeproj
.
Obiekt HelloWorld.xcodeproj
nie będzie przydatny w tym samouczku, ponieważ użyjemy
aby stworzyć aplikację na iOS. Zawartość
Katalog $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
jest wymieniony poniżej:
AppDelegate.h
iAppDelegate.m
ViewController.h
iViewController.m
main.m
Info.plist
Main.storyboard
iLaunch.storyboard
- Katalog
Assets.xcassets
.
Skopiuj te pliki do katalogu o nazwie HelloWorld
i do lokalizacji, do której masz dostęp
za pomocą kodu źródłowego platformy MediaPipe Framework. Na przykład kod źródłowy funkcji
aplikacja, w której skompilujemy ten samouczek, znajduje się
mediapipe/examples/ios/HelloWorld
Będziemy ją nazywać
$APPLICATION_PATH
w ramach ćwiczeń z programowania.
Utwórz plik BUILD
w $APPLICATION_PATH
i dodaj tę kompilację
reguły:
MIN_IOS_VERSION = "11.0"
load(
"@build_bazel_rules_apple//apple:ios.bzl",
"ios_application",
)
ios_application(
name = "HelloWorldApp",
bundle_id = "com.google.mediapipe.HelloWorld",
families = [
"iphone",
"ipad",
],
infoplists = ["Info.plist"],
minimum_os_version = MIN_IOS_VERSION,
provisioning_profile = "//mediapipe/examples/ios:developer_provisioning_profile",
deps = [":HelloWorldAppLibrary"],
)
objc_library(
name = "HelloWorldAppLibrary",
srcs = [
"AppDelegate.m",
"ViewController.m",
"main.m",
],
hdrs = [
"AppDelegate.h",
"ViewController.h",
],
data = [
"Base.lproj/LaunchScreen.storyboard",
"Base.lproj/Main.storyboard",
],
sdk_frameworks = [
"UIKit",
],
deps = [],
)
Reguła objc_library
dodaje zależności: AppDelegate
oraz
Zajęcia: ViewController
, main.m
i scenorysy aplikacji.
aplikacja oparta na szablonie korzysta tylko z pakietu SDK UIKit
.
Reguła ios_application
używa biblioteki Objective-C HelloWorldAppLibrary
wygenerowane w celu utworzenia aplikacji na iOS, którą można zainstalować na urządzeniu z iOS.
Aby utworzyć aplikację, użyj w terminalu tego polecenia:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Aby na przykład skompilować aplikację HelloWorldApp
w
mediapipe/examples/ios/helloworld
, użyj tego polecenia:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Następnie wróć do XCode, otwórz Window > Urządzenia i symulatory, wybierz
i dodaj na nim plik .ipa
wygenerowany za pomocą powyższego polecenia.
Poniżej znajduje się dokument o konfigurowaniu i kompilowaniu aplikacji iOS Framework.
Otwórz aplikację na urządzeniu. Jest puste, dlatego powinien wyświetlić się pusty, biały ekran.
Użyj aparatu do wyświetlania podglądu na żywo
W tym samouczku użyjemy klasy MPPCameraInputSource
, aby uzyskać dostęp do
rejestruje klatki z aparatu. Ta klasa używa interfejsu API AVCaptureSession
, aby pobrać
dzięki klatkom z kamery.
Zanim jednak użyjesz tej klasy, zmień plik Info.plist
, aby obsługiwał aparat
z użytkowania w aplikacji.
W ViewController.m
dodaj ten wiersz importu:
#import "mediapipe/objc/MPPCameraInputSource.h"
Dodaj ten fragment do bloku implementacji, aby utworzyć obiekt
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Dodaj do pliku viewDidLoad()
ten kod:
-(void)viewDidLoad {
[super viewDidLoad];
_cameraSource = [[MPPCameraInputSource alloc] init];
_cameraSource.sessionPreset = AVCaptureSessionPresetHigh;
_cameraSource.cameraPosition = AVCaptureDevicePositionBack;
// The frame's native format is rotated with respect to the portrait orientation.
_cameraSource.orientation = AVCaptureVideoOrientationPortrait;
}
Kod inicjuje zdarzenie _cameraSource
, ustawia gotowe ustawienie sesji przechwytywania i określa,
z aparatu.
Musimy pobrać ramki z pliku _cameraSource
do aplikacji
ViewController
, aby je wyświetlić. MPPCameraInputSource
jest podklasą
MPPInputSource
, która udostępnia swoim przedstawicielom protokół,
MPPInputSourceDelegate
Nasza aplikacja ViewController
może być
delegatem
z _cameraSource
.
Zaktualizuj odpowiednio definicję interfejsu elementu ViewController
:
@interface ViewController () <MPPInputSourceDelegate>
Aby skonfigurować kamerę i przetwarzać przychodzące klatki, powinniśmy użyć kolejki
niż główna kolejka. Dodaj poniższy kod do bloku implementacji
ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
W viewDidLoad()
dodaj ten wiersz po zainicjowaniu
Obiekt _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Dodaj ten kod, aby zainicjować kolejkę przed skonfigurowaniem
_cameraSource
obiekt:
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(
DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, /*relative_priority=*/0);
_videoQueue = dispatch_queue_create(kVideoQueueLabel, qosAttribute);
Użyjemy kolejki szeregowej o priorytecie QOS_CLASS_USER_INTERACTIVE
dla
przetwarzania klatek kamery.
Dodaj poniższy wiersz po zaimportowaniu nagłówka na górze pliku, przed
interfejs/implementacja ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Przed wdrożeniem metody z protokołu MPPInputSourceDelegate
musimy
skonfiguruj sposób wyświetlania ramek kamery. Platforma Mediapipe Framework
inne narzędzie o nazwie MPPLayerRenderer
. Ten
można użyć do wyświetlania obiektów CVPixelBufferRef
, które są typem
obrazy udostępnione przedstawicielom przez firmę MPPCameraInputSource
.
W ViewController.m
dodaj ten wiersz importu:
#import "mediapipe/objc/MPPLayerRenderer.h"
Aby wyświetlić obrazy ekranu, musimy dodać nowy obiekt UIView
o nazwie
_liveView
do: ViewController
.
Dodaj te wiersze do bloku implementacji ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Otwórz Main.storyboard
i dodaj obiekt UIView
z biblioteki obiektów do
View
z ViewController
klasy. Dodaj punkt odniesienia z tego widoku do
obiekt _liveView
dodany właśnie do klasy ViewController
. Zmień rozmiar
w taki sposób, aby był wyśrodkowany i zakrywał cały ekran aplikacji.
Wróć do strony ViewController.m
i dodaj ten kod do witryny viewDidLoad()
w
zainicjuj obiekt _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Aby pobierać klatki z aparatu, wdrożymy tę metodę:
// Must be invoked on _videoQueue.
- (void)processVideoFrame:(CVPixelBufferRef)imageBuffer
timestamp:(CMTime)timestamp
fromSource:(MPPInputSource*)source {
if (source != _cameraSource) {
NSLog(@"Unknown source: %@", source);
return;
}
// Display the captured image on the screen.
CFRetain(imageBuffer);
dispatch_async(dispatch_get_main_queue(), ^{
[_renderer renderPixelBuffer:imageBuffer];
CFRelease(imageBuffer);
});
}
Jest to metoda przekazywania dostępu do funkcji MPPInputSource
. Najpierw sprawdzamy, czy
pobieranie klatek z odpowiedniego źródła, np. z urządzenia _cameraSource
. Następnie wyświetlamy
klatkę odebraną z kamery za pomocą _renderer
w głównej kolejce.
Teraz musimy uruchomić aparat, gdy tylko na ekranie pojawią się klatki,
pojawią się wkrótce. W tym celu wdrożymy
Funkcja viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Zanim uruchomimy kamerę, potrzebujemy zgody użytkownika.
MPPCameraInputSource
udostępnia funkcję
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
, aby poprosić o dostęp do kamery i wykonać czynności, jeśli użytkownik:
napisał(a). Dodaj do viewWillAppear:animated
ten kod:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Zanim utworzysz aplikację, dodaj do BUILD
te zależności
plik:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Teraz skompiluj i uruchom aplikację na urządzeniu z iOS. Powinna pojawić się transmisja na żywo obraz obrazu z kamery po zaakceptowaniu uprawnień do korzystania z aparatu.
Teraz na wykresie MediaPipe możemy używać ramek aparatu.
Korzystanie z grafu MediaPipe w systemie iOS
Dodaj odpowiednie zależności
Dodaliśmy już zależności kodu platformy MediaPipe, który zawiera
interfejsu API iOS, aby używać grafu MediaPipe. Aby użyć wykresu MediaPipe, musimy dodać
zależności od wykresu, którego chcemy użyć w aplikacji. Dodaj następujące elementy
do listy data
w pliku BUILD
:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Dodaj zależność do kalkulatorów użytych na tym wykresie w polu deps
w pliku BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Na koniec zmień nazwę pliku ViewController.m
na ViewController.mm
, aby
Objective-C++.
Użyj wykresu w: ViewController
W ViewController.m
dodaj ten wiersz importu:
#import "mediapipe/objc/MPPGraph.h"
Zadeklaruj stałą statyczną w nazwie grafu, strumienia wejściowego oraz strumień wyjściowy:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Dodaj do interfejsu ViewController
tę właściwość:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Jak wyjaśniliśmy w komentarzu powyżej, zainicjujemy ten wykres w
Najpierw viewDidLoad
. W tym celu musimy wczytać wykres z pliku .pbtxt
przy użyciu tej funkcji:
+ (MPPGraph*)loadGraphFromResource:(NSString*)resource {
// Load the graph config resource.
NSError* configLoadError = nil;
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
if (!resource || resource.length == 0) {
return nil;
}
NSURL* graphURL = [bundle URLForResource:resource withExtension:@"binarypb"];
NSData* data = [NSData dataWithContentsOfURL:graphURL options:0 error:&configLoadError];
if (!data) {
NSLog(@"Failed to load MediaPipe graph config: %@", configLoadError);
return nil;
}
// Parse the graph config resource into mediapipe::CalculatorGraphConfig proto object.
mediapipe::CalculatorGraphConfig config;
config.ParseFromArray(data.bytes, data.length);
// Create MediaPipe graph with mediapipe::CalculatorGraphConfig proto object.
MPPGraph* newGraph = [[MPPGraph alloc] initWithGraphConfig:config];
[newGraph addFrameOutputStream:kOutputStream outputPacketType:MPPPacketTypePixelBuffer];
return newGraph;
}
Użyj tej funkcji, aby zainicjować wykres w viewDidLoad
w następujący sposób:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Wykres powinien przesłać wyniki przetwarzania klatek aparatu z powrotem do funkcji
ViewController
Po zainicjowaniu wykresu dodaj następujący wiersz, aby ustawić
ViewController
jako delegat obiektu mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Aby uniknąć rywalizacji o pamięć podczas przetwarzania klatek z transmisji wideo na żywo, dodaj ten wiersz:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Teraz zacznij tworzyć wykres, gdy użytkownik zezwoli na używanie aparatu w naszej aplikacji:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
// Start running self.mediapipeGraph.
NSError* error;
if (![self.mediapipeGraph startWithError:&error]) {
NSLog(@"Failed to start graph: %@", error);
}
else if (![self.mediapipeGraph waitUntilIdleWithError:&error]) {
NSLog(@"Failed to complete graph initial run: %@", error);
}
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Wcześniej, gdy otrzymaliśmy klatki z kamery w processVideoFrame
, wyświetliliśmy je w _liveView
za pomocą _renderer
. Teraz
przesyłać te ramki do wykresu,
a potem renderować wyniki. Zmień
implementacji tej funkcji w taki sposób:
- (void)processVideoFrame:(CVPixelBufferRef)imageBuffer
timestamp:(CMTime)timestamp
fromSource:(MPPInputSource*)source {
if (source != _cameraSource) {
NSLog(@"Unknown source: %@", source);
return;
}
[self.mediapipeGraph sendPixelBuffer:imageBuffer
intoStream:kInputStream
packetType:MPPPacketTypePixelBuffer];
}
Wysyłamy pakiet imageBuffer
do self.mediapipeGraph
jako pakiet typu
MPPPacketTypePixelBuffer
do strumienia wejściowego kInputStream
, tj.
„input_video”.
Wykres będzie działał z tym pakietem wejściowym i wyświetli wynik w
kOutputStream
, czyli „output_video”. Możemy wyznaczyć przedstawiciela:
do odbierania pakietów w tym strumieniu wyjściowym i wyświetlania ich na ekranie:
- (void)mediapipeGraph:(MPPGraph*)graph
didOutputPixelBuffer:(CVPixelBufferRef)pixelBuffer
fromStream:(const std::string&)streamName {
if (streamName == kOutputStream) {
// Display the captured image on the screen.
CVPixelBufferRetain(pixelBuffer);
dispatch_async(dispatch_get_main_queue(), ^{
[_renderer renderPixelBuffer:pixelBuffer];
CVPixelBufferRelease(pixelBuffer);
});
}
}
Zaktualizuj definicję interfejsu ViewController
za pomocą MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
To wszystko. Utwórz i uruchom aplikację na urządzeniu z iOS. Powinna pojawić się ikona wyniki zastosowania wykresu wykrywania krawędzi w kanale wideo na żywo. Gratulacje!
Pamiętaj, że w przykładach na iOS jest teraz używana popularna aplikacja do tworzenia szablonów. Kod w argumencie
Ten samouczek jest używany w aplikacji do tworzenia standardowych szablonów. Aplikacja helloworld ma:
odpowiednie zależności pliku BUILD
na potrzeby wykresu wykrywania krawędzi.