Einleitung
In dieser Hello World!-Anleitung wird das MediaPipe-Framework verwendet, um eine iOS-Anwendung zu entwickeln, die ein MediaPipe-Diagramm unter iOS ausführt.
Umfang
Eine einfache Kamera-App zur Echtzeiterkennung von Sobel Edge, die auf einen Live-Videostream auf einem iOS-Gerät angewendet wird.
Einrichtung
- Installieren Sie das MediaPipe-Framework auf Ihrem System. Weitere Informationen finden Sie in der Framework-Installationsanleitung.
- Richten Sie Ihr iOS-Gerät für die Entwicklung ein.
- Richten Sie Bazel auf Ihrem System ein, um die iOS-App zu erstellen und bereitzustellen.
Grafik für Randerkennung
Wir verwenden das folgende Diagramm 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"
}
Hier eine Visualisierung der Grafik:
Dieses Diagramm enthält einen einzelnen Eingabestream mit dem Namen input_video
für alle eingehenden Frames, die von der Kamera Ihres Geräts bereitgestellt werden.
Der erste Knoten im Diagramm, LuminanceCalculator
, verwendet ein einzelnes Paket (Bildframe) und wendet eine Änderung der Helligkeit mithilfe eines OpenGL-Shaders an. Der resultierende Bildframe wird an den luma_video
-Ausgabestream gesendet.
Der zweite Knoten, SobelEdgesCalculator
, wendet die Edge-Erkennung auf eingehende Pakete im Stream luma_video
an und gibt den Ausgabestream output_video
aus.
Unsere iOS-Anwendung zeigt die Ausgabe-Bildframes des output_video
-Streams an.
Anfängliche minimale Anwendungseinrichtung
Wir beginnen mit einer einfachen iOS-Anwendung und zeigen, wie sie mit bazel
erstellt wird.
Erstellen Sie zuerst über „File“ > „New“ > „Single View App“ ein XCode-Projekt.
Legen Sie den Produktnamen auf „HelloWorld“ fest und verwenden Sie eine geeignete Organisations-ID wie com.google.mediapipe
. Die Organisationskennung ist zusammen mit dem Produktnamen der bundle_id
für die Anwendung, z. B. com.google.mediapipe.HelloWorld
.
Legen Sie als Sprache Objective-C fest.
Speichern Sie das Projekt an einem geeigneten Speicherort. Nennen wir dies $PROJECT_TEMPLATE_LOC
. Ihr Projekt befindet sich also im Verzeichnis $PROJECT_TEMPLATE_LOC/HelloWorld
. Dieses Verzeichnis enthält ein weiteres Verzeichnis mit dem Namen HelloWorld
und eine HelloWorld.xcodeproj
-Datei.
Der HelloWorld.xcodeproj
ist für diese Anleitung nicht hilfreich, da wir zum Erstellen der iOS-Anwendung zum Erstellen der iOS-Anwendung zum Erstellen der iOS-Anwendung verwenden. Der Inhalt des Verzeichnisses $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
ist unten aufgeführt:
AppDelegate.h
undAppDelegate.m
ViewController.h
undViewController.m
main.m
Info.plist
Main.storyboard
undLaunch.storyboard
Assets.xcassets
befinden.
Kopieren Sie diese Dateien in ein Verzeichnis mit dem Namen HelloWorld
an einen Speicherort, der auf den Quellcode des MediaPipe-Frameworks zugreifen kann. Der Quellcode der Anwendung, die wir in dieser Anleitung erstellen, befindet sich beispielsweise in mediapipe/examples/ios/HelloWorld
. Wir bezeichnen diesen Pfad im gesamten Codelab als $APPLICATION_PATH
.
Erstellen Sie eine BUILD
-Datei in der Datei $APPLICATION_PATH
und fügen Sie die folgenden Build-Regeln hinzu:
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 = [],
)
Die Regel objc_library
fügt Abhängigkeiten für die Klassen AppDelegate
und ViewController
, main.m
und die Anwendungs-Storyboards hinzu. Die Vorlagenanwendung hängt nur vom UIKit
SDK ab.
Die Regel ios_application
verwendet die Objective-C-Bibliothek HelloWorldAppLibrary
, die generiert wird, um eine iOS-App für die Installation auf Ihrem iOS-Gerät zu erstellen.
Verwenden Sie den folgenden Befehl in einem Terminal, um die App zu erstellen:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Verwenden Sie beispielsweise den folgenden Befehl, um die Anwendung HelloWorldApp
in mediapipe/examples/ios/helloworld
zu erstellen:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Kehren Sie dann zu XCode zurück, öffnen Sie „Fenster“ > „Geräte und Simulatoren“, wählen Sie Ihr Gerät aus und fügen Sie die mit dem obigen Befehl generierte Datei .ipa
zu Ihrem Gerät hinzu.
Hier finden Sie das Dokument zum Einrichten und Kompilieren von iOS Framework-Apps.
Öffnen Sie die App auf Ihrem Gerät. Da sie leer ist, sollte ein leerer weißer Bildschirm angezeigt werden.
Kamera für den Livestream verwenden
In dieser Anleitung verwenden wir die Klasse MPPCameraInputSource
, um auf die Frames der Kamera zuzugreifen und diese zu erfassen. Diese Klasse verwendet die AVCaptureSession
API, um die Frames von der Kamera abzurufen.
Bevor Sie diese Klasse verwenden, müssen Sie jedoch die Datei Info.plist
ändern, damit die Kameranutzung in der App unterstützt wird.
Fügen Sie in ViewController.m
die folgende Importzeile hinzu:
#import "mediapipe/objc/MPPCameraInputSource.h"
Fügen Sie dem Implementierungsblock Folgendes hinzu, um ein _cameraSource
-Objekt zu erstellen:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Fügen Sie den folgenden Code zu viewDidLoad()
hinzu:
-(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;
}
Mit dem Code wird _cameraSource
initialisiert, die Voreinstellung für die Erfassungssitzung festgelegt und die zu verwendende Kamera festgelegt.
Wir müssen Frames aus dem _cameraSource
in die Anwendung ViewController
übertragen, damit sie angezeigt werden können. MPPCameraInputSource
ist eine abgeleitete Klasse von MPPInputSource
, die ein Protokoll für seine Bevollmächtigten bereitstellt, nämlich den MPPInputSourceDelegate
. Die Anwendung ViewController
kann also ein Delegat von _cameraSource
sein.
Aktualisieren Sie die Schnittstellendefinition von ViewController
entsprechend:
@interface ViewController () <MPPInputSourceDelegate>
Für die Einrichtung der Kamera und die Verarbeitung eingehender Frames sollten wir eine andere Warteschlange als die Hauptwarteschlange verwenden. Fügen Sie Folgendes in den Implementierungsblock von ViewController
ein:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
Fügen Sie in viewDidLoad()
die folgende Zeile ein, nachdem Sie das _cameraSource
-Objekt initialisiert haben:
[_cameraSource setDelegate:self queue:_videoQueue];
Fügen Sie außerdem den folgenden Code ein, um die Warteschlange zu initialisieren, bevor das _cameraSource
-Objekt eingerichtet wird:
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);
Wir verwenden eine serielle Warteschlange mit der Priorität QOS_CLASS_USER_INTERACTIVE
zur Verarbeitung von Kamerabildern.
Fügen Sie die folgende Zeile hinzu, nachdem der Header am Anfang der Datei importiert wurde, und zwar vor der Schnittstelle/Implementierung von ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Bevor wir eine Methode aus dem MPPInputSourceDelegate
-Protokoll implementieren, müssen wir zuerst eine Möglichkeit zum Anzeigen der Kamerabilder einrichten. Das Mediapipe-Framework stellt ein weiteres Dienstprogramm namens MPPLayerRenderer
bereit, mit dem Bilder auf dem Bildschirm angezeigt werden können. Dieses Dienstprogramm kann verwendet werden, um CVPixelBufferRef
-Objekte anzuzeigen. Dabei handelt es sich um den Bildtyp, den MPPCameraInputSource
den Bevollmächtigten zur Verfügung stellt.
Fügen Sie in ViewController.m
die folgende Importzeile hinzu:
#import "mediapipe/objc/MPPLayerRenderer.h"
Damit Bilder des Bildschirms angezeigt werden können, muss dem ViewController
ein neues UIView
-Objekt mit dem Namen _liveView
hinzugefügt werden.
Fügen Sie die folgenden Zeilen in den Implementierungsblock von ViewController
ein:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Rufen Sie Main.storyboard
auf und fügen Sie ein UIView
-Objekt aus der Objektbibliothek zum View
der ViewController
-Klasse hinzu. Fügen Sie dem _liveView
-Objekt, das Sie gerade der Klasse ViewController
hinzugefügt haben, ein verweisendes Outlet aus dieser Ansicht hinzu. Ändern Sie die Größe der Ansicht so, dass sie zentriert ist und den gesamten Anwendungsbildschirm abdeckt.
Kehren Sie zu ViewController.m
zurück und fügen Sie den folgenden Code in viewDidLoad()
ein, um das Objekt _renderer
zu initialisieren:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Um Frames von der Kamera abzurufen, implementieren wir die folgende Methode:
// 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);
});
}
Dies ist eine Delegate-Methode von MPPInputSource
. Zuerst prüfen wir, ob Frames aus der richtigen Quelle (_cameraSource
) abgerufen werden. Dann wird der Frame, den die Kamera über _renderer
erhalten hat, in der Hauptwarteschlange angezeigt.
Jetzt muss die Kamera gestartet werden, sobald die Ansicht zur Darstellung der Frames zu sehen ist. Dazu implementieren wir die Funktion viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Bevor die Kamera gestartet wird, benötigen wir die Zugriffsberechtigung des Nutzers.
MPPCameraInputSource
bietet die Funktion requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
, mit der der Zugriff auf die Kamera angefordert und bestimmte Aktionen ausgeführt werden können, wenn der Nutzer geantwortet hat. Fügen Sie viewWillAppear:animated
den folgenden Code hinzu:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Fügen Sie der Datei BUILD
die folgenden Abhängigkeiten hinzu, bevor Sie die Anwendung erstellen:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Erstellen Sie die Anwendung nun auf Ihrem iOS-Gerät und führen Sie sie aus. Nachdem Sie die Kameraberechtigungen akzeptiert haben, sollten Sie einen Livestream der Kamera sehen.
Jetzt können Kameraframes in einer MediaPipe-Grafik verwendet werden.
MediaPipe-Grafik unter iOS verwenden
Relevante Abhängigkeiten hinzufügen
Wir haben bereits die Abhängigkeiten des MediaPipe-Framework-Codes hinzugefügt, der die iOS API enthält, um eine MediaPipe-Grafik zu verwenden. Um eine MediaPipe-Grafik zu verwenden, müssen wir eine Abhängigkeit von der Grafik hinzufügen, die wir in unserer Anwendung verwenden möchten. Fügen Sie der Liste data
in der Datei BUILD
die folgende Zeile hinzu:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Fügen Sie nun die Abhängigkeit zu den Rechnern hinzu, die in diesem Diagramm im Feld deps
der Datei BUILD
verwendet werden:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Benennen Sie abschließend die Datei ViewController.m
in ViewController.mm
um, um Objective-C++ zu unterstützen.
Diagramm in ViewController
verwenden
Fügen Sie in ViewController.m
die folgende Importzeile hinzu:
#import "mediapipe/objc/MPPGraph.h"
Geben Sie eine statische Konstante mit dem Namen der Grafik, dem Eingabestream und dem Ausgabestream an:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Fügen Sie der Schnittstelle von ViewController
das folgende Attribut hinzu:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Wie im Kommentar oben erläutert, initialisieren wir dieses Diagramm zuerst in viewDidLoad
. Dazu müssen wir das Diagramm mit der folgenden Funktion aus der Datei .pbtxt
laden:
+ (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;
}
Verwenden Sie diese Funktion, um das Diagramm in viewDidLoad
so zu initialisieren:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Das Diagramm sollte die Ergebnisse der Verarbeitung von Kameraframes zurück an ViewController
senden. Fügen Sie nach dem Initialisieren des Diagramms die folgende Zeile hinzu, um ViewController
als Delegaten des mediapipeGraph
-Objekts festzulegen:
self.mediapipeGraph.delegate = self;
Fügen Sie die folgende Zeile hinzu, um Speicherkonflikte während der Verarbeitung von Frames aus dem Live-Videofeed zu vermeiden:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Starten Sie die Grafik jetzt, wenn der Nutzer die Berechtigung zur Verwendung der Kamera in unserer App erteilt hat:
[_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];
});
}
}];
Als wir Frames von der Kamera in der Funktion processVideoFrame
erhalten haben, haben wir sie zuvor mit _renderer
in _liveView
angezeigt. Jetzt müssen wir diese Frames an die Grafik senden und die Ergebnisse rendern. Ändern Sie die Implementierung dieser Funktion so:
- (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];
}
Wir senden imageBuffer
als Paket vom Typ MPPPacketTypePixelBuffer
in den Eingabestream kInputStream
an self.mediapipeGraph
, d.h. "input_video".
Die Grafik wird mit diesem Eingabepaket ausgeführt und gibt ein Ergebnis in kOutputStream
aus (d.h. „output_video“). Wir können die folgende Delegate-Methode implementieren, um Pakete in diesem Ausgabestream zu empfangen und auf dem Bildschirm anzuzeigen:
- (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);
});
}
}
Aktualisieren Sie die Schnittstellendefinition von ViewController
mit MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Das ist alles! Erstellen Sie die App auf Ihrem iOS-Gerät und führen Sie sie aus. Sie sollten die Ergebnisse der Erstellung des Edge-Erkennungsdiagramms in einem Live-Videofeed sehen. Glückwunsch!
Beachten Sie, dass in den iOS-Beispielen jetzt eine common Vorlagen-App verwendet wird. Der Code in dieser Anleitung wird in der common Vorlagen-App verwendet. Die helloworld-App verfügt über die entsprechenden BUILD
-Dateiabhängigkeiten für die Edge-Erkennungsgrafik.