เกริ่นนำ
บทแนะนำ Hello World! นี้ใช้ MediaPipe Framework ในการพัฒนาแอปพลิเคชัน 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
โหนดที่ 2 SobelEdgesCalculator
จะใช้การตรวจจับ Edge กับแพ็กเก็ตขาเข้าในสตรีม luma_video
และเอาต์พุตจะทำให้มีสตรีมเอาต์พุต output_video
รายการ
แอปพลิเคชัน iOS จะแสดงเฟรมรูปภาพเอาต์พุตของสตรีม output_video
การตั้งค่าแอปพลิเคชันขั้นต่ำเริ่มต้น
เราเริ่มต้นด้วยแอปพลิเคชัน iOS ง่ายๆ ก่อนและสาธิตวิธีใช้ bazel
เพื่อสร้าง
ก่อนอื่นให้สร้างโปรเจ็กต์ XCode ผ่าน File > New > Single View App
ตั้งชื่อผลิตภัณฑ์เป็น " 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
และเพิ่มกฎบิลด์ต่อไปนี้
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
เพิ่มทรัพยากร Dependency สำหรับคลาส 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
เปิดแอปพลิเคชันบนอุปกรณ์ เนื่องจากเป็นพื้นที่ว่าง จึงควรแสดงหน้าจอ ว่างเปล่าสีขาว
ใช้กล้องสําหรับฟีด Live View
ในบทแนะนำนี้ เราจะใช้คลาส MPPCameraInputSource
เพื่อเข้าถึงและจับเฟรมจากกล้อง คลาสนี้ใช้ AVCaptureSession
API เพื่อรับเฟรมจากกล้อง
แต่ก่อนที่จะใช้คลาสนี้ ให้เปลี่ยนไฟล์ 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 Framework มียูทิลิตีอีกชื่อหนึ่งชื่อว่า 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];
});
}
}];
ก่อนสร้างแอปพลิเคชัน ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในไฟล์ 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
เพิ่มทรัพยากร Dependency ที่เกี่ยวข้อง
เราได้เพิ่มทรัพยากร Dependency ของโค้ดเฟรมเวิร์ก MediaPipe ซึ่งมี iOS API แล้วเพื่อใช้กราฟ 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 โค้ดในบทแนะนำนี้จะใช้ในแอปเทมเพลตcommon แอป helloworld มีการพึ่งพาไฟล์ BUILD
ที่เหมาะสมสำหรับกราฟการตรวจจับขอบ