Introdução
Este "Hello World!" tutorial usa o MediaPipe Framework para desenvolver um aplicativo aplicativo que executa um gráfico do MediaPipe no iOS.
O que você criará
Um app de câmera simples para detecção em tempo real do Sobel Edge aplicado a um vídeo ao vivo transmitir em um dispositivo iOS.
Configuração
- Instale o MediaPipe Framework no seu sistema. Consulte Instalação do framework para mais detalhes.
- Configure seu dispositivo iOS para desenvolvimento.
- Configure o Bazel no seu sistema para criar e implantar o app iOS.
Gráfico para detecção de borda
Vamos usar o seguinte gráfico, 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"
}
Confira abaixo uma visualização do gráfico:
Este gráfico tem um único fluxo de entrada chamado input_video
para todos os frames recebidos
que será fornecido pela câmera do seu dispositivo.
O primeiro nó do gráfico, LuminanceCalculator
, recebe um único pacote (imagem
frame) e aplica uma alteração na luminância usando um sombreador do OpenGL. O resultado
frame de imagem é enviado para o stream de saída luma_video
.
O segundo nó, SobelEdgesCalculator
, aplica a detecção de borda
Pacotes no fluxo luma_video
e gera uma saída output_video
riacho.
Nosso aplicativo iOS vai mostrar os frames de imagem de saída do output_video
.
riacho.
Configuração inicial mínima do aplicativo
Vamos começar com um aplicativo iOS simples e demonstrar como usar o bazel
para criá-lo.
Primeiro, crie um projeto XCode em File > Novo > App de visualização única.
Definir o nome do produto como "HelloWorld" e usar uma organização apropriada
identificador, como com.google.mediapipe
. O identificador da organização
com o nome do produto, será o bundle_id
do aplicativo, como
com.google.mediapipe.HelloWorld
.
Defina o idioma como Objective-C.
Salve o projeto em um local apropriado. Vamos chamá-lo
$PROJECT_TEMPLATE_LOC
: Portanto, seu projeto estará
$PROJECT_TEMPLATE_LOC/HelloWorld
. Esse diretório vai conter
outro diretório chamado HelloWorld
e um arquivo HelloWorld.xcodeproj
.
O HelloWorld.xcodeproj
não será útil para este tutorial, porque usaremos
Bazel para criar o aplicativo iOS. O conteúdo do
$PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
está listado abaixo:
AppDelegate.h
eAppDelegate.m
ViewController.h
eViewController.m
main.m
Info.plist
Main.storyboard
eLaunch.storyboard
Assets.xcassets
.
Copie esses arquivos em um diretório chamado HelloWorld
para um local que possa acessar
o código-fonte do MediaPipe Framework. Por exemplo, o código-fonte da
que vamos criar neste tutorial está localizado em
mediapipe/examples/ios/HelloWorld
: Chamaremos este caminho de
$APPLICATION_PATH
no codelab.
Crie um arquivo BUILD
no $APPLICATION_PATH
e adicione o seguinte build
regras:
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 = [],
)
A regra objc_library
adiciona dependências para AppDelegate
e
As classes ViewController
, main.m
e os storyboards do aplicativo. A
app baseado em modelo depende apenas do SDK do UIKit
.
A regra ios_application
usa a biblioteca HelloWorldAppLibrary
do Objective-C
gerado para criar um aplicativo iOS para instalação em seu dispositivo iOS.
Para criar o app, use o seguinte comando em um terminal:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Por exemplo, para criar o aplicativo HelloWorldApp
em
mediapipe/examples/ios/helloworld
, use o seguinte comando:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Depois, volte ao XCode, abra Window > Dispositivos e simuladores, selecione seu
dispositivo e adicione ao seu dispositivo o arquivo .ipa
gerado pelo comando acima.
Este é o documento sobre como configurar e compilar apps de framework do iOS.
Abra o aplicativo no seu dispositivo. Como está vazio, ele deve exibir uma uma tela em branco.
Usar a câmera para o feed de imagens ao vivo
Neste tutorial, usaremos a classe MPPCameraInputSource
para acessar e
captura de frames da câmera. Essa classe usa a API AVCaptureSession
para receber
os frames da câmera.
No entanto, antes de usar essa classe, mude o arquivo Info.plist
para oferecer suporte à câmera
uso no app.
Em ViewController.m
, adicione a seguinte linha de importação:
#import "mediapipe/objc/MPPCameraInputSource.h"
Adicione o seguinte ao bloco de implementação para criar um objeto
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Adicione o código a seguir 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;
}
O código inicializa o _cameraSource
, define a predefinição da sessão de captura e qual
usar.
Precisamos transmitir os frames do _cameraSource
para o aplicativo.
ViewController
para que elas sejam mostradas. MPPCameraInputSource
é uma subclasse de
MPPInputSource
, que fornece um protocolo para os delegados, ou seja,
MPPInputSourceDelegate
Portanto, nosso aplicativo ViewController
pode ser um delegado
de _cameraSource
.
Atualize a definição da interface de ViewController
conforme necessário:
@interface ViewController () <MPPInputSourceDelegate>
Para lidar com a configuração da câmera e processar os frames recebidos, devemos usar uma fila
diferente da fila principal. Adicione o seguinte ao bloco de implementação de
o ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
No viewDidLoad()
, adicione a linha abaixo depois de inicializar o
Objeto _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
E adicione o código a seguir para inicializar a fila antes de configurar o
Objeto _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);
Usaremos uma fila serial com prioridade QOS_CLASS_USER_INTERACTIVE
para
processando frames da câmera.
Adicione a linha a seguir após a importação do cabeçalho no topo do arquivo, antes de
a interface/implementação do ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Antes de implementar qualquer método do protocolo MPPInputSourceDelegate
, precisamos
configure uma maneira de exibir os frames da câmera. O framework Mediapipe fornece
outro utilitário chamado MPPLayerRenderer
para mostrar imagens na tela. Isso
utilitário pode ser usado para mostrar objetos CVPixelBufferRef
, que é o tipo de
as imagens fornecidas por MPPCameraInputSource
aos delegados.
Em ViewController.m
, adicione a seguinte linha de importação:
#import "mediapipe/objc/MPPLayerRenderer.h"
Para mostrar imagens da tela, precisamos adicionar um novo objeto UIView
chamado
_liveView
para ViewController
.
Adicione as linhas abaixo ao bloco de implementação de ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Acesse o Main.storyboard
e adicione um objeto UIView
da biblioteca de objetos ao
View
da classe ViewController
. Adicione uma saída de referência a partir desta visualização para
o objeto _liveView
que você acabou de adicionar à classe ViewController
. Redimensione o
para que fique centralizada e cubra toda a tela do aplicativo.
Volte para ViewController.m
e adicione o seguinte código ao arquivo viewDidLoad()
para
inicialize o objeto _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Para receber frames da câmera, vamos implementar o seguinte método:
// 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);
});
}
Esse é um método delegado de MPPInputSource
. Primeiro verificamos se estamos
receber frames da origem certa, ou seja, a _cameraSource
. Em seguida, exibimos
o frame recebido da câmera por _renderer
na fila principal.
Agora, precisamos iniciar a câmera assim que a visualização para exibir os frames estiver
prestes a aparecer. Para isso, vamos implementar a
Função viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Antes de executarmos a câmera, precisamos da permissão do usuário para acessá-la.
MPPCameraInputSource
fornece uma função
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
para solicitar acesso à câmera e realizar algumas tarefas quando o usuário tiver
respondeu. Adicione o código a seguir à viewWillAppear:animated
:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Antes de criar o aplicativo, adicione as seguintes dependências ao arquivo BUILD
arquivo:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Agora crie e execute o aplicativo no seu dispositivo iOS. Você verá um feed de visualização da câmera depois de aceitar as permissões da câmera.
Agora estamos prontos para usar frames da câmera em um gráfico do MediaPipe.
Como usar um gráfico do MediaPipe no iOS
Adicionar dependências relevantes
Já adicionamos as dependências do código do framework do MediaPipe, que contém
a API do iOS para usar um gráfico MediaPipe. Para usar um gráfico do MediaPipe, precisamos adicionar um
dependência do gráfico que pretendemos usar no aplicativo. Adicione o seguinte:
linha à lista data
no seu arquivo BUILD
:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Agora adicione a dependência às calculadoras usadas no gráfico no campo deps
no arquivo BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Por fim, renomeie o arquivo ViewController.m
para ViewController.mm
.
Objective-C++:
Usar o gráfico no ViewController
Em ViewController.m
, adicione a seguinte linha de importação:
#import "mediapipe/objc/MPPGraph.h"
Declare uma constante estática com o nome do gráfico, o fluxo de entrada e o stream de saída:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Adicione a seguinte propriedade à interface do ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Como explicado no comentário acima, vamos inicializar o gráfico
viewDidLoad
primeiro. Para fazer isso, precisamos carregar o gráfico do arquivo .pbtxt
.
usando a seguinte função:
+ (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;
}
Use essa função para inicializar o gráfico em viewDidLoad
da seguinte maneira:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
O gráfico deve enviar os resultados do processamento de frames da câmera de volta ao
ViewController
: Adicione a linha a seguir depois de inicializar o gráfico para definir
ViewController
como delegado do objeto mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Para evitar contenção de memória ao processar quadros do feed de vídeo ao vivo, adicione a seguinte linha:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Agora, inicie o gráfico quando o usuário conceder permissão para usar a câmera no nosso 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];
});
}
}];
Anteriormente, quando recebíamos frames da câmera no processVideoFrame
as exibimos no _liveView
usando a _renderer
. Agora, nós
precisa enviar esses frames ao gráfico e renderizar os resultados. Modificar
implementação dessa função para fazer o seguinte:
- (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];
}
Enviamos imageBuffer
para self.mediapipeGraph
como um pacote do tipo
MPPPacketTypePixelBuffer
no fluxo de entrada kInputStream
, ou seja,
"input_video".
O gráfico será executado com esse pacote de entrada e gerará um resultado
kOutputStream
, ou seja, "output_video". Podemos implementar o seguinte
para receber pacotes nesse stream de saída e exibi-los na tela:
- (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);
});
}
}
Atualize a definição de interface de ViewController
com MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
E isso é tudo! Crie e execute o app no seu dispositivo iOS. Aparecerá resultados da execução do gráfico de detecção de borda em um feed de vídeo ao vivo. Parabéns!
Os exemplos de iOS agora usam um aplicativo de modelo comum. O código no
este tutorial é usado no app de modelo comum. O aplicativo helloworld tem a
dependências do arquivo BUILD
apropriadas para o gráfico de detecção de borda.