Pengantar
Tutorial Hello World! ini menggunakan MediaPipe Framework untuk mengembangkan aplikasi iOS yang menjalankan grafik MediaPipe di iOS.
Yang akan Anda bangun
Aplikasi kamera sederhana untuk deteksi tepi Sobel secara real-time yang diterapkan ke streaming video live di perangkat iOS.
Penyiapan
- Instal MediaPipe Framework di sistem Anda, lihat Panduan penginstalan framework untuk mengetahui detailnya.
- Siapkan perangkat iOS Anda untuk pengembangan.
- Siapkan Bazel di sistem untuk membangun dan men-deploy aplikasi iOS.
Grafik untuk deteksi tepi
Kita akan menggunakan grafik berikut, 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"
}
Visualisasi grafik ditampilkan di bawah ini:
Grafik ini memiliki satu aliran input bernama input_video
untuk semua frame masuk
yang akan disediakan oleh kamera perangkat Anda.
Node pertama dalam grafik, LuminanceCalculator
, mengambil satu paket (frame
gambar) dan menerapkan perubahan luminans menggunakan shader OpenGL. Frame gambar
yang dihasilkan dikirim ke aliran output luma_video
.
Node kedua, SobelEdgesCalculator
, menerapkan deteksi tepi ke paket
yang masuk di aliran luma_video
dan menghasilkan output dalam aliran
output output_video
.
Aplikasi iOS kita akan menampilkan frame gambar output dari aliran data output_video
.
Penyiapan aplikasi minimal awal
Pertama-tama, kami akan mulai dengan aplikasi iOS sederhana dan mendemonstrasikan cara menggunakan bazel
untuk membangunnya.
Pertama, buat project XCode melalui File > New > Single View App.
Tetapkan nama produk ke "HelloWorld", dan gunakan ID organisasi
yang sesuai, seperti com.google.mediapipe
. ID organisasi
beserta nama produk adalah bundle_id
untuk aplikasi, seperti
com.google.mediapipe.HelloWorld
.
Setel bahasa ke Objective-C.
Simpan project ke lokasi yang tepat. Sebut saja ini
$PROJECT_TEMPLATE_LOC
. Dengan begitu, project Anda akan berada di
direktori $PROJECT_TEMPLATE_LOC/HelloWorld
. Direktori ini akan berisi
direktori lain bernama HelloWorld
dan file HelloWorld.xcodeproj
.
HelloWorld.xcodeproj
tidak akan berguna untuk tutorial ini, karena kita akan menggunakan bazel untuk membangun aplikasi iOS. Isi direktori
$PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld
tercantum di bawah ini:
AppDelegate.h
danAppDelegate.m
ViewController.h
danViewController.m
main.m
Info.plist
Main.storyboard
danLaunch.storyboard
- Direktori
Assets.xcassets
.
Salin file ini ke direktori bernama HelloWorld
ke lokasi yang dapat mengakses
kode sumber MediaPipe Framework. Misalnya, kode sumber aplikasi yang akan kita bangun dalam tutorial ini terletak di mediapipe/examples/ios/HelloWorld
. Kita akan menyebut jalur ini sebagai $APPLICATION_PATH
di seluruh codelab.
Buat file BUILD
di $APPLICATION_PATH
dan tambahkan aturan build
berikut:
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 = [],
)
Aturan objc_library
menambahkan dependensi untuk class AppDelegate
dan
ViewController
, main.m
, dan storyboard aplikasi. Aplikasi
yang diberi template hanya bergantung pada UIKit
SDK.
Aturan ios_application
menggunakan library Objective-C HelloWorldAppLibrary
yang dibuat untuk mem-build aplikasi iOS untuk diinstal di perangkat iOS Anda.
Untuk mem-build aplikasi, gunakan perintah berikut di terminal:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Misalnya, untuk mem-build aplikasi HelloWorldApp
di
mediapipe/examples/ios/helloworld
, gunakan perintah berikut:
bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp
Kemudian, kembali ke XCode, buka Window > Devices and Simulators, pilih
perangkat Anda, dan tambahkan file .ipa
yang dihasilkan oleh perintah di atas ke perangkat Anda.
Berikut adalah dokumen tentang menyiapkan dan mengompilasi aplikasi Framework iOS.
Buka aplikasi di perangkat Anda. Karena kosong, kode akan menampilkan layar putih kosong.
Menggunakan kamera untuk feed tayangan live
Dalam tutorial ini, kita akan menggunakan class MPPCameraInputSource
untuk mengakses dan
mengambil frame dari kamera. Class ini menggunakan AVCaptureSession
API untuk mendapatkan
frame dari kamera.
Namun sebelum menggunakan class ini, ubah file Info.plist
untuk mendukung penggunaan
kamera di aplikasi.
Di ViewController.m
, tambahkan baris impor berikut:
#import "mediapipe/objc/MPPCameraInputSource.h"
Tambahkan kode berikut ke blok implementasinya untuk membuat objek
_cameraSource
:
@implementation ViewController {
// Handles camera access via AVCaptureSession library.
MPPCameraInputSource* _cameraSource;
}
Tambahkan kode berikut ke 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;
}
Kode ini menginisialisasi _cameraSource
, menetapkan preset sesi pengambilan gambar, dan kamera yang akan digunakan.
Kita perlu memasukkan frame dari _cameraSource
ke ViewController
aplikasi
untuk menampilkannya. MPPCameraInputSource
adalah subclass
MPPInputSource
, yang menyediakan protokol untuk delegasinya, yaitu
MPPInputSourceDelegate
. Jadi, ViewController
aplikasi kita dapat menjadi delegasi
_cameraSource
.
Perbarui definisi antarmuka ViewController
sebagaimana mestinya:
@interface ViewController () <MPPInputSourceDelegate>
Untuk menangani penyiapan kamera dan memproses frame yang masuk, kita harus menggunakan antrean
yang berbeda dengan antrean utama. Tambahkan kode berikut ke blok implementasi
ViewController
:
// Process camera frames on this queue.
dispatch_queue_t _videoQueue;
Di viewDidLoad()
, tambahkan baris berikut setelah menginisialisasi objek _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Lalu, tambahkan kode berikut untuk menginisialisasi antrean sebelum menyiapkan objek _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);
Kita akan menggunakan antrean serial dengan prioritas QOS_CLASS_USER_INTERACTIVE
untuk
memproses frame kamera.
Tambahkan baris berikut setelah header diimpor di bagian atas file, sebelum
antarmuka/implementasi ViewController
:
static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";
Sebelum menerapkan metode apa pun dari protokol MPPInputSourceDelegate
, kita harus
menyiapkan cara untuk menampilkan frame kamera terlebih dahulu. Framework Mediapipe menyediakan
utilitas lain yang disebut MPPLayerRenderer
untuk menampilkan gambar di layar. Utilitas
ini dapat digunakan untuk menampilkan objek CVPixelBufferRef
, yang merupakan jenis
gambar yang disediakan oleh MPPCameraInputSource
kepada delegasinya.
Di ViewController.m
, tambahkan baris impor berikut:
#import "mediapipe/objc/MPPLayerRenderer.h"
Untuk menampilkan gambar layar, kita perlu menambahkan objek UIView
baru yang disebut
_liveView
ke ViewController
.
Tambahkan baris berikut ke blok implementasi ViewController
:
// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;
Buka Main.storyboard
, tambahkan objek UIView
dari library objek ke View
dari class ViewController
. Tambahkan stopkontak referensi dari tampilan ini ke
objek _liveView
yang baru saja Anda tambahkan ke class ViewController
. Ubah ukuran
tampilan agar berada di tengah dan mencakup seluruh layar aplikasi.
Kembali ke ViewController.m
dan tambahkan kode berikut ke viewDidLoad()
untuk
melakukan inisialisasi objek _renderer
:
_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;
Untuk mendapatkan frame dari kamera, kita akan mengimplementasikan metode berikut:
// 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);
});
}
Ini adalah metode delegasi MPPInputSource
. Pertama-tama, kita periksa apakah kita
mendapatkan frame dari sumber yang tepat, yaitu _cameraSource
. Kemudian, kita menampilkan frame yang diterima dari kamera melalui _renderer
pada antrean utama.
Sekarang, kita harus memulai kamera segera setelah tampilan untuk menampilkan frame
akan muncul. Untuk melakukannya, kita akan mengimplementasikan
fungsi viewWillAppear:(BOOL)animated
:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
Sebelum mulai menjalankan kamera, kita memerlukan izin pengguna untuk mengaksesnya.
MPPCameraInputSource
menyediakan fungsi
requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL
granted))handler
untuk meminta akses kamera dan melakukan beberapa pekerjaan saat pengguna
merespons. Tambahkan kode berikut ke viewWillAppear:animated
:
[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
if (granted) {
dispatch_async(_videoQueue, ^{
[_cameraSource start];
});
}
}];
Sebelum mem-build aplikasi, tambahkan dependensi berikut ke file BUILD
Anda:
sdk_frameworks = [
"AVFoundation",
"CoreGraphics",
"CoreMedia",
],
deps = [
"//mediapipe/objc:mediapipe_framework_ios",
"//mediapipe/objc:mediapipe_input_sources_ios",
"//mediapipe/objc:mediapipe_layer_renderer",
],
Sekarang, bangun dan jalankan aplikasi di perangkat iOS Anda. Anda akan melihat feed penayangan kamera live setelah menyetujui izin kamera.
Sekarang kita siap menggunakan bingkai kamera dalam grafik MediaPipe.
Menggunakan grafik MediaPipe di iOS
Menambahkan dependensi yang relevan
Kami telah menambahkan dependensi kode framework MediaPipe yang berisi API iOS untuk menggunakan grafik MediaPipe. Untuk menggunakan grafik MediaPipe, kita perlu menambahkan
dependensi pada grafik yang ingin digunakan dalam aplikasi. Tambahkan baris berikut ke daftar data
di file BUILD
Anda:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Sekarang tambahkan dependensi ke kalkulator yang digunakan dalam grafik ini di kolom deps
di file BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Terakhir, ganti nama file ViewController.m
menjadi ViewController.mm
untuk mendukung Objective-C++.
Gunakan grafik dalam ViewController
Di ViewController.m
, tambahkan baris impor berikut:
#import "mediapipe/objc/MPPGraph.h"
Deklarasikan konstanta statis dengan nama grafik, stream input, dan aliran output:
static NSString* const kGraphName = @"mobile_gpu";
static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";
Tambahkan properti berikut ke antarmuka ViewController
:
// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;
Seperti yang dijelaskan dalam komentar di atas, kita akan melakukan inisialisasi grafik ini dalam viewDidLoad
terlebih dahulu. Untuk melakukannya, kita harus memuat grafik dari file .pbtxt
menggunakan fungsi berikut:
+ (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;
}
Gunakan fungsi ini untuk melakukan inisialisasi grafik di viewDidLoad
seperti berikut:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Grafik akan mengirimkan hasil pemrosesan frame kamera kembali ke
ViewController
. Tambahkan baris berikut setelah menginisialisasi grafik untuk menetapkan ViewController
sebagai delegasi objek mediapipeGraph
:
self.mediapipeGraph.delegate = self;
Untuk menghindari pertentangan memori saat memproses frame dari feed video live, tambahkan baris berikut:
// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;
Sekarang, mulai grafik saat pengguna telah memberikan izin untuk menggunakan kamera di aplikasi kita:
[_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];
});
}
}];
Sebelumnya, saat menerima frame dari kamera dalam fungsi processVideoFrame
, kami menampilkannya di _liveView
menggunakan _renderer
. Sekarang, kita
harus mengirim frame tersebut ke grafik dan merender hasilnya. Ubah penerapan fungsi ini untuk melakukan hal berikut:
- (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];
}
Kita mengirimkan imageBuffer
ke self.mediapipeGraph
sebagai paket jenis
MPPPacketTypePixelBuffer
ke dalam aliran input kInputStream
, yaitu
"input_video".
Grafik akan berjalan dengan paket input ini dan menampilkan hasil dalam
kOutputStream
, yaitu "output_video". Kita dapat mengimplementasikan metode delegasi berikut untuk menerima paket pada stream output ini dan menampilkannya di layar:
- (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);
});
}
}
Perbarui definisi antarmuka ViewController
dengan MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Dan itu saja! Buat dan jalankan aplikasi di perangkat iOS Anda. Anda akan melihat hasil menjalankan grafik deteksi tepi pada feed video live. Selamat!
Perhatikan bahwa contoh iOS kini menggunakan aplikasi template common. Kode dalam
tutorial ini digunakan di aplikasi template common. Aplikasi helloworld memiliki
dependensi file BUILD
yang sesuai untuk grafik deteksi tepi.