บทนำ
สวัสดีโลกนี้ บทแนะนำ ใช้เฟรมเวิร์ก MediaPipe เพื่อพัฒนา 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 > ใหม่ > แอป Single View
ตั้งชื่อผลิตภัณฑ์เป็น " 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 ตัวอย่างเช่น ซอร์สโค้ดของ
ที่เราจะสร้างขึ้นในบทแนะนำนี้
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 ทำให้
ยูทิลิตีอื่นที่ชื่อว่า 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 ใช้แอปเทมเพลตทั่วไป โค้ดใน
บทแนะนำนี้ใช้ในแอปเทมเพลตทั่วไป แอป helloworld มี
ทรัพยากร Dependency ของไฟล์ BUILD
ที่เหมาะสมสำหรับกราฟการตรวจจับขอบ