iOS'te Hello World!

Giriş

Bu Hello World! eğiticisi, iOS'te MediaPipe grafiği çalıştıran bir iOS uygulaması geliştirmek için MediaPipe Framework'ü kullanır.

Ne oluşturacaksınız?

iOS cihazdaki canlı video akışına gerçek zamanlı Sobel kenar algılama özelliği sağlayan basit bir kamera uygulamasıdır.

edge_detection_ios_gpu_gif

Kurulum

  1. Sisteminize MediaPipe Framework'ü yükleyin. Ayrıntılar için Çerçeve kurulum kılavuzuna bakın.
  2. iOS cihazınızı geliştirme için ayarlayın.
  3. iOS uygulamasını derlemek ve dağıtmak için sisteminizde Bazel'i kurun.

Kenar algılama grafiği

Kullanılacak grafik 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"
}

Aşağıda grafikle ilgili bir görsel yer almaktadır:

edge_detection_mobile_gpu

Bu grafikte, cihazınızın kamerası tarafından sağlanacak tüm gelen kareler için input_video adlı tek bir giriş akışı vardır.

Grafikteki ilk düğüm (LuminanceCalculator), tek bir paket (görüntü çerçevesi) alır ve OpenGL gölgelendirici kullanarak parlaklık değişikliği uygular. Elde edilen resim çerçevesi, luma_video çıkış akışına gönderilir.

İkinci düğüm olan SobelEdgesCalculator, luma_video akışındaki gelen paketlere uç algılama uygular ve çıkışta output_video çıkış akışı oluşturur.

iOS uygulamamız, output_video akışının çıktı resim çerçevelerini gösterir.

Minimum minimum uygulama kurulumu

İlk olarak basit bir iOS uygulamasıyla başlıyor ve bu uygulamayı oluşturmak için bazel hizmetinin nasıl kullanıldığını gösteriyoruz.

İlk olarak, Dosya > Yeni > Tek Görünüm Uygulaması yoluyla bir XCode projesi oluşturun.

Ürün adını "HelloWorld" olarak ayarlayın ve com.google.mediapipe gibi uygun bir kuruluş tanımlayıcısı kullanın. Kuruluş tanımlayıcısı, ürün adıyla birlikte uygulamanın bundle_id öğesi olacaktır (ör. com.google.mediapipe.HelloWorld).

Dili Objective-C olarak ayarlayın.

Projeyi uygun bir konuma kaydedin. Buna $PROJECT_TEMPLATE_LOC adını verelim. Bu durumda projeniz $PROJECT_TEMPLATE_LOC/HelloWorld dizininde olur. Bu dizin, HelloWorld adlı başka bir dizin ve bir HelloWorld.xcodeproj dosyası içerecek.

iOS uygulamasını oluştururken Bazel kullanacağımızdan, HelloWorld.xcodeproj bu eğiticide faydalı olmayacaktır. $PROJECT_TEMPLATE_LOC/HelloWorld/HelloWorld dizininin içeriği aşağıda listelenmiştir:

  1. AppDelegate.h ve AppDelegate.m
  2. ViewController.h ve ViewController.m
  3. main.m
  4. Info.plist
  5. Main.storyboard ve Launch.storyboard
  6. Assets.xcassets dizini.

Bu dosyaları HelloWorld adlı bir dizine, MediaPipe Çerçevesi kaynak koduna erişebilen bir konuma kopyalayın. Örneğin, bu eğiticide oluşturacağımız uygulamanın kaynak kodu mediapipe/examples/ios/HelloWorld konumundadır. Codelab genelinde bu yol $APPLICATION_PATH olarak adlandırılacaktır.

$APPLICATION_PATH içinde bir BUILD dosyası oluşturun ve aşağıdaki derleme kurallarını ekleyin:

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 kuralı; AppDelegate ve ViewController sınıfları, main.m ve uygulama resimli taslakları için bağımlılık ekler. Şablonlu uygulama yalnızca UIKit SDK'sına bağlıdır.

