Hello World! für iOS

Einführung

Dies ist Hello World! Tutorial verwendet MediaPipe Framework zur Entwicklung eines iOS- die ein MediaPipe-Diagramm unter iOS ausführt.

Umfang

Eine einfache Kamera-App für die Sobel-Kantenerkennung in Echtzeit, die auf ein Live-Video angewendet wird auf einem iOS-Gerät streamen.

edge_detection_ios_gpu_gif

Einrichtung

  1. MediaPipe Framework auf Ihrem System installieren (siehe Framework-Installation) .
  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 die Kantenerkennung

Wir verwenden die folgende Grafik, 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"
}

Im Folgenden sehen Sie eine Visualisierung des Diagramms:

edge_detection_mobile_gpu

Dieses Diagramm enthält einen einzelnen Eingabestream namens input_video für alle eingehenden Frames die von der Kamera Ihres Geräts bereitgestellt wird.

Der erste Knoten im Diagramm, LuminanceCalculator, verwendet ein einzelnes Paket (Bild Frame) und wendet eine Änderung der Leuchtdichte mithilfe eines OpenGL-Shaders an. Das Ergebnis Bildframe an den luma_video-Ausgabestream gesendet.

Der zweite Knoten, SobelEdgesCalculator, wendet die Edge-Erkennung auf eingehende Anfragen an Pakete im luma_video-Stream und Ausgaben führen zu einer output_video-Ausgabe .

In der iOS-App werden die Ausgabe-Bildframes von output_video angezeigt. .

Minimale anfängliche Einrichtung der Anwendung

Wir beginnen mit einer einfachen iOS-App und zeigen, wie bazel verwendet wird um sie zu erstellen.

Erstellen Sie zuerst ein XCode-Projekt über „File“ > Neu > Einzelansicht-App.

Legen Sie als Produktnamen „HelloWorld“ fest und verwenden Sie eine geeignete Organisation. Kennung wie com.google.mediapipe. Die Organisations-ID zusammen mit dem Produktnamen die bundle_id für die Anwendung, z. B.: com.google.mediapipe.HelloWorld.

Setzen Sie die Sprache auf Objective-C.

Speichern Sie das Projekt an einem geeigneten Speicherort. Nennen wir sie $PROJECT_TEMPLATE_LOC Ihr Projekt befindet sich $PROJECT_TEMPLATE_LOC/HelloWorld-Verzeichnis. Dieses Verzeichnis enthält ein weiteres Verzeichnis namens HelloWorld und eine HelloWorld.xcodeproj-Datei.

Das HelloWorld.xcodeproj ist für diese Anleitung nicht hilfreich, da wir bazel, um die iOS-App zu erstellen. Der Inhalt der Das Verzeichnis $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 das Verzeichnis „HelloWorld“ an einen Speicherort mit Zugriffsberechtigung. des MediaPipe Framework-Quellcodes. Der Quellcode der die wir in diesem Tutorial erstellen werden, mediapipe/examples/ios/HelloWorld Wir bezeichnen diesen Pfad als $APPLICATION_PATH im gesamten Codelab.

Erstellen Sie eine BUILD-Datei in $APPLICATION_PATH und fügen Sie den folgenden Build hinzu: Regeln:

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 AppDelegate und ViewController-Klassen, main.m und die Anwendungs-Storyboards. Die Vorlagen-App hängt nur vom UIKit SDK ab.

Die Regel ios_application verwendet die Objective-C-Bibliothek HelloWorldAppLibrary wird generiert, um eine iOS-App zur Installation auf Ihrem iOS-Gerät zu erstellen.

Führen Sie den folgenden Befehl in einem Terminal aus, um die App zu erstellen:

bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'

Um die Anwendung HelloWorldApp beispielsweise in mediapipe/examples/ios/helloworld, verwenden Sie den folgenden Befehl:

bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp

Gehen Sie dann zurück zu XCode, öffnen Sie Window > „Geräte und Simulatoren“. Wählen Sie Ihre Gerät und fügen Sie Ihrem Gerät die mit dem obigen Befehl generierte Datei .ipa hinzu. Hier finden Sie das Dokument zum Einrichten und Kompilieren von iOS-Framework-Apps.

