Hyrje
Kjo Hello World! tutoriali përdor MediaPipe Framework për të zhvilluar një aplikacion iOS që ekzekuton një grafik MediaPipe në iOS.
Çfarë do të ndërtoni
Një aplikacion i thjeshtë kamere për zbulimin e skajeve të Sobel në kohë reale aplikohet në një transmetim video të drejtpërdrejtë në një pajisje iOS.

Konfigurimi
- Instaloni MediaPipe Framework në sistemin tuaj, shikoni udhëzuesin e instalimit të Framework për detaje.
- Konfiguro pajisjen tuaj iOS për zhvillim.
- Konfiguro Bazel në sistemin tënd për të ndërtuar dhe vendosur aplikacionin iOS.
Grafiku për zbulimin e skajeve
Ne do të përdorim grafikun e mëposhtëm, 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"
}
Një vizualizimi i grafikut është paraqitur më poshtë:

Ky grafik ka një transmetim të vetëm hyrje të quajtur input_video për të gjitha kornizat hyrëse që do të ofrohen nga kamera e pajisjes suaj.
Nyja e parë në grafik, LuminanceCalculator , merr një paketë të vetme (kornizë imazhi) dhe zbaton një ndryshim në ndriçimin duke përdorur një shader OpenGL. Korniza e imazhit që rezulton dërgohet në rrjedhën e daljes luma_video .
Nyja e dytë, SobelEdgesCalculator aplikon zbulimin e skajeve për paketat hyrëse në rrjedhën luma_video dhe nxjerr rezultatet në rrjedhën e daljes output_video .
Aplikacioni ynë iOS do të shfaqë kornizat e imazhit në dalje të transmetimit output_video .
Konfigurimi fillestar minimal i aplikacionit
Fillimisht fillojmë me një aplikacion të thjeshtë iOS dhe demonstrojmë se si të përdorim bazel për ta ndërtuar atë.
Së pari, krijoni një projekt XCode përmes File > New > Single View App.
Cakto emrin e produktit në "HelloWorld" dhe përdor një identifikues të përshtatshëm organizimi, si p.sh. com.google.mediapipe . Identifikuesi i organizatës së bashku me emrin e produktit do të jetë bundle_id për aplikacionin, si p.sh. com.google.mediapipe.HelloWorld .
Vendoseni gjuhën në Objektivi-C.
Ruani projektin në një vend të përshtatshëm. Le ta quajmë këtë $PROJECT_TEMPLATE_LOC . Kështu që projekti juaj do të jetë në drejtorinë $PROJECT_TEMPLATE_LOC/HelloWorld . Kjo direktori do të përmbajë një drejtori tjetër të quajtur HelloWorld dhe një skedar HelloWorld.xcodeproj .
HelloWorld.xcodeproj nuk do të jetë i dobishëm për këtë tutorial, pasi ne do të përdorim bazel për të ndërtuar aplikacionin iOS. Përmbajtja e drejtorisë $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld është renditur më poshtë:
-
AppDelegate.hdheAppDelegate.m -
ViewController.hdheViewController.m -
main.m -
Info.plist -
Main.storyboarddheLaunch.storyboard - Drejtoria
Assets.xcassets.
Kopjojini këta skedarë në një drejtori të quajtur HelloWorld në një vendndodhje që mund të hyjë në kodin burimor të MediaPipe Framework. Për shembull, kodi burim i aplikacionit që do të ndërtojmë në këtë tutorial ndodhet në mediapipe/examples/ios/HelloWorld . Ne do t'i referohemi kësaj rruge si $APPLICATION_PATH në të gjithë laboratorin e kodeve.
Krijoni një skedar BUILD në $APPLICATION_PATH dhe shtoni rregullat e mëposhtme të ndërtimit:
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 = [],
)
Rregulli objc_library shton varësi për klasat AppDelegate dhe ViewController , main.m dhe skedat e aplikacioneve. Aplikacioni i modeluar varet vetëm nga UIKit SDK.
Rregulli ios_application përdor bibliotekën HelloWorldAppLibrary Objective-C të krijuar për të ndërtuar një aplikacion iOS për instalim në pajisjen tuaj iOS.
Për të ndërtuar aplikacionin, përdorni komandën e mëposhtme në një terminal:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Për shembull, për të ndërtuar aplikacionin HelloWorldApp në mediapipe/examples/ios/helloworld , përdorni komandën e mëposhtme:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Më pas, kthehuni te XCode, hapni dritaren > Pajisjet dhe simuluesit, zgjidhni pajisjen tuaj dhe shtoni skedarin .ipa të krijuar nga komanda e mësipërme në pajisjen tuaj. Këtu është dokumenti për konfigurimin dhe përpilimin e aplikacioneve iOS Framework.
Hapni aplikacionin në pajisjen tuaj. Meqenëse është bosh, duhet të shfaqë një ekran të bardhë bosh.
Përdorni kamerën për furnizimin e pamjes së drejtpërdrejtë
Në këtë tutorial, ne do të përdorim klasën MPPCameraInputSource për të hyrë dhe për të kapur kornizat nga kamera. Kjo klasë përdor API- AVCaptureSession për të marrë kornizat nga kamera.
Por përpara se të përdorni këtë klasë, ndryshoni skedarin Info.plist për të mbështetur përdorimin e kamerës në aplikacion.
Në ViewController.m , shtoni linjën e mëposhtme të importit:
#import "mediapipe/objc/MPPCameraInputSource.h"
Shtoni sa vijon në bllokun e tij të zbatimit për të krijuar një objekt _cameraSource :
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Shto kodin e mëposhtëm për të 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;
}
Kodi inicializon _cameraSource , cakton paracaktimin e sesionit të kapjes dhe cilën kamerë të përdoret.
Ne duhet të marrim korniza nga _cameraSource në aplikacionin tonë ViewController për t'i shfaqur ato. MPPCameraInputSource është një nënklasë e MPPInputSource , e cila ofron një protokoll për delegatët e tij, përkatësisht MPPInputSourceDelegate . Pra, aplikacioni ynë ViewController mund të jetë një delegat i _cameraSource .
Përditësoni përkufizimin e ndërfaqes së ViewController në përputhje me rrethanat:
@interface ViewController () <MPPInputSourceDelegate>
Për të trajtuar konfigurimin e kamerës dhe për të përpunuar kornizat hyrëse, duhet të përdorim një radhë të ndryshme nga radha kryesore. Shtoni sa vijon në bllokun e zbatimit të ViewController :
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
Në viewDidLoad() , shtoni rreshtin e mëposhtëm pas inicializimit të objektit _cameraSource :
[_cameraSource setDelegate:self queue:_videoQueue];
Dhe shtoni kodin e mëposhtëm për të inicializuar radhën përpara se të vendosni objektin _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);
Ne do të përdorim një radhë serike me përparësi QOS_CLASS_USER_INTERACTIVE për përpunimin e kornizave të kamerës.
Shtoni rreshtin e mëposhtëm pas importimit të kokës në krye të skedarit, përpara ndërfaqes/zbatimit të ViewController :
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Përpara se të zbatojmë ndonjë metodë nga protokolli MPPInputSourceDelegate , së pari duhet të vendosim një mënyrë për të shfaqur kornizat e kamerës. Mediapipe Framework ofron një mjet tjetër të quajtur MPPLayerRenderer për të shfaqur imazhet në ekran. Ky mjet mund të përdoret për të shfaqur objektet CVPixelBufferRef , që është lloji i imazheve të ofruara nga MPPCameraInputSource për delegatët e tij.
Në ViewController.m , shtoni linjën e mëposhtme të importit:
#import "mediapipe/objc/MPPLayerRenderer.h"
Për të shfaqur imazhet e ekranit, duhet të shtojmë një objekt të ri UIView të quajtur _liveView në ViewController .
Shtoni linjat e mëposhtme në bllokun e zbatimit të ViewController :
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Shkoni te Main.storyboard , shtoni një objekt UIView nga biblioteka e objekteve në View e klasës ViewController . Shtoni një prizë referimi nga kjo pamje tek objekti _liveView që sapo keni shtuar në klasën ViewController . Ndryshoni madhësinë e pamjes në mënyrë që të jetë në qendër dhe të mbulojë të gjithë ekranin e aplikacionit.
Kthehuni te ViewController.m dhe shtoni kodin e mëposhtëm në viewDidLoad() për të inicializuar objektin _renderer :
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Për të marrë korniza nga kamera, ne do të zbatojmë metodën e mëposhtme:
// 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);
});
}
Kjo është një metodë delegimi e MPPInputSource . Fillimisht kontrollojmë që po marrim korniza nga burimi i duhur, pra _cameraSource . Më pas ne shfaqim kornizën e marrë nga kamera nëpërmjet _renderer në radhën kryesore.
Tani, duhet ta nisim kamerën sapo të shfaqet pamja për të shfaqur kornizat. Për ta bërë këtë, ne do të zbatojmë funksionin viewWillAppear:(BOOL)animated :
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Përpara se të fillojmë të ekzekutojmë kamerën, na duhet leja e përdoruesit për të hyrë në të. MPPCameraInputSource ofron një funksion requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL granted))handler për të kërkuar qasje në kamerë dhe për të bërë disa punë kur përdoruesi është përgjigjur. Shtoni kodin e mëposhtëm për të viewWillAppear:animated :
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Përpara se të ndërtoni aplikacionin, shtoni varësitë e mëposhtme në skedarin tuaj BUILD :
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Tani ndërtoni dhe ekzekutoni aplikacionin në pajisjen tuaj iOS. Duhet të shihni një furnizim të drejtpërdrejtë të pamjes së kamerës pasi të pranoni lejet e kamerës.
Tani jemi gati të përdorim kornizat e kamerës në një grafik MediaPipe.
Përdorimi i një grafiku MediaPipe në iOS
Shtoni varësitë përkatëse
Ne kemi shtuar tashmë varësitë e kodit të kornizës MediaPipe i cili përmban API-në iOS për të përdorur një grafik MediaPipe. Për të përdorur një grafik MediaPipe, duhet të shtojmë një varësi nga grafiku që synojmë të përdorim në aplikacionin tonë. Shtoni rreshtin e mëposhtëm në listën e data në skedarin tuaj BUILD :
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Tani shtoni varësinë te kalkulatorët e përdorur në këtë grafik në fushën deps në skedarin BUILD :
"//mediapipe/graphs/edge_detection:mobile_calculators",
Më në fund, riemërtoni skedarin ViewController.m në ViewController.mm për të mbështetur Objective-C++.
Përdorni grafikun në ViewController
Në ViewController.m , shtoni linjën e mëposhtme të importit:
#import "mediapipe/objc/MPPGraph.h"
Deklaroni një konstante statike me emrin e grafikut, rrymën hyrëse dhe rrjedhën dalëse:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Shtoni veçorinë e mëposhtme në ndërfaqen e ViewController :
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Siç shpjegohet në komentin e mësipërm, ne fillimisht do ta inicializojmë këtë grafik në viewDidLoad . Për ta bërë këtë, ne duhet të ngarkojmë grafikun nga skedari .pbtxt duke përdorur funksionin e mëposhtëm:
+ (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;
}
Përdorni këtë funksion për të inicializuar grafikun në viewDidLoad si më poshtë:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Grafiku duhet t'i dërgojë rezultatet e përpunimit të kornizave të kamerës përsëri te ViewController . Shtoni rreshtin e mëposhtëm pas inicializimit të grafikut për të vendosur ViewController si delegat të objektit mediapipeGraph :
self.mediapipeGraph.delegate = self;
Për të shmangur grindjet e kujtesës gjatë përpunimit të kornizave nga furnizimi i drejtpërdrejtë i videos, shtoni rreshtin e mëposhtëm:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Tani, filloni grafikun kur përdoruesi ka dhënë lejen për të përdorur kamerën në aplikacionin tonë:
[_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];
});
}
}];
Më parë, kur merrnim korniza nga kamera në funksionin processVideoFrame , ne i shfaqnim ato në _liveView duke përdorur _renderer . Tani, ne duhet t'i dërgojmë ato korniza në grafik dhe në vend të kësaj të japim rezultatet. Ndryshoni zbatimin e këtij funksioni për të bërë sa më poshtë:
- (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];
}
Ne dërgojmë imageBuffer në self.mediapipeGraph si një paketë të tipit MPPPacketTypePixelBuffer në rrjedhën hyrëse kInputStream , dmth. "input_video".
Grafiku do të ekzekutohet me këtë paketë hyrëse dhe do të nxjerrë një rezultat në kOutputStream , p.sh. "output_video". Ne mund të zbatojmë metodën e mëposhtme të delegimit për të marrë paketa në këtë rrjedhë dalëse dhe për t'i shfaqur ato në ekran:
- (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);
});
}
}
Përditësoni përkufizimin e ndërfaqes së ViewController me MPPGraphDelegate :
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Dhe kjo është e gjitha! Ndërtoni dhe ekzekutoni aplikacionin në pajisjen tuaj iOS. Ju duhet të shihni rezultatet e ekzekutimit të grafikut të zbulimit të skajeve në një furnizim video të drejtpërdrejtë. urime!

Ju lutemi vini re se shembujt e iOS tani përdorin një aplikacion të zakonshëm shabllonesh. Kodi në këtë tutorial përdoret në aplikacionin e zakonshëm të shabllonit. Aplikacioni helloworld ka varësitë e duhura të skedarit BUILD për grafikun e zbulimit të skajeve.