ios_application kuralı, iOS cihazınıza yüklenmek üzere bir iOS uygulaması oluşturmak için oluşturulan HelloWorldAppLibrary Objective-C kitaplığını kullanır.

Uygulamayı derlemek için bir terminalde aşağıdaki komutu kullanın:

bazel build -c opt --config=ios_arm64 <$APPLICATION_PATH>:HelloWorldApp'

Örneğin, mediapipe/examples/ios/helloworld içinde HelloWorldApp uygulamasını derlemek için aşağıdaki komutu kullanın:

bazel build -c opt --config=ios_arm64 mediapipe/examples/ios/helloworld:HelloWorldApp

Ardından XCode'a geri dönün, Pencere > Cihazlar ve Simülatörler'i açın, cihazınızı seçin ve yukarıdaki komut tarafından oluşturulan .ipa dosyasını cihazınıza ekleyin. iOS Framework uygulamalarını kurma ve derleme ile ilgili dokümanı burada bulabilirsiniz.

Cihazınızda uygulamayı açın. Boş olduğu için boş bir beyaz ekran göstermesi gerekir.

Canlı görüntüleme feed'i için kamerayı kullanma

Bu eğiticide, kameradan karelere erişmek ve kareleri yakalamak için MPPCameraInputSource sınıfını kullanacağız. Bu sınıf, kareleri kameradan almak için AVCaptureSession API'sini kullanır.

Ancak, bu sınıfı kullanmadan önce uygulamada kamera kullanımını desteklemek için Info.plist dosyasını değiştirin.

ViewController.m dosyasına aşağıdaki içe aktarma satırını ekleyin:

#import "mediapipe/objc/MPPCameraInputSource.h"

Bir nesne _cameraSource oluşturmak için uygulama bloğuna aşağıdaki kodu ekleyin:

@implementation ViewController {
  // Handles camera access via AVCaptureSession library.
  MPPCameraInputSource* _cameraSource;
}

Şu kodu viewDidLoad() hesabına ekleyin:

-(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;
}

Kod; _cameraSource özelliğini başlatır, çekim oturumu hazır ayarını ve hangi kameranın kullanılacağını belirler.

Görüntülemek için _cameraSource kaynağından ViewController uygulamamıza kareler almamız gerekir. MPPCameraInputSource, yetki verdikleri kullanıcılar için bir protokol sağlayan MPPInputSourceDelegate adlı MPPInputSource alt sınıfıdır. Yani ViewController uygulamamız, _cameraSource kullanıcısının temsilcisi olabilir.

ViewController arayüz tanımını uygun şekilde güncelleyin:

@interface ViewController () <MPPInputSourceDelegate>

Kamera kurulumunu işlemek ve gelen kareleri işlemek için ana sıradan farklı bir sıra kullanmamız gerekir. ViewController öğesinin uygulama bloğuna aşağıdaki kodu ekleyin:

// Process camera frames on this queue.
dispatch_queue_t _videoQueue;

viewDidLoad() aracında, _cameraSource nesnesini başlattıktan sonra aşağıdaki satırı ekleyin:

[_cameraSource setDelegate:self queue:_videoQueue];

Ayrıca _cameraSource nesnesini ayarlamadan önce sırayı başlatmak için aşağıdaki kodu ekleyin:

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);

Kamera çerçevelerini işlemek için önceliği QOS_CLASS_USER_INTERACTIVE olan bir seri sıra kullanırız.

Dosyanın üst kısmında başlık içe aktarıldıktan sonra, ViewController arayüzü/uygulaması öncesinde aşağıdaki satırı ekleyin:

static const char* kVideoQueueLabel = "com.google.mediapipe.example.videoQueue";

