Hello World! für iOS

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.

edge_detection_ios_gpu_gif

Einrichtung

  1. Installieren Sie das MediaPipe-Framework auf Ihrem System. Weitere Informationen finden Sie in der Framework-Installationsanleitung.
  2. Richten Sie Ihr iOS-Gerät für die Entwicklung ein.
  3. 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:

edge_detection_mobile_gpu

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:

  1. AppDelegate.h und AppDelegate.m
  2. ViewController.h und ViewController.m
  3. main.m
  4. Info.plist
  5. Main.storyboard und Launch.storyboard
  6. 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!

edge_detection_ios_gpu_gif

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.