Öffnen Sie die App auf Ihrem Gerät. Da das Feld leer ist, sollte ein leerer weißer Bildschirm.

Kamera für Livestream-Feed verwenden

In dieser Anleitung verwenden wir die Klasse MPPCameraInputSource, um auf Bilder von der Kamera nehmen. Diese Klasse verwendet die AVCaptureSession API, um die Bilder von der Kamera.

Bevor Sie diese Klasse verwenden, ändern Sie die Datei Info.plist so, dass sie Kamera unterstützt Nutzung in der App.

Fügen Sie in ViewController.m die folgende Importzeile hinzu:

#import "mediapipe/objc/MPPCameraInputSource.h"

Fügen Sie dem Implementierungsblock Folgendes hinzu, um ein Objekt zu erstellen _cameraSource:

@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 Aufnahmesitzung wird festgelegt und die Einstellung, Kamera verwenden.

Die Frames aus dem _cameraSource müssen in unsere App übertragen werden. ViewController, um sie anzuzeigen. MPPCameraInputSource ist eine abgeleitete Klasse von MPPInputSource, das ein Protokoll für seine Bevollmächtigten bereitstellt, nämlich die MPPInputSourceDelegate Unsere Anwendung ViewController kann also ein Bevollmächtigter von _cameraSource.

Aktualisieren Sie die Schnittstellendefinition von ViewController entsprechend:

@interface ViewController () <MPPInputSourceDelegate>

Für die Kameraeinrichtung und die Verarbeitung eingehender Bilder sollte eine Warteschlange verwendet werden. die sich von der Hauptwarteschlange unterscheiden. Fügen Sie Folgendes in den Implementierungsblock von ViewController:

// Process camera frames on this queue.
dispatch_queue_t _videoQueue;

Fügen Sie in viewDidLoad() die folgende Zeile hinzu, nachdem Sie das _cameraSource-Objekt:

[_cameraSource setDelegate:self queue:_videoQueue];

Fügen Sie den folgenden Code hinzu, um die Warteschlange zu initialisieren, bevor Sie die Objekt _cameraSource:

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 für Bilderrahmen zu verarbeiten.

Fügen Sie nach den Header-Importen am Anfang der Datei die folgende Zeile hinzu, bevor die 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 eine Möglichkeit zur Darstellung der Kamerabilder eingerichtet. Das Mediapipe-Framework bietet ein weiteres Dienstprogramm namens MPPLayerRenderer, mit dem Bilder auf dem Bildschirm angezeigt werden. Dieses können Sie CVPixelBufferRef-Objekte anzeigen lassen. Dabei handelt es sich um den Typ die Bilder, die von MPPCameraInputSource den Bevollmächtigten zur Verfügung gestellt werden.

Fügen Sie in ViewController.m die folgende Importzeile hinzu:

#import "mediapipe/objc/MPPLayerRenderer.h"

Zum Anzeigen von Bildschirmbildern müssen wir ein neues UIView-Objekt namens _liveView zu ViewController.

Fügen Sie dem Implementierungsblock von ViewController die folgenden Zeilen hinzu:

// 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 Klasse ViewController. Fügen Sie in dieser Ansicht ein verweisendes Outlet zum Das _liveView-Objekt, das Sie der Klasse ViewController gerade hinzugefügt haben. Ändern Sie die Größe des sodass sie zentriert wird und den gesamten App-Bildschirm einnimmt.

Kehren Sie zu ViewController.m zurück und fügen Sie den folgenden Code in viewDidLoad() ein: Initialisieren Sie das Objekt _renderer:

_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 Delegatmethode von MPPInputSource. Zunächst überprüfen wir, ob wir Frames aus der richtigen Quelle beziehen, d.h. dem _cameraSource. Dann zeigen wir den Frame, den die Kamera über _renderer in der Hauptwarteschlange empfangen hat.

Jetzt müssen wir die Kamera starten, sobald sich die Ansicht zur Anzeige der Frames angezeigt werden. Hierzu implementieren wir die viewWillAppear:(BOOL)animated-Funktion:

-(void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
}

Bevor wir die Kamera ausführen können, benötigen wir die Erlaubnis des Nutzers, um darauf zuzugreifen. MPPCameraInputSource stellt eine Funktion bereit. requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL granted))handler, um den Zugriff auf die Kamera anzufordern und etwas zu tun, wenn der Nutzer haben geantwortet. Fügen Sie viewWillAppear:animated den folgenden Code hinzu:

[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
  if (granted) {
    dispatch_async(_videoQueue, ^{
      [_cameraSource start];
    });
  }
}];

Bevor Sie die Anwendung erstellen, fügen Sie die folgenden Abhängigkeiten zu BUILD hinzu. Datei:

sdk_frameworks = [
    "AVFoundation",
    "CoreGraphics",
    "CoreMedia",
],
deps = [
    "//mediapipe/objc:mediapipe_framework_ios",
    "//mediapipe/objc:mediapipe_input_sources_ios",
    "//mediapipe/objc:mediapipe_layer_renderer",
],

Erstellen Sie nun die Anwendung und führen Sie sie auf Ihrem iOS-Gerät aus. Es sollte eine Live-Anzeige Kamera-Ansicht-Feed nach dem Akzeptieren der Kameraberechtigungen.

Jetzt können Kameraframes in einem MediaPipe-Diagramm verwendet werden.

MediaPipe-Diagramm unter iOS verwenden

Relevante Abhängigkeiten hinzufügen

Wir haben bereits die Abhängigkeiten des MediaPipe-Framework-Codes hinzugefügt, der um ein MediaPipe-Diagramm zu verwenden. Um ein MediaPipe-Diagramm zu verwenden, müssen wir Abhängigkeit von der Grafik, die wir in unserer Anwendung verwenden wollen. Folgendes hinzufügen Zeile zur data-Liste in der Datei BUILD hinzu:

"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",

Fügen Sie nun die Abhängigkeit zu den in diesem Diagramm verwendeten Rechnern im Feld deps hinzu. in der Datei BUILD:

"//mediapipe/graphs/edge_detection:mobile_calculators",

Benennen Sie abschließend die Datei ViewController.m in ViewController.mm um. Objective-C++.

Das 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:

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 diese Grafik Zuerst viewDidLoad. Dazu müssen wir das Diagramm aus der Datei .pbtxt laden. mithilfe der folgenden Funktion:

+   (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;
}

Initialisieren Sie mit dieser Funktion die Grafik in viewDidLoad so:

self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];

Die Grafik sollte die Ergebnisse der Verarbeitung von Kamerabildern an die ViewController Fügen Sie nach der Initialisierung des Diagramms die folgende Zeile hinzu, um den ViewController als Bevollmächtigter des mediapipeGraph-Objekts:

self.mediapipeGraph.delegate = self;

Um Speicherkonflikte bei der Verarbeitung von Frames aus dem Live-Videofeed zu vermeiden, fügen Sie folgende Zeile:

// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;

Starte nun das Diagramm, wenn der Benutzer die Berechtigung zur Verwendung der Kamera erteilt hat. in unserer App:

[_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 zuvor Frames von der Kamera im processVideoFrame wurden sie mithilfe der _renderer-Funktion im _liveView angezeigt. Jetzt müssen diese Frames an die Grafik senden und stattdessen die Ergebnisse rendern. Ändern die Implementierung dieser Funktion, um Folgendes zu erreichen:

-   (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 die imageBuffer als Paket vom Typ an self.mediapipeGraph MPPPacketTypePixelBuffer in den Eingabestream kInputStream, d.h. „input_video“.

Die Grafik wird mit diesem Eingabepaket ausgeführt und gibt ein Ergebnis in kOutputStream, z.B. „output_video“. Wir können den folgenden Bevollmächtigten -Methode, 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 und führen Sie sie auf Ihrem iOS-Gerät aus. Das Feld Ergebnisse der Ausführung des Kantenerkennungsdiagramms in einem Live-Videofeed. Glückwunsch!

edge_detection_ios_gpu_gif

Für die iOS-Beispiele wird jetzt eine gängige Vorlagen-App verwendet. Der Code in Diese Anleitung wird in der allgemeinen Vorlagen-App verwendet. Die App helloworld enthält die geeignete BUILD-Dateiabhängigkeiten für den Edge-Erkennungsdiagramm.