MPPInputSourceDelegate protokolünden herhangi bir yöntem uygulamadan önce kamera karelerini görüntüleyecek bir yöntem belirlememiz gerekir. Mediapipe Framework, görüntüleri ekranda göstermek için MPPLayerRenderer adlı başka bir yardımcı program sunar. Bu yardımcı program, MPPCameraInputSource tarafından yetki verilmiş kullanıcılara sağlanan görüntü türü olan CVPixelBufferRef nesnelerini görüntülemek için kullanılabilir.

ViewController.m dosyasına aşağıdaki içe aktarma satırını ekleyin:

#import "mediapipe/objc/MPPLayerRenderer.h"

Ekranın görüntülerini görüntülemek için ViewController öğesine _liveView adlı yeni bir UIView nesnesi eklememiz gerekir.

ViewController öğesinin uygulama bloğuna aşağıdaki satırları ekleyin:

// Display the camera preview frames.
IBOutlet UIView* _liveView;
// Render frames in a layer.
MPPLayerRenderer* _renderer;

Main.storyboard bölümüne gidin ve nesne kitaplığından ViewController sınıfının View öğesine UIView nesnesi ekleyin. ViewController sınıfına yeni eklediğiniz _liveView nesnesine bu görünümden bir referans prizi ekleyin. Görünümü ortalanıp tüm uygulama ekranını kaplayacak şekilde yeniden boyutlandırın.

_renderer nesnesini başlatmak için ViewController.m öğesine geri dönün ve aşağıdaki kodu viewDidLoad() öğesine ekleyin:

_renderer = [[MPPLayerRenderer alloc] init];
_renderer.layer.frame = _liveView.layer.bounds;
[_liveView.layer addSublayer:_renderer.layer];
_renderer.frameScaleMode = MPPFrameScaleModeFillAndCrop;

Kameradan kareleri almak için aşağıdaki yöntemi uygulayacağız:

// 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);
  });
}

Bu, MPPInputSource için yetki verme yöntemidir. İlk olarak, kareleri doğru kaynaktan, yani _cameraSource'den aldığımızı kontrol ederiz. Ardından, kameradan alınan kareyi _renderer üzerinden ana sırada görüntüleriz.

Şimdi, kareleri görüntülemek üzere görünümün belirmesi için kamerayı hemen başlatmamız gerekiyor. Bunun için viewWillAppear:(BOOL)animated işlevini uygulayacağız:

-(void)viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
}

Kamerayı çalıştırmaya başlamadan önce, kameraya erişmek için kullanıcının iznini almamız gerekir. MPPCameraInputSource, kullanıcı yanıt verdiğinde kameraya erişim izni istemek ve bazı işlemler yapmak için requestCameraAccessWithCompletionHandler:(void (^_Nullable)(BOOL granted))handler işlevi sunar. Şu kodu viewWillAppear:animated hesabına ekleyin:

[_cameraSource requestCameraAccessWithCompletionHandler:^void(BOOL granted) {
  if (granted) {
    dispatch_async(_videoQueue, ^{
      [_cameraSource start];
    });
  }
}];

Uygulamayı derlemeden önce aşağıdaki bağımlılıkları BUILD dosyanıza ekleyin:

sdk_frameworks = [
    "AVFoundation",
    "CoreGraphics",
    "CoreMedia",
],
deps = [
    "//mediapipe/objc:mediapipe_framework_ios",
    "//mediapipe/objc:mediapipe_input_sources_ios",
    "//mediapipe/objc:mediapipe_layer_renderer",
],

Şimdi uygulamayı iOS cihazınızda derleyip çalıştırın. Kamera izinlerini kabul ettikten sonra canlı kamera görüntü feed'i görürsünüz.

Artık kamera çerçevelerini MediaPipe grafiğinde kullanmaya hazırız.

iOS'te MediaPipe grafiği kullanma

Alakalı bağımlılıkları ekleyin

MediaPipe grafiğini kullanmak için iOS API'yi içeren MediaPipe çerçeve kodunun bağımlılıklarını ekledik. Bir MediaPipe grafiği kullanmak için, uygulamamızda kullanmayı amaçladığımız grafiğe bir bağımlılık eklememiz gerekir. Aşağıdaki satırı BUILD dosyanızdaki data listesine ekleyin:

"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",

Şimdi bağımlılığı, bu grafikte kullanılan hesaplayıcılara BUILD dosyasındaki deps alanında ekleyin:

"//mediapipe/graphs/edge_detection:mobile_calculators",

Son olarak, ViewController.m dosyasını Objective-C++ özelliğini desteklemek için ViewController.mm olarak yeniden adlandırın.

Grafiği ViewController aracında kullanın

ViewController.m dosyasına aşağıdaki içe aktarma satırını ekleyin:

#import "mediapipe/objc/MPPGraph.h"

Grafik adı, giriş akışı ve çıkış akışıyla statik bir sabit belirtin:

static NSString* const kGraphName = @"mobile_gpu";

static const char* kInputStream = "input_video";
static const char* kOutputStream = "output_video";

Aşağıdaki özelliği ViewController arayüzüne ekleyin:

// The MediaPipe graph currently in use. Initialized in viewDidLoad, started in viewWillAppear: and
// sent video frames on _videoQueue.
@property(nonatomic) MPPGraph* mediapipeGraph;

Yukarıdaki yorumda açıklandığı gibi önce bu grafiği viewDidLoad ile başlatacağız. Bunun için aşağıdaki işlevi kullanarak grafiği .pbtxt dosyasından yüklememiz gerekir:

+   (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 içindeki grafiği aşağıdaki gibi başlatmak için bu işlevi kullanın:

self.mediapipeGraph = [[self class] loadGraphFromResource:kGraphName];

Grafik, kamera karelerinin işlenmesinin sonuçlarını ViewController hedefine geri gönderecektir. Grafik başlatıldıktan sonra ViewController öğesini mediapipeGraph nesnesinin temsilcisi olarak ayarlamak için aşağıdaki satırı ekleyin:

self.mediapipeGraph.delegate = self;

Canlı video feed'indeki kareleri işlerken bellek çakışmasını önlemek için aşağıdaki satırı ekleyin:

// Set maxFramesInFlight to a small value to avoid memory contention for real-time processing.
self.mediapipeGraph.maxFramesInFlight = 2;

Kullanıcı, uygulamamızda kamerayı kullanma izni verdiğinde grafiği başlatın:

[_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];
    });
  }
}];

Daha önce processVideoFrame işlevinde kameradan kareler aldığımızda bu kareleri _renderer kullanarak _liveView içinde gösteriyorduk. Şimdi, bu kareleri grafiğe göndermemiz ve bunun yerine sonuçları oluşturmamız gerekiyor. Bu işlevin uygulamasında değişiklik yaparak aşağıdakileri yapabilirsiniz:

-   (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 öğesini self.mediapipeGraph alanına, kInputStream giriş akışına MPPPacketTypePixelBuffer türünde bir paket olarak göndeririz (ör. "input_video").

Grafik, bu giriş paketiyle çalışacak ve kOutputStream sonucunu (yani "output_video") verir. Bu çıkış akışında paket almak ve bunları ekranda görüntülemek için aşağıdaki yetki verme yöntemini uygulayabiliriz:

-   (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 arayüz tanımını MPPGraphDelegate ile güncelleyin:

@interface ViewController () <MPPGraphDelegate, MPPInputSourceDelegate>

Hepsi bu kadar! Uygulamayı iOS cihazınızda derleyip çalıştırın. Canlı video feed'inde kenar algılama grafiğini çalıştırmanın sonuçlarını görürsünüz. Tebrikler!

edge_detection_ios_gpu_gif

iOS örneklerinde artık common bir şablon uygulaması kullanıldığını lütfen unutmayın. Bu eğitimdeki kod, common şablon uygulamasında kullanılır. helloworld uygulaması, kenar algılama grafiği için uygun BUILD dosyası bağımlılıklarına sahiptir.