Introduzione
Ciao mondo! il tutorial utilizza il framework MediaPipe per sviluppare una che esegue un grafico MediaPipe su iOS.
Cosa creerai
Una semplice app della fotocamera per il rilevamento dei bordi Sobel in tempo reale applicato a un video in diretta trasmettere in streaming su un dispositivo iOS.
Configurazione
- Installa il framework MediaPipe sul tuo sistema, vedi Installazione di framework. Google Cloud.
- Configura il tuo dispositivo iOS per lo sviluppo.
- Configura Bazel sul tuo sistema per creare e implementare l'app per iOS.
Grafico per il rilevamento dei bordi
Utilizzeremo il seguente grafico, 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"
}
Di seguito è riportata una visualizzazione del grafico:
Questo grafico ha un singolo flusso di input denominato input_video
per tutti i frame in entrata
fornito dalla fotocamera del dispositivo.
Il primo nodo del grafico, LuminanceCalculator
, accetta un singolo pacchetto (immagine
frame) e applica una variazione di luminanza utilizzando uno shaker OpenGL. Il risultato
frame immagine viene inviato allo stream di output luma_video
.
Il secondo nodo, SobelEdgesCalculator
, applica il rilevamento perimetrale alla rete
di pacchetti nel flusso luma_video
e restituisce l'output output_video
flusso di dati.
La nostra applicazione iOS mostrerà i frame immagine di output dell'output_video
flusso di dati.
Configurazione iniziale minima dell'applicazione
Iniziamo con una semplice applicazione per iOS e dimostriamo come utilizzare bazel
per realizzarla.
Innanzitutto, crea un progetto XCode tramite File > Nuovo > App Visualizzazione singola.
Imposta il nome del prodotto su "HelloWorld" e utilizza un'organizzazione appropriata
come com.google.mediapipe
. L'identificatore dell'organizzazione
insieme al nome del prodotto sarà il bundle_id
per l'applicazione, ad esempio
com.google.mediapipe.HelloWorld
.
Imposta il linguaggio su Objective-C.
Salva il progetto nella posizione appropriata. Lo chiamiamo
$PROJECT_TEMPLATE_LOC
. Quindi il tuo progetto sarà
Directory $PROJECT_TEMPLATE_LOC/HelloWorld
. Questa directory contiene
un'altra directory denominata HelloWorld
e un file HelloWorld.xcodeproj
.
HelloWorld.xcodeproj
non sarà utile per questo tutorial, perché lo utilizzeremo
per creare l'applicazione iOS. I contenuti
La directory $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
è elencata di seguito:
AppDelegate.h
eAppDelegate.m
ViewController.h
eViewController.m
main.m
Info.plist
Main.storyboard
eLaunch.storyboard
- Directory
Assets.xcassets
.
Copia questi file in una directory denominata HelloWorld
in una posizione che può accedere
con il codice sorgente del framework MediaPipe. Ad esempio, il codice sorgente
che creerai in questo tutorial si trova
mediapipe/examples/ios/HelloWorld
. Ci riferiremo a questo percorso come
$APPLICATION_PATH
in tutto il codelab.
Crea un file BUILD
in $APPLICATION_PATH
e aggiungi la seguente build
regole:
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 = [],
)
La regola objc_library
aggiunge dipendenze per AppDelegate
e
classi ViewController
, main.m
e gli storyboard dell'applicazione. La
app basata su modelli dipende solo dall'SDK UIKit
.
La regola ios_application
utilizza la libreria HelloWorldAppLibrary
Objective-C
generati per creare un'applicazione iOS da installare sul tuo dispositivo iOS.
Per creare l'app, usa questo comando in un terminale:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Ad esempio, per creare l'applicazione HelloWorldApp
in
mediapipe/examples/ios/helloworld
, usa questo comando:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Quindi, torna a XCode, apri Window > Dispositivi e simulatori, seleziona
dispositivo e aggiungi al tuo dispositivo il file .ipa
generato dal comando precedente.
Questo è il documento sulla configurazione e la compilazione delle app Framework per iOS.
Apri l'applicazione sul dispositivo. Poiché è vuoto, dovrebbe essere visualizzato un schermata bianca vuota.
Utilizzare la videocamera per il feed della visione in diretta
In questo tutorial, utilizzeremo la classe MPPCameraInputSource
per accedere e
acquisire fotogrammi dalla fotocamera. Questo corso utilizza l'API AVCaptureSession
per recuperare
i fotogrammi della fotocamera.
Ma prima di utilizzare questo corso, modifica il file Info.plist
in modo che supporti la fotocamera
all'utilizzo nell'app.
In ViewController.m
, aggiungi la seguente riga di importazione:
#import "mediapipe/objc/MPPCameraInputSource.h"
Aggiungi quanto segue al relativo blocco di implementazione per creare un oggetto
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Aggiungi il codice seguente a viewDidLoad()
:
-(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;
}
Il codice inizializza _cameraSource
, imposta la sessione di acquisizione preimpostata e
la videocamera.
Dobbiamo trasferire i frame dal _cameraSource
alla nostra applicazione
ViewController
per visualizzarli. MPPCameraInputSource
è una sottoclasse di
MPPInputSource
, che fornisce un protocollo per i suoi delegati, ovvero
MPPInputSourceDelegate
. Quindi la nostra applicazione ViewController
può essere un delegato
di _cameraSource
.
Aggiorna la definizione dell'interfaccia di ViewController
di conseguenza:
@interface ViewController () <MPPInputSourceDelegate>
Per gestire la configurazione della videocamera e l'elaborazione dei fotogrammi in arrivo, è necessario usare una coda
diverso da quello della coda principale. Aggiungi quanto segue al blocco di implementazione di
ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
In viewDidLoad()
, aggiungi la seguente riga dopo aver inizializzato il valore
Oggetto _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Aggiungi il seguente codice per inizializzare la coda prima di configurare il
Oggetto _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);
Utilizzeremo una coda seriale con priorità QOS_CLASS_USER_INTERACTIVE
per
l'elaborazione dei fotogrammi delle fotocamere.
Aggiungi la seguente riga dopo l'importazione dell'intestazione nella parte superiore del file, prima
l'interfaccia/l'implementazione di ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Prima di implementare qualsiasi metodo del protocollo MPPInputSourceDelegate
, dobbiamo
per prima cosa imposta un modo
per visualizzare i fotogrammi della fotocamera. Il framework Mediapipe fornisce
un'altra utility chiamata MPPLayerRenderer
per visualizzare immagini sullo schermo. Questo
può essere utilizzata per visualizzare CVPixelBufferRef
oggetti, che è il tipo
le immagini fornite da MPPCameraInputSource
ai suoi delegati.
In ViewController.m
, aggiungi la seguente riga di importazione:
#import "mediapipe/objc/MPPLayerRenderer.h"
Per visualizzare immagini dello schermo, dobbiamo aggiungere un nuovo oggetto UIView
chiamato
_liveView
a ViewController
.
Aggiungi le seguenti righe al blocco di implementazione di ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Vai a Main.storyboard
, aggiungi un oggetto UIView
dalla libreria di oggetti al
View
del corso ViewController
. Aggiungi uno sbocco di riferimento da questa visualizzazione a
l'oggetto _liveView
che hai appena aggiunto alla classe ViewController
. Ridimensiona la
in modo che sia centrato e copra l'intera schermata dell'applicazione.
Torna a ViewController.m
e aggiungi il seguente codice a viewDidLoad()
per
inizializza l'oggetto _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Per ottenere fotogrammi dalla fotocamera, implementeremo il seguente metodo:
// 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);
});
}
Questo è un metodo delegato di MPPInputSource
. Innanzitutto, verifichiamo
ricevere frame dall'origine corretta, ovvero _cameraSource
. Quindi mostriamo
il fotogramma ricevuto dalla fotocamera tramite _renderer
nella coda principale.
Ora dobbiamo avviare la videocamera non appena la visualizzazione per visualizzare i fotogrammi è
che sta per essere visualizzato. Per farlo, implementeremo il
Funzione viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Per poter iniziare a eseguire la fotocamera, è necessaria l'autorizzazione dell'utente per l'accesso.
MPPCameraInputSource
fornisce una funzione
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
per richiedere l'accesso alla fotocamera e svolgere alcune operazioni quando l'utente ha
hanno risposto. Aggiungi il seguente codice a viewWillAppear:animated
:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Prima di creare l'applicazione, aggiungi le seguenti dipendenze a BUILD
file:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Ora crea ed esegui l'applicazione sul tuo dispositivo iOS. Dovresti vedere una live feed visualizzazione videocamera dopo aver accettato le autorizzazioni di accesso alla videocamera.
Ora siamo pronti per utilizzare i fotogrammi della fotocamera in un grafico MediaPipe.
Utilizzo di un grafico MediaPipe in iOS
Aggiungi dipendenze pertinenti
Abbiamo già aggiunto le dipendenze del codice del framework MediaPipe che contiene
l'API iOS per usare un grafico MediaPipe. Per utilizzare un grafico MediaPipe, dobbiamo aggiungere
dal grafico che intendiamo usare nella nostra applicazione. Aggiungi quanto segue
riga all'elenco data
nel tuo file BUILD
:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Ora aggiungi la dipendenza alle calcolatrici utilizzate in questo grafico nel campo deps
nel file BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Infine, rinomina il file ViewController.m
in ViewController.mm
per renderlo
Objective-C++
Usa il grafico in ViewController
In ViewController.m
, aggiungi la seguente riga di importazione:
#import "mediapipe/objc/MPPGraph.h"
Dichiara una costante statica con il nome del grafico, il flusso di input e flusso di output:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Aggiungi la seguente proprietà all'interfaccia di ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Come spiegato nel commento precedente, inizializziamo questo grafico
viewDidLoad
prima. Per farlo, dobbiamo caricare il grafico dal file .pbtxt
utilizzando la seguente funzione:
+ (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;
}
Utilizza questa funzione per inizializzare il grafico in viewDidLoad
come segue:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Il grafico dovrebbe inviare i risultati dell'elaborazione dei fotogrammi della fotocamera al
ViewController
. Aggiungi la seguente riga dopo l'inizializzazione del grafico per impostare la
ViewController
come delegato dell'oggetto mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Per evitare la contesa della memoria durante l'elaborazione dei frame dal feed video in diretta, aggiungi nella riga seguente:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Ora avvia il grafico una volta che l'utente ha concesso l'autorizzazione a utilizzare la fotocamera nella nostra 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];
});
}
}];
Finora, quando ricevevamo fotogrammi dalla fotocamera nell'processVideoFrame
, le abbiamo visualizzate in _liveView
utilizzando la funzione _renderer
. Ora
inviare i frame al grafico e visualizzare i risultati. Modifica
l'implementazione di questa funzione per fare quanto segue:
- (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];
}
Inviamo imageBuffer
all'indirizzo self.mediapipeGraph
come pacchetto di tipo
MPPPacketTypePixelBuffer
nel flusso di input kInputStream
, ad esempio
"video_input".
Il grafico verrà eseguito con questo pacchetto di input e restituirà un risultato
kOutputStream
, ad esempio "output_video". Possiamo implementare il seguente delegato
per ricevere i pacchetti nel flusso di output e visualizzarli sullo schermo:
- (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);
});
}
}
Aggiorna la definizione dell'interfaccia di ViewController
con MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
E questo è tutto. Crea ed esegui l'app sul tuo dispositivo iOS. Dovresti vedere l'etichetta risultati dell'esecuzione del grafico del rilevamento dei bordi su un feed video in diretta. Complimenti!
Tieni presente che gli esempi per iOS ora utilizzano un'app modello comune. Il codice in
questo tutorial viene utilizzato nell'app modello comune. L'app helloworld include
le dipendenze del file BUILD
appropriate per il grafico del rilevamento perimetrale.