Introduction
Bonjour le monde ! utilise MediaPipe pour développer une application qui exécute un graphique MediaPipe sur iOS.
Objectifs de l'atelier
Application d'appareil photo simple pour la détection en temps réel des bords Sobel à une vidéo en direct diffuser sur un appareil iOS.
Configuration
- Installez MediaPipe Framework sur votre système, consultez la section Installation de Framework guide pour plus d'informations.
- Configurez votre appareil iOS pour le développement.
- Configurez Bazel sur votre système pour créer et déployer l'application iOS.
Graphique de détection des bords
Nous utiliserons le graphique suivant, 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"
}
Voici une visualisation du graphique:
Ce graphique comporte un seul flux d'entrée nommé input_video
pour tous les frames entrants
qui sera fournie par l'appareil photo de votre appareil.
Le premier nœud du graphique, LuminanceCalculator
, accepte un seul paquet (image
cadre) et applique un changement de luminance à l'aide d'un nuanceur OpenGL. Le résultat
la trame d'image est envoyée au flux de sortie luma_video
.
Le deuxième nœud, SobelEdgesCalculator
, applique la détection en périphérie aux
paquets dans le flux luma_video
et génère une sortie output_video
flux.
Notre application iOS affiche les images de sortie de output_video
.
flux.
Configuration initiale de l'application minimale
Nous commencerons par une application iOS simple et nous montrerons comment utiliser bazel
.
pour la construire.
Commencez par créer un projet XCode via Fichier > Nouveau > Single View App.
Définissez le nom du produit sur "HelloWorld" et utilisez une organisation appropriée.
tel que com.google.mediapipe
. Identifiant de l'organisation
ainsi que le nom de produit correspond à l'bundle_id
de l'application, par exemple
com.google.mediapipe.HelloWorld
Définissez le langage sur Objective-C.
Enregistrez le projet à l'emplacement approprié. Appelons cela
$PROJECT_TEMPLATE_LOC
Votre projet sera donc
Répertoire $PROJECT_TEMPLATE_LOC/HelloWorld
. Ce répertoire contiendra
un autre répertoire nommé HelloWorld
et un fichier HelloWorld.xcodeproj
.
Le HelloWorld.xcodeproj
ne sera pas utile pour ce tutoriel, car nous allons utiliser
bazel pour créer l'application iOS. Le contenu de la
Le répertoire $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
est indiqué ci-dessous:
AppDelegate.h
etAppDelegate.m
ViewController.h
etViewController.m
main.m
Info.plist
Main.storyboard
etLaunch.storyboard
Assets.xcassets
.
Copiez ces fichiers dans un répertoire nommé HelloWorld
et vers un emplacement accessible
le code source du framework MediaPipe. Par exemple, le code source du
que nous allons créer dans ce didacticiel se trouve
mediapipe/examples/ios/HelloWorld
Nous appellerons ce chemin
$APPLICATION_PATH
tout au long de l'atelier de programmation.
Créez un fichier BUILD
dans $APPLICATION_PATH
et ajoutez le build suivant
règles:
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 règle objc_library
ajoute des dépendances pour AppDelegate
et
ViewController
, main.m
et les storyboards d'application. La
l'application modélisée ne dépend que du SDK UIKit
.
La règle ios_application
utilise la bibliothèque Objective-C de HelloWorldAppLibrary
généré pour créer une application iOS
à installer sur votre appareil iOS.
Pour créer l'application, utilisez la commande suivante dans un terminal:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Par exemple, pour créer l'application HelloWorldApp
dans
mediapipe/examples/ios/helloworld
, exécutez la commande suivante:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Revenez ensuite dans XCode, ouvrez Fenêtre > Appareils et simulateurs, sélectionnez votre
puis ajoutez à votre appareil le fichier .ipa
généré par la commande ci-dessus.
Voici un document sur la configuration et la compilation des applications utilisant le framework iOS.
Ouvrez l'application sur votre appareil. Étant donné qu'il est vide, un écran blanc vierge.
Utiliser la caméra pour le flux vidéo en direct
Dans ce tutoriel, nous allons utiliser la classe MPPCameraInputSource
pour accéder aux
prendre des images
de l'appareil photo. Cette classe utilise l'API AVCaptureSession
pour obtenir
les images de l'appareil photo.
Mais avant d'utiliser cette classe, modifiez le fichier Info.plist
pour qu'il soit compatible avec l'appareil photo.
dans l'application.
Dans ViewController.m
, ajoutez la ligne d'importation suivante:
#import "mediapipe/objc/MPPCameraInputSource.h"
Ajoutez le code suivant à son bloc d'implémentation pour créer un objet.
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Ajoutez le code suivant à 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;
}
Le code initialise _cameraSource
, définit le préréglage de la session de capture et définit les paramètres
l'appareil photo à utiliser.
Nous devons transférer les frames de _cameraSource
dans notre application.
ViewController
pour les afficher. MPPCameraInputSource
est une sous-classe de
MPPInputSource
, qui fournit un protocole à ses délégués, à savoir le
MPPInputSourceDelegate
Notre application ViewController
peut donc être déléguée
sur _cameraSource
.
Mettez à jour la définition de l'interface de ViewController
en conséquence:
@interface ViewController () <MPPInputSourceDelegate>
Pour gérer la configuration de la caméra et traiter les images entrantes, nous devons utiliser une file d'attente
différente de la file d'attente principale. Ajoutez le code suivant au bloc d'implémentation de
le ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
Dans viewDidLoad()
, ajoutez la ligne suivante après avoir initialisé
Objet _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Ajoutez le code suivant pour initialiser la file d'attente avant de configurer
Objet _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);
Nous allons utiliser une file d'attente série avec la priorité QOS_CLASS_USER_INTERACTIVE
pour
et traiter les images de l'appareil photo.
Ajoutez la ligne suivante après les importations d'en-têtes en haut du fichier, avant
l'interface/l'implémentation de ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Avant d'implémenter une méthode du protocole MPPInputSourceDelegate
, nous devons
configurez tout d'abord un moyen
d'afficher les images de l'appareil photo. Mediapipe Framework fournit
un autre utilitaire appelé MPPLayerRenderer
pour afficher des images à l'écran. Ce
vous permet d'afficher les objets CVPixelBufferRef
, qui est le type de
les images fournies par MPPCameraInputSource
à ses délégués.
Dans ViewController.m
, ajoutez la ligne d'importation suivante:
#import "mediapipe/objc/MPPLayerRenderer.h"
Pour afficher les images de l'écran, nous devons ajouter un objet UIView
appelé
_liveView
sur ViewController
.
Ajoutez les lignes suivantes au bloc d'implémentation de ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Accédez à Main.storyboard
, ajoutez un objet UIView
de la bibliothèque d'objets à
View
de la classe ViewController
. Ajoutez une sortie de référence à partir de cette vue pour
l'objet _liveView
que vous venez d'ajouter à la classe ViewController
; Redimensionnez la
afin qu'elle soit centrée
et recouvre tout l'écran de l'application.
Revenez à ViewController.m
et ajoutez le code suivant à viewDidLoad()
pour
initialisez l'objet _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Pour obtenir les images de l'appareil photo, nous allons implémenter la méthode suivante:
// 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);
});
}
Il s'agit d'une méthode déléguée de MPPInputSource
. Nous vérifions
d’abord que nous
obtenir des images de la bonne source, c'est-à-dire de _cameraSource
. Ensuite, nous affichons
image reçue de l'appareil photo via _renderer
dans la file d'attente principale.
Nous devons maintenant démarrer l'appareil photo dès que la vue permettant d'afficher les images est
sur le point d'apparaître. Pour ce faire, nous allons implémenter
Fonction viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Avant de commencer à utiliser l'appareil photo, nous avons besoin de l'autorisation de l'utilisateur pour y accéder.
MPPCameraInputSource
fournit une fonction
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
pour demander l'accès à l'appareil photo et effectuer des tâches lorsque l'utilisateur a
a répondu. Ajoutez le code suivant à viewWillAppear:animated
:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Avant de créer l'application, ajoutez les dépendances suivantes à votre BUILD
:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Maintenant, créez et exécutez l'application sur votre appareil iOS. Vous devriez voir flux de vue de la caméra après avoir accepté les autorisations d'accès à l'appareil photo.
Nous sommes maintenant prêts à utiliser des images d'appareil photo dans un graphique MediaPipe.
Utiliser un graphique MediaPipe dans iOS
Ajouter les dépendances pertinentes
Nous avons déjà ajouté les dépendances du code du framework MediaPipe, qui contient
l'API iOS pour utiliser
un graphique MediaPipe. Pour utiliser un graphe MediaPipe, nous devons ajouter
sur le graphique que nous avons l'intention
d'utiliser dans notre application. Ajoutez les éléments suivants :
à la liste data
de votre fichier BUILD
:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Ajoutez maintenant la dépendance aux calculatrices utilisées dans ce graphique dans le champ deps
dans le fichier BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Enfin, remplacez le nom du fichier ViewController.m
par ViewController.mm
pour qu'il accepte
Objective-C++
Utiliser le graphique en ViewController
Dans ViewController.m
, ajoutez la ligne d'importation suivante:
#import "mediapipe/objc/MPPGraph.h"
Déclarez une constante statique avec le nom du graphique, le flux d'entrée et le flux de sortie:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Ajoutez la propriété suivante à l'interface de ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Comme expliqué dans le commentaire ci-dessus, nous allons initialiser ce graphique dans
viewDidLoad
d'abord. Pour ce faire, nous devons charger le graphique à partir du fichier .pbtxt
.
à l'aide de la fonction suivante:
+ (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;
}
Utilisez cette fonction pour initialiser le graphique dans viewDidLoad
, comme suit:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Le graphique doit renvoyer les résultats du traitement des images de l'appareil photo
ViewController
Après avoir initialisé le graphique, ajoutez la ligne suivante pour définir le
ViewController
en tant que délégué de l'objet mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Pour éviter les conflits dans la mémoire lors du traitement des images du flux vidéo en direct, ajoutez à la ligne suivante:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Démarrer le graphique une fois que l'utilisateur a autorisé l'utilisation de l'appareil photo dans notre application:
[_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];
});
}
}];
Précédemment, lorsque nous recevions des images de l'appareil photo dans processVideoFrame
nous les avons affichés dans _liveView
à l'aide de _renderer
. Maintenant, nous
vous devez envoyer ces images au graphique
et afficher les résultats à la place. Modifier
l'implémentation de cette fonction pour effectuer les opérations suivantes:
- (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];
}
Nous envoyons le imageBuffer
à self.mediapipeGraph
en tant que paquet de type
MPPPacketTypePixelBuffer
dans le flux d'entrée kInputStream
, c'est-à-dire
"input_video".
Le graphique s'exécutera avec ce paquet d'entrée et renverra un résultat dans
kOutputStream
, par exemple "output_video". Nous pouvons implémenter le délégué suivant :
pour recevoir des paquets sur ce flux de sortie et les afficher à l'écran:
- (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);
});
}
}
Mettez à jour la définition de l'interface de ViewController
avec MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Et c'est tout ! Créez et exécutez l'application sur votre appareil iOS. Vous devriez voir résultats de l'exécution du graphique de détection des bords sur un flux vidéo en direct. Félicitations !
Veuillez noter que les exemples iOS utilisent désormais un modèle d'application commun. Le code
ce tutoriel est utilisé dans le modèle d'application common. L'application helloworld contient les
les dépendances appropriées du fichier BUILD
pour le graphique de détection de périphérie.