مقدمة
مرحبًا بكم برنامج تعليمي حول استخدام MediaPipeframe لتطوير نظام iOS تطبيق يشغّل رسمًا بيانيًا MediaPipe على نظام iOS.
ما الذي ستقوم ببنائه
هو تطبيق كاميرا بسيط لرصد حافة Sobel في الوقت الفعلي على فيديو مباشر. بدء البث على جهاز iOS
ضبط إعدادات الجهاز
- تثبيت إطار عمل MediaPipe على النظام، وراجِع تثبيت إطار العمل. الدليل للاطّلاع على التفاصيل.
- إعداد جهاز 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
مفيدة لهذا البرنامج التعليمي، لأننا سنستخدم
لإنشاء تطبيق 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
إلى موقع يمكنه الوصول إليه
رمز مصدر MediaPipeframe. على سبيل المثال، قد يتضمن رمز المصدر
التطبيق الذي سننشئه في هذا البرنامج التعليمي في
mediapipe/examples/ios/HelloWorld
سنشير إلى هذا المسار على أنه
$APPLICATION_PATH
خلال الدرس التطبيقي حول الترميز
يمكنك إنشاء ملف BUILD
في $APPLICATION_PATH
وإضافة الإصدار التالي.
القواعد:
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.
افتح التطبيق على جهازك. نظرًا لأنها فارغة، يجب أن تعرض شاشة بيضاء فارغة.
استخدام الكاميرا لخلاصة "العرض المباشر"
في هذا البرنامج التعليمي، سنستخدم الفئة MPPCameraInputSource
للوصول إلى
والتقاط الإطارات من الكاميرا. يستخدم هذا الصف واجهة برمجة التطبيقات 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
، ينبغي لنا
عليكم أولاً إعداد طريقة لعرض إطارات الكاميرا. يوفّر إطار عمل Mediapi
أداة أخرى اسمها 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
.
الملف:
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
إضافة التبعيات ذات الصلة
لقد أضفنا بالفعل تبعيات رمز إطار عمل MediaPipe الذي يحتوي على
واجهة برمجة تطبيقات 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
لدعمه.
الهدف-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 تستخدم الآن تطبيق النماذج الشائعة. الكود في
يُستخدم هذا البرنامج التعليمي في تطبيق النموذج الشائع. يحتوي تطبيق helloworld على
تبعيات ملف BUILD
المناسبة للرسم البياني لكشف الحواف.