Giới thiệu
Đây là Hello World! hướng dẫn sử dụng Khung MediaPipe để phát triển một iOS chạy biểu đồ MediaPipe trên iOS.
Sản phẩm bạn sẽ tạo ra
Ứng dụng máy ảnh đơn giản giúp phát hiện cạnh Sobel theo thời gian thực, áp dụng cho video trực tiếp phát trực tuyến trên thiết bị iOS.
Thiết lập
- Cài đặt Khung MediaPipe trên hệ thống của bạn, xem phần Cài đặt khung để biết thông tin chi tiết.
- Thiết lập thiết bị iOS của bạn để phát triển.
- Thiết lập Bazel trên hệ thống của bạn để xây dựng và triển khai ứng dụng iOS.
Biểu đồ phát hiện cạnh
Chúng ta sẽ sử dụng biểu đồ sau đây, 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"
}
Biểu đồ trực quan được hiển thị dưới đây:
Biểu đồ này có một luồng đầu vào duy nhất tên là input_video
cho tất cả các khung hình sắp tới
do máy ảnh trên thiết bị của bạn cung cấp.
Nút đầu tiên trong biểu đồ, LuminanceCalculator
, nhận một gói duy nhất (hình ảnh
) và áp dụng thay đổi về độ chói bằng chương trình đổ bóng OpenGL. Kết quả
khung hình ảnh này sẽ được gửi đến luồng đầu ra luma_video
.
Nút thứ hai, SobelEdgesCalculator
áp dụng tính năng phát hiện cạnh cho lệnh đến
các gói trong luồng luma_video
và xuất ra kết quả đầu ra output_video
luồng.
Ứng dụng iOS của chúng ta sẽ hiển thị khung hình ảnh đầu ra của output_video
luồng.
Thiết lập ứng dụng tối thiểu ban đầu
Trước tiên, chúng ta bắt đầu với một ứng dụng iOS đơn giản và minh hoạ cách sử dụng bazel
để xây dựng ứng dụng.
Trước tiên, hãy tạo một dự án XCode qua File > Mới > Ứng dụng Chế độ xem đơn.
Đặt tên sản phẩm thành "HelloWorld" và sử dụng một tổ chức phù hợp
mã nhận dạng, chẳng hạn như com.google.mediapipe
. Mã nhận dạng tổ chức
cùng với tên sản phẩm sẽ là bundle_id
cho ứng dụng, chẳng hạn như
com.google.mediapipe.HelloWorld
Đặt ngôn ngữ thành Target-C.
Lưu dự án vào một vị trí thích hợp. Đặt tên là
$PROJECT_TEMPLATE_LOC
. Vì vậy, dự án của bạn sẽ nằm trong
Thư mục $PROJECT_TEMPLATE_LOC/HelloWorld
. Thư mục này sẽ chứa
một thư mục khác có tên HelloWorld
và một tệp HelloWorld.xcodeproj
.
HelloWorld.xcodeproj
sẽ không hữu ích cho hướng dẫn này, vì chúng ta sẽ sử dụng
bazel để tạo ứng dụng iOS. Nội dung của
Thư mục $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
được liệt kê dưới đây:
AppDelegate.h
vàAppDelegate.m
ViewController.h
vàViewController.m
main.m
Info.plist
Main.storyboard
vàLaunch.storyboard
- Thư mục
Assets.xcassets
.
Sao chép các tệp này vào thư mục có tên HelloWorld
vào một vị trí có thể truy cập
mã nguồn Khung MediaPipe. Ví dụ: mã nguồn của
mà chúng tôi sẽ tạo trong hướng dẫn này nằm trong
mediapipe/examples/ios/HelloWorld
. Chúng tôi sẽ gọi đường dẫn này là
$APPLICATION_PATH
trong suốt lớp học lập trình này.
Tạo một tệp BUILD
trong $APPLICATION_PATH
và thêm bản dựng sau đây
quy tắc:
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 = [],
)
Quy tắc objc_library
thêm các phần phụ thuộc cho AppDelegate
và
ViewController
, main.m
và bảng phân cảnh của ứng dụng. Chiến lược phát hành đĩa đơn
ứng dụng theo mẫu chỉ phụ thuộc vào SDK UIKit
.
Quy tắc ios_application
sử dụng thư viện Object-C HelloWorldAppLibrary
được tạo để tạo ứng dụng iOS nhằm cài đặt trên thiết bị iOS của bạn.
Để tạo ứng dụng, hãy sử dụng lệnh sau trong một cửa sổ dòng lệnh:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Ví dụ: để tạo ứng dụng HelloWorldApp
trong
mediapipe/examples/ios/helloworld
, hãy dùng lệnh sau:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Sau đó, quay lại XCode, mở Window > Thiết bị và Trình mô phỏng, hãy chọn
thiết bị rồi thêm tệp .ipa
được tạo bằng lệnh ở trên vào thiết bị của bạn.
Đây là tài liệu về thiết lập và biên dịch ứng dụng khung iOS.
Mở ứng dụng trên thiết bị. Vì trống nên khung nhìn sẽ hiện màn hình trắng trống.
Sử dụng camera cho nguồn cấp dữ liệu chế độ xem trực tiếp
Trong hướng dẫn này, chúng ta sẽ sử dụng lớp MPPCameraInputSource
để truy cập và
lấy khung hình từ máy ảnh. Lớp này sử dụng API AVCaptureSession
để tải
các khung hình từ máy ảnh.
Tuy nhiên, trước khi sử dụng lớp này, hãy thay đổi tệp Info.plist
để hỗ trợ máy ảnh
trong ứng dụng.
Trong ViewController.m
, hãy thêm dòng nhập sau:
#import "mediapipe/objc/MPPCameraInputSource.h"
Thêm đoạn mã sau vào khối triển khai để tạo một đối tượng
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Thêm mã sau vào 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;
}
Mã này khởi chạy _cameraSource
, đặt giá trị đặt trước cho phiên chụp và
camera để sử dụng.
Chúng ta cần đưa các khung hình từ _cameraSource
vào ứng dụng của mình
ViewController
để hiển thị chúng. MPPCameraInputSource
là lớp con của
MPPInputSource
, cung cấp một giao thức cho các đại biểu, cụ thể là
MPPInputSourceDelegate
. Để ứng dụng ViewController
của chúng ta có thể là một đại biểu
trong tổng số _cameraSource
.
Cập nhật định nghĩa giao diện của ViewController
cho phù hợp:
@interface ViewController () <MPPInputSourceDelegate>
Để xử lý việc thiết lập máy ảnh và xử lý các khung hình nhận được, chúng ta nên sử dụng hàng đợi
khác với hàng đợi chính. Thêm đoạn mã sau vào khối triển khai của
ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
Trong viewDidLoad()
, hãy thêm dòng sau sau khi khởi chạy phương thức
Đối tượng _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Và thêm mã sau để khởi chạy hàng đợi trước khi thiết lập
Đối tượng _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);
Chúng ta sẽ dùng hàng đợi nối tiếp có mức độ ưu tiên QOS_CLASS_USER_INTERACTIVE
cho
đang xử lý khung hình camera.
Thêm dòng sau ở đầu tệp vào sau tiêu đề nhập
giao diện/cách triển khai của ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Trước khi triển khai bất kỳ phương thức nào từ giao thức MPPInputSourceDelegate
, chúng ta phải
đầu tiên, thiết lập cách hiển thị khung máy ảnh. Khung Mediapipe cung cấp
một tiện ích khác có tên là MPPLayerRenderer
để hiển thị hình ảnh trên màn hình. Chiến dịch này
phần mềm tiện ích có thể dùng để hiển thị đối tượng CVPixelBufferRef
, đây là loại
hình ảnh do MPPCameraInputSource
cung cấp cho người được uỷ quyền.
Trong ViewController.m
, hãy thêm dòng nhập sau:
#import "mediapipe/objc/MPPLayerRenderer.h"
Để hiện hình ảnh màn hình, chúng ta cần thêm một đối tượng UIView
mới có tên là
_liveView
đến ViewController
.
Thêm các dòng sau vào khối triển khai của ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Chuyển đến Main.storyboard
, thêm đối tượng UIView
từ thư viện đối tượng vào
View
của lớp ViewController
. Thêm đầu ra tham chiếu từ thành phần hiển thị này vào
đối tượng _liveView
mà bạn vừa thêm vào lớp ViewController
. Đổi kích thước của
sao cho nó được căn giữa và bao phủ toàn bộ màn hình ứng dụng.
Quay lại ViewController.m
và thêm mã sau vào viewDidLoad()
để
khởi tạo đối tượng _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Để lấy khung hình từ máy ảnh, chúng ta sẽ triển khai phương thức sau:
// 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);
});
}
Đây là phương thức uỷ quyền của MPPInputSource
. Trước tiên, chúng tôi kiểm tra xem mình
lấy khung hình từ nguồn phù hợp, tức là _cameraSource
. Sau đó, chúng tôi hiển thị
khung hình nhận được từ máy ảnh qua _renderer
trên hàng đợi chính.
Bây giờ, chúng ta cần khởi động máy ảnh ngay khi chế độ xem để hiển thị các khung hình
sắp xuất hiện. Để làm được điều này, chúng tôi sẽ triển khai
Hàm viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Trước khi bắt đầu chạy camera, chúng ta cần được người dùng cho phép truy cập vào camera.
MPPCameraInputSource
cung cấp một hàm
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
để yêu cầu quyền truy cập vào máy ảnh và thực hiện một số thao tác khi người dùng có
đã phản hồi. Thêm mã sau vào viewWillAppear:animated
:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Trước khi tạo ứng dụng, hãy thêm các phần phụ thuộc sau vào BUILD
tệp:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Bây giờ, hãy tạo và chạy ứng dụng trên thiết bị iOS của bạn. Bạn sẽ thấy một URL trực tiếp nguồn cấp dữ liệu chế độ xem camera sau khi chấp nhận quyền truy cập vào camera.
Chúng ta hiện đã sẵn sàng sử dụng khung máy ảnh trong biểu đồ MediaPipe.
Sử dụng biểu đồ MediaPipe trong iOS
Thêm phần phụ thuộc có liên quan
Chúng tôi đã thêm các phần phụ thuộc của mã khung MediaPipe có chứa
API iOS để sử dụng biểu đồ MediaPipe. Để sử dụng biểu đồ MediaPipe, chúng ta cần thêm
trên biểu đồ mà chúng tôi dự định sử dụng trong ứng dụng của mình. Thêm đoạn mã sau
dòng vào danh sách data
trong tệp BUILD
của bạn:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Bây giờ, hãy thêm phần phụ thuộc vào các máy tính dùng trong biểu đồ này trong trường deps
trong tệp BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Cuối cùng, hãy đổi tên tệp ViewController.m
thành ViewController.mm
để được hỗ trợ
Mục tiêu C++.
Sử dụng biểu đồ trong ViewController
Trong ViewController.m
, hãy thêm dòng nhập sau:
#import "mediapipe/objc/MPPGraph.h"
Khai báo hằng số tĩnh bằng tên của biểu đồ, luồng đầu vào và luồng đầu ra:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Thêm thuộc tính sau vào giao diện của ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Như được giải thích trong nhận xét ở trên, chúng tôi sẽ khởi tạo đồ thị này theo
viewDidLoad
đầu tiên. Để thực hiện việc này, chúng ta cần tải biểu đồ từ tệp .pbtxt
bằng cách sử dụng hàm sau:
+ (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;
}
Sử dụng hàm này để khởi tạo biểu đồ trong viewDidLoad
như sau:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Biểu đồ sẽ gửi kết quả xử lý khung máy ảnh trở lại
ViewController
. Thêm dòng sau đây sau khi khởi tạo biểu đồ để đặt giá trị
ViewController
làm đối tượng uỷ quyền của đối tượng mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Để tránh tình trạng tranh chấp bộ nhớ trong khi xử lý các khung hình từ nguồn cấp dữ liệu video trực tiếp, hãy thêm dòng sau:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Bây giờ, hãy bắt đầu biểu đồ khi người dùng cấp quyền sử dụng máy ảnh trong ứng dụng của chúng tôi:
[_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];
});
}
}];
Trước đó, khi chúng tôi nhận được khung hình từ máy ảnh trong processVideoFrame
chúng ta đã hiển thị chúng trong _liveView
bằng _renderer
. Bây giờ, chúng tôi
cần gửi các khung đó đến biểu đồ và hiển thị kết quả thay thế. Sửa đổi
phương thức triển khai hàm này để làm những việc sau:
- (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];
}
Chúng ta gửi imageBuffer
đến self.mediapipeGraph
dưới dạng một gói tin thuộc loại này
MPPPacketTypePixelBuffer
vào luồng đầu vào kInputStream
, tức là
"input_video".
Biểu đồ sẽ chạy với gói đầu vào này và xuất kết quả trong
kOutputStream
, tức là "output_video". Chúng ta có thể triển khai thực thể đại diện sau
để nhận các gói trên luồng đầu ra này và hiển thị chúng trên màn hình:
- (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);
});
}
}
Cập nhật định nghĩa giao diện của ViewController
bằng MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Chỉ vậy thôi! Tạo bản dựng và chạy ứng dụng trên thiết bị iOS của bạn. Bạn sẽ thấy kết quả chạy biểu đồ phát hiện cạnh trên nguồn cấp dữ liệu video trực tiếp. Xin chúc mừng!
Xin lưu ý rằng các ví dụ về iOS hiện sử dụng ứng dụng mẫu phổ biến. Mã trong
hướng dẫn này được dùng trong ứng dụng mẫu phổ biến. Ứng dụng helloworld có
phần phụ thuộc tệp BUILD
thích hợp cho biểu đồ phát hiện cạnh.