מבוא
שלום לעולם הזה! המדריך משתמש ב-MediaPipe Framework כדי לפתח גרסת iOS אפליקציה שמריצה תרשים MediaPipe ב-iOS.
מה תפַתחו
אפליקציית מצלמה פשוטה לזיהוי קצה של Sobel בזמן אמת, שמופעלת על סרטון בשידור חי להפעיל סטרימינג במכשיר iOS.
הגדרה
- למידע על התקנת MediaPipe Framework במערכת, ראו התקנת Framework מדריך לפרטים.
- הגדרת מכשיר iOS לפיתוח.
- מגדירים את Bazel במערכת כדי ליצור ולפרוס את האפליקציה ל-iOS.
תרשים לזיהוי קצה
נשתמש בתרשים הבא, 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"
}
המחשה של התרשים מוצגת למטה:
התרשים הזה כולל זרם קלט יחיד בשם input_video
לכל הפריימים הנכנסים
שמצלמת המכשיר תספק.
הצומת הראשון בתרשים, LuminanceCalculator
, לוקח חבילה אחת (תמונה)
מסגרת ) ומחיל שינוי ברמת הבהירות באמצעות תוכנת ההצללה של OpenGL. התוצאה של
מסגרת התמונה נשלחת לזרם הפלט של luma_video
.
הצומת השני, SobelEdgesCalculator
מחיל זיהוי קצה על כניסות
המנות בזרם luma_video
והפלט גורמות לפלט output_video
.
אפליקציית iOS שלנו תציג את פריימים של תמונת הפלט של output_video
.
הגדרה מינימלית ראשונית של האפליקציה
קודם כל אנחנו מתחילים באפליקציה פשוטה ל-iOS ומדגימים איך להשתמש ב-bazel
כדי לבנות אותו.
קודם כול, יוצרים פרויקט XCode דרך 'קובץ' > חדש > אפליקציה עם תצוגה יחידה.
יש להגדיר את שם המוצר ל-"HelloWorld" ולהשתמש בארגון מתאים
למשל com.google.mediapipe
. מזהה הארגון
יחד עם שם המוצר יהיה bundle_id
של האפליקציה, כמו
com.google.mediapipe.HelloWorld
.
מגדירים את השפה כ-Objective-C.
שומרים את הפרויקט במיקום מתאים. רוצה לקרוא לזה?
$PROJECT_TEMPLATE_LOC
כלומר הפרויקט שלכם
ספריית $PROJECT_TEMPLATE_LOC/HelloWorld
. הספרייה הזו תכיל
עם ספרייה אחרת בשם HelloWorld
וקובץ HelloWorld.xcodeproj
.
הקובץ HelloWorld.xcodeproj
לא יועיל במדריך הזה, כי נשתמש בו
bazel כדי לבנות את האפליקציה ל-iOS. התוכן של
הספרייה $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
רשומה כאן:
AppDelegate.h
וגםAppDelegate.m
ViewController.h
וגםViewController.m
main.m
Info.plist
Main.storyboard
וגםLaunch.storyboard
- ספריית
Assets.xcassets
.
אפשר להעתיק את הקבצים האלה לספרייה בשם HelloWorld
למיקום שיש לו גישה
את קוד המקור של MediaPipe Framework. לדוגמה, קוד המקור של
שניצור במדריך הזה נמצאת
mediapipe/examples/ios/HelloWorld
נתייחס לנתיב הזה
$APPLICATION_PATH
במהלך ה-Codelab.
צריך ליצור קובץ BUILD
ב-$APPLICATION_PATH
ולהוסיף את גרסת ה-build הבאה
כללים:
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 = [],
)
הכלל objc_library
מוסיף יחסי תלות של AppDelegate
ViewController
כיתות, main.m
וסטוריבורד של האפליקציה.
אפליקציה בתבנית תלויה רק ב-SDK UIKit
.
הכלל ios_application
משתמש בספריית Objective-C HelloWorldAppLibrary
שנוצר כדי לבנות אפליקציה ל-iOS להתקנה במכשיר iOS.
כדי ליצור את האפליקציה, צריך להשתמש בפקודה הבאה בטרמינל:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
לדוגמה, כדי לפתח את האפליקציה HelloWorldApp
ב-
mediapipe/examples/ios/helloworld
, משתמשים בפקודה הבאה:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
לאחר מכן, חוזרים אל XCode, פותחים חלון > מכשירים וסימולטורים, בוחרים
במכשיר, ומוסיפים למכשיר את הקובץ .ipa
שנוצר באמצעות הפקודה שלמעלה.
כאן ניתן למצוא את המסמך בנושא הגדרה והידור של אפליקציות iOS Framework.
פותחים את האפליקציה במכשיר. מכיוון שהוא ריק, צריך להציג מסך לבן ריק.
שימוש במצלמה ליצירת פיד של צפייה בשידור חי
במדריך הזה נשתמש בכיתה MPPCameraInputSource
כדי לגשת
לוקחים פריימים מהמצלמה. הכיתה הזו משתמשת ב-API של AVCaptureSession
כדי לקבל
את הפריימים מהמצלמה.
עם זאת, לפני השימוש בכיתה הזו, צריך לשנות את הקובץ Info.plist
כך שיתמוך במצלמה
שימוש באפליקציה.
ב-ViewController.m
, מוסיפים את שורת הייבוא הבאה:
#import "mediapipe/objc/MPPCameraInputSource.h"
צריך להוסיף את הקוד הבא לבלוק ההטמעה שלו כדי ליצור אובייקט
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
מוסיפים ל-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;
}
הקוד מפעיל את _cameraSource
ומגדיר את ההגדרה הקבועה מראש של סשן הצילום.
מצלמה כדי להשתמש בה.
אנחנו צריכים להעביר פריימים מ-_cameraSource
לאפליקציה שלנו
ViewController
כדי להציג אותן. MPPCameraInputSource
הוא תת-מחלקה של
MPPInputSource
, שמספק פרוטוקול למעניקי ההרשאה שלו, כלומר
MPPInputSourceDelegate
. לכן הבקשה שלנו, ViewController
, יכולה להיות מיופה כוח
מתוך _cameraSource
.
מעדכנים את הגדרת הממשק של ViewController
בהתאם:
@interface ViewController () <MPPInputSourceDelegate>
כדי לטפל בהגדרת המצלמה ולעבד פריימים נכנסים, צריך להשתמש בתור
שונה מהתור הראשי. צריך להוסיף את הקוד הבא לקטע ההטמעה של
ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
ב-viewDidLoad()
, צריך להוסיף את השורה הבאה אחרי אתחול של
אובייקט _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
וצריך להוסיף את הקוד הבא כדי לאתחל את התור לפני הגדרת
אובייקט _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);
נשתמש בתור סידורי עם עדיפות QOS_CLASS_USER_INTERACTIVE
עבור
ומעבדות את מסגרות המצלמה.
צריך להוסיף את השורה הבאה אחרי ייבוא הכותרת בחלק העליון של הקובץ, לפני
הממשק/ההטמעה של ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
לפני שמטמיעים שיטה כלשהי מפרוטוקול MPPInputSourceDelegate
, צריך
להגדיר תחילה דרך להציג את מסגרות המצלמה. פלטפורמת Mediapipe מספקת
כלי שירות אחר שנקרא MPPLayerRenderer
להצגת תמונות על המסך. הזה
יכול לשמש להצגת אובייקטים מסוג CVPixelBufferRef
,
תמונות שסופקו על ידי MPPCameraInputSource
למעניקי ההרשאות שלו.
ב-ViewController.m
, מוסיפים את שורת הייבוא הבאה:
#import "mediapipe/objc/MPPLayerRenderer.h"
כדי להציג תמונות של המסך, אנחנו צריכים להוסיף אובייקט UIView
חדש שנקרא
_liveView
אל ViewController
.
מוסיפים את השורות הבאות לקטע ההטמעה של ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
עוברים אל Main.storyboard
, מוסיפים אובייקט UIView
מספריית האובייקטים אל
View
מתוך הכיתה ViewController
. הוספת שקע הפניה מהתצוגה הזו אל
את האובייקט _liveView
שהוספתם עכשיו למחלקה ViewController
. לשנות את הגודל של
כך שהיא תהיה במרכז ותכסה את כל מסך האפליקציה.
חוזרים אל ViewController.m
ומוסיפים את הקוד הבא אל viewDidLoad()
כדי
מאתחלים את האובייקט _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
כדי לקבל פריימים מהמצלמה, נטמיע את השיטה הבאה:
// 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);
});
}
זוהי שיטה להענקת גישה של MPPInputSource
. קודם כל אנחנו מוודאים
שהוא מקבל פריימים מהמקור הנכון, כלומר _cameraSource
. לאחר מכן אנחנו מציגים
הפריים שהתקבל מהמצלמה דרך _renderer
בתור הראשי.
עכשיו צריך להפעיל את המצלמה ברגע שהתצוגה להצגת הפריימים
יופיעו בקרוב. כדי לעשות את זה, ניישם
פונקציית viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
לפני שנתחיל להפעיל את המצלמה, אנחנו צריכים לקבל הרשאת גישה מהמשתמש.
MPPCameraInputSource
מספק פונקציה
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
כדי לבקש גישה למצלמה ולבצע פעולות מסוימות כשהמשתמש
הגיבו. מוסיפים ל-viewWillAppear:animated
את הקוד הבא:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
לפני פיתוח האפליקציה, צריך להוסיף את יחסי התלות הבאים ל-BUILD
file:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
עכשיו יש לך אפשרות ליצור ולהפעיל את האפליקציה במכשיר iOS שלך. אתם אמורים לראות שידור חי אחרי אישור ההרשאות למצלמה, בפיד הצפייה במצלמה.
עכשיו אנחנו מוכנים להשתמש בפריימים של המצלמה בתרשים MediaPipe.
שימוש בתרשים MediaPipe ב-iOS
הוספת יחסי תלות רלוונטיים
כבר הוספנו את יחסי התלות של קוד ה-framework של MediaPipe
את ממשק ה-API של iOS כדי להשתמש בתרשים MediaPipe. כדי להשתמש בתרשים MediaPipe, צריך להוסיף
תלויה בתרשים שבו אנחנו מתכוונים להשתמש באפליקציה שלנו. מוסיפים את הטקסט הבא
שורה לרשימה data
בקובץ BUILD
:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
עכשיו מוסיפים את התלות למחשבונים שמשמשים בתרשים הזה בשדה deps
בקובץ BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
לסיום, צריך לשנות את שם הקובץ ViewController.m
ל-ViewController.mm
כדי שיש תמיכה
Objective-C++.
שימוש בתרשים בפונקציה ViewController
ב-ViewController.m
, מוסיפים את שורת הייבוא הבאה:
#import "mediapipe/objc/MPPGraph.h"
להצהיר על קבוע סטטי עם שם הגרף, זרם הקלט זרם הפלט:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
מוסיפים את המאפיין הבא לממשק של ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
כפי שהוסבר בהערה למעלה, נאתחל את הגרף הזה
קודם viewDidLoad
. כדי לעשות זאת, צריך לטעון את התרשים מהקובץ .pbtxt
באמצעות הפונקציה הבאה:
+ (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;
}
אפשר להשתמש בפונקציה הזו כדי לאתחל את הגרף ב-viewDidLoad
באופן הבא:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
הגרף אמור לשלוח את התוצאות של עיבוד פריימים מהמצלמה בחזרה אל
ViewController
צריך להוסיף את השורה הבאה אחרי אתחול התרשים כדי להגדיר
ViewController
כמקבל הגישה לאובייקט mediapipeGraph
:
self.mediapipeGraph.delegate = self;
כדי למנוע תחרות על הזיכרון בזמן עיבוד פריימים מפיד הווידאו בשידור חי, מוסיפים את השורה הבאה:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
עכשיו צריך להתחיל את התרשים אחרי שהמשתמש נתן את ההרשאה להשתמש במצלמה באפליקציה שלנו:
[_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];
});
}
}];
מוקדם יותר, כשקיבלנו פריימים מהמצלמה בprocessVideoFrame
הצגנו אותם ב-_liveView
באמצעות _renderer
. אנחנו
צריך לשלוח את הפריימים האלה לתרשים ולעבד את התוצאות במקום זאת. שינוי
של הפונקציה הזו כדי:
- (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];
}
אנחנו שולחים את imageBuffer
אל self.mediapipeGraph
כחבילה מסוג כלשהו
MPPPacketTypePixelBuffer
לזרם הקלט kInputStream
, כלומר,
"input_video"
הגרף יפעל עם חבילת הקלט הזו והפלט שתתקבל
kOutputStream
, למשל "output_video". אנחנו יכולים ליישם את הגישה הבאה:
לקבלת חבילות בזרם הפלט הזה ולהציג אותן על המסך:
- (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);
});
}
}
מעדכנים את הגדרת הממשק של ViewController
בעזרת MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
וזהו! יוצרים ומפעילים את האפליקציה במכשיר iOS. אתם אמורים לראות תוצאות של הפעלת תרשים זיהוי הקצה בפיד וידאו בשידור חי. מזל טוב!
הערה: הדוגמאות ל-iOS משתמשות עכשיו באפליקציית תבנית נפוצה. הקוד ב-
המדריך הזה משמש באפליקציית התבניות common. האפליקציה helloworld כוללת את
יחסי התלות המתאימים של קובצי BUILD
עבור תרשים זיהוי הקצה.