Pengantar
Halo Dunia! menggunakan Framework MediaPipe untuk mengembangkan aplikasi aplikasi yang menjalankan grafik MediaPipe di iOS.
Yang akan Anda build
Aplikasi kamera sederhana untuk deteksi tepi Sobel secara real-time yang diterapkan ke video live melakukan streaming di perangkat iOS.
Penyiapan
- Instal Framework MediaPipe di sistem Anda, lihat Penginstalan framework untuk mengetahui detailnya.
- Siapkan perangkat iOS Anda untuk pengembangan.
- Siapkan Bazel di sistem Anda 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 yang masuk
yang akan disediakan oleh kamera perangkat Anda.
Node pertama dalam grafik, LuminanceCalculator
, mengambil satu paket (gambar
) dan menerapkan perubahan luminans menggunakan shader OpenGL. Hasil
frame gambar dikirim ke aliran data output luma_video
.
Node kedua, SobelEdgesCalculator
, menerapkan deteksi edge ke peristiwa masuk
paket di aliran data luma_video
dan menghasilkan output berupa output output_video
feed.
Aplikasi iOS kita akan menampilkan frame gambar output dari output_video
feed.
Penyiapan aplikasi minimal awal
Pertama-tama, kami akan memulai dengan aplikasi iOS sederhana dan menunjukkan cara menggunakan bazel
untuk membangunnya.
Pertama, buat proyek XCode melalui File > Baru > Aplikasi Tampilan Tunggal.
Tetapkan nama produk menjadi "HelloWorld", dan gunakan organisasi yang sesuai
ID, seperti com.google.mediapipe
. ID organisasi
bersama dengan nama produknya adalah bundle_id
untuk aplikasi, seperti
com.google.mediapipe.HelloWorld
.
Tetapkan bahasa ke Objective-C.
Simpan project ke lokasi yang tepat. Sebut saja ini
$PROJECT_TEMPLATE_LOC
. Jadi proyek 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 dari
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 build berikut
aturan:
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 AppDelegate
dan
Class ViewController
, main.m
, dan storyboard aplikasi. Tujuan
aplikasi dengan template hanya bergantung pada UIKit
SDK.
Aturan ios_application
menggunakan library Objective-C HelloWorldAppLibrary
dibuat untuk membangun aplikasi iOS
yang akan diinstal di perangkat iOS Anda.
Untuk membangun aplikasi, gunakan perintah berikut di terminal:
bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'
Misalnya, untuk membangun 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 > Perangkat dan Simulator, pilih
perangkat, 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, tampilan harus menampilkan layar putih yang kosong.
Menggunakan kamera untuk feed tayangan live
Dalam tutorial ini, kita akan menggunakan class MPPCameraInputSource
untuk mengakses dan
mengambil bingkai dari kamera. Class ini menggunakan AVCaptureSession
API untuk mendapatkan
{i>frame<i} dari kamera.
Namun, sebelum menggunakan class ini, ubah file Info.plist
untuk mendukung kamera
tingkat penggunaan 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 melakukan inisialisasi _cameraSource
, menetapkan preset sesi pengambilan gambar, dan
kamera untuk digunakan.
Kita perlu memasukkan frame dari _cameraSource
ke dalam aplikasi
ViewController
untuk menampilkannya. MPPCameraInputSource
adalah subclass dari
MPPInputSource
, yang menyediakan protokol untuk delegasinya, yaitu
MPPInputSourceDelegate
. Agar aplikasi ViewController
dapat berupa delegasi
dari _cameraSource
.
Perbarui definisi antarmuka ViewController
sesuai dengan:
@interface ViewController () <MPPInputSourceDelegate>
Untuk menangani penyiapan kamera dan memproses frame yang masuk, kita harus menggunakan antrean
berbeda dari 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 melakukan inisialisasi
Objek _cameraSource
:
[_cameraSource setDelegate:self queue:_videoQueue];
Dan 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 bingkai 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 mengimplementasikan metode apa pun dari protokol MPPInputSourceDelegate
, kita harus
pertama-tama mengatur cara
untuk menampilkan {i>frame<i} kamera. Mediapipe Framework menyediakan
utilitas lain bernama MPPLayerRenderer
untuk menampilkan gambar di layar. Ini
utilitas dapat digunakan untuk menampilkan objek CVPixelBufferRef
, yang merupakan jenis
gambar yang diberikan 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
sehingga berada di tengah dan menutupi seluruh layar aplikasi.
Kembali ke ViewController.m
dan tambahkan kode berikut ke viewDidLoad()
untuk
lakukan 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
memeriksa 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 perlu memulai kamera segera setelah
tampilan untuk menampilkan {i>frame<i}
yang akan ditampilkan. 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 tugas saat pengguna memiliki
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 BUILD
Anda
file:
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 tampilan kamera setelah menyetujui izin kamera.
Sekarang kita siap menggunakan frame kamera dalam grafik MediaPipe.
Menggunakan grafik MediaPipe di iOS
Menambahkan dependensi yang relevan
Kami telah menambahkan dependensi
kode kerangka kerja MediaPipe yang berisi
API iOS untuk menggunakan
grafik MediaPipe. Untuk menggunakan grafik MediaPipe, kita perlu menambahkan
dependensi pada grafik yang ingin
kita gunakan dalam aplikasi. Tambahkan hal berikut
baris ke daftar data
dalam file BUILD
Anda:
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
Sekarang tambahkan dependensi ke kalkulator yang digunakan dalam grafik ini di kolom deps
dalam file BUILD
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Terakhir, ganti nama file ViewController.m
menjadi ViewController.mm
untuk mendukung
Objective-C++.
Menggunakan grafik dalam ViewController
Di ViewController.m
, tambahkan baris impor berikut:
#import "mediapipe/objc/MPPGraph.h"
Deklarasikan konstanta statis dengan nama grafik, aliran input, dan stream 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
viewDidLoad
terlebih dahulu. Untuk melakukannya, kita perlu 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
sebagai berikut:
self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];
Grafik ini harus mengirimkan hasil pemrosesan {i>frame<i} kamera kembali ke
ViewController
. Tambahkan baris berikut setelah menginisialisasi grafik untuk mengatur
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 setelah pengguna 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 kita menerima frame dari kamera di processVideoFrame
, kita menampilkannya di _liveView
menggunakan _renderer
. Sekarang, kita telah
perlu mengirim {i>frame<i} tersebut ke grafik dan
merender hasilnya sebagai gantinya. {i>Modify<i}
implementasi 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];
}
Kami mengirim imageBuffer
ke self.mediapipeGraph
sebagai paket jenis
MPPPacketTypePixelBuffer
ke dalam aliran input kInputStream
, yaitu
"input_video".
Grafik akan dijalankan dengan paket
input ini dan {i>output<i} sebagai hasil
kOutputStream
, yaitu "output_video". Kita dapat mengimplementasikan delegasi berikut
untuk menerima paket pada aliran 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);
});
}
}
Update definisi antarmuka ViewController
dengan MPPGraphDelegate
:
@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>
Itu saja! Bangun dan jalankan aplikasi di perangkat iOS Anda. Anda akan melihat hasil dari menjalankan grafik deteksi tepi pada feed video live. Selamat!
Perhatikan bahwa contoh iOS kini menggunakan aplikasi template umum. Kode di
tutorial ini digunakan dalam aplikasi template umum. Aplikasi helloworld memiliki
dependensi file BUILD
yang sesuai untuk grafik deteksi tepi.