تقسیم‌بندی سلفی با کیت ML در iOS

کیت ML یک SDK بهینه شده برای قطعه‌بندی سلفی ارائه می‌دهد. فایل‌های قطعه‌بندی سلفی در زمان ساخت به صورت ایستا به برنامه شما متصل می‌شوند. این کار حجم برنامه شما را تا 24 مگابایت افزایش می‌دهد و تأخیر API می‌تواند بسته به اندازه تصویر ورودی، همانطور که در آیفون X اندازه‌گیری شده است، از 7 میلی‌ثانیه تا 12 میلی‌ثانیه متغیر باشد.

امتحانش کن.

قبل از اینکه شروع کنی

  1. کتابخانه‌های ML Kit زیر را در Podfile خود قرار دهید:

    pod 'GoogleMLKit/SegmentationSelfie', '8.0.0'
    
  2. پس از نصب یا به‌روزرسانی Pods پروژه خود، پروژه Xcode خود را با استفاده از xcworkspace . باز کنید. کیت ML در Xcode نسخه ۱۳.۲.۱ یا بالاتر پشتیبانی می‌شود.

۱. یک نمونه از Segmenter ایجاد کنید

برای انجام قطعه‌بندی روی یک تصویر سلفی، ابتدا یک نمونه از Segmenter با SelfieSegmenterOptions ایجاد کنید و به صورت اختیاری تنظیمات قطعه‌بندی را مشخص کنید.

گزینه‌های تقسیم‌بندی

حالت سگمنت

Segmenter در دو حالت کار می‌کند. مطمئن شوید که حالتی را انتخاب می‌کنید که با مورد استفاده شما مطابقت دارد.

STREAM_MODE (default)

این حالت برای پخش فریم‌ها از ویدیو یا دوربین طراحی شده است. در این حالت، قطعه‌ساز از نتایج فریم‌های قبلی برای بازگرداندن نتایج قطعه‌بندی روان‌تر استفاده می‌کند.

SINGLE_IMAGE_MODE (default)

این حالت برای تصاویر تکی که به هم مرتبط نیستند طراحی شده است. در این حالت، قطعه‌ساز هر تصویر را به‌طور مستقل و بدون هموارسازی فریم‌ها پردازش می‌کند.

فعال کردن ماسک اندازه خام

از قطعه‌ساز می‌خواهد که ماسک اندازه خام را که با اندازه خروجی مدل مطابقت دارد، برگرداند.

اندازه ماسک خام (مثلاً ۲۵۶x۲۵۶) معمولاً کوچکتر از اندازه تصویر ورودی است.

بدون مشخص کردن این گزینه، قطعه‌ساز، ماسک خام را برای مطابقت با اندازه تصویر ورودی، تغییر مقیاس می‌دهد. اگر می‌خواهید منطق تغییر مقیاس سفارشی اعمال کنید یا تغییر مقیاس برای مورد استفاده شما لازم نیست، استفاده از این گزینه را در نظر بگیرید.

گزینه‌های تقسیم‌بندی را مشخص کنید:

سویفت

let options = SelfieSegmenterOptions()
options.segmenterMode = .singleImage
options.shouldEnableRawSizeMask = true

هدف-سی

MLKSelfieSegmenterOptions *options = [[MLKSelfieSegmenterOptions alloc] init];
options.segmenterMode = MLKSegmenterModeSingleImage;
options.shouldEnableRawSizeMask = YES;

در نهایت، یک نمونه از Segmenter دریافت کنید. گزینه‌هایی را که مشخص کرده‌اید، به آن بدهید:

سویفت

let segmenter = Segmenter.segmenter(options: options)

هدف-سی

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

۲. تصویر ورودی را آماده کنید

برای تقسیم‌بندی سلفی‌ها، برای هر تصویر یا فریم از ویدیو، مراحل زیر را انجام دهید. اگر حالت پخش جریانی را فعال کرده‌اید، باید اشیاء VisionImage از CMSampleBuffer ایجاد کنید.

با استفاده از UIImage یا CMSampleBuffer یک شیء VisionImage ایجاد کنید.

اگر از UIImage استفاده می‌کنید، مراحل زیر را دنبال کنید:

  • یک شیء VisionImage با UIImage ایجاد کنید. مطمئن شوید که .orientation صحیح را مشخص می‌کنید.

    سویفت

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    هدف-سی

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

اگر از CMSampleBuffer استفاده می‌کنید، مراحل زیر را دنبال کنید:

  • جهت داده‌های تصویر موجود در CMSampleBuffer را مشخص کنید.

    برای بدست آوردن جهت تصویر:

    سویفت

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    هدف-سی

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • با استفاده از شیء و جهت‌گیری CMSampleBuffer ، یک شیء VisionImage ایجاد کنید:

    سویفت

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    هدف-سی

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

۳. تصویر را پردازش کنید

شیء VisionImage را به یکی از متدهای پردازش تصویر Segmenter ارسال کنید. می‌توانید از متد process(image:) ناهمگام یا متد results(in:) همگام استفاده کنید.

برای انجام قطعه‌بندی روی یک تصویر سلفی به صورت همزمان:

سویفت

var mask: [SegmentationMask]
do {
  mask = try segmenter.results(in: image)
} catch let error {
  print("Failed to perform segmentation with error: \(error.localizedDescription).")
  return
}

// Success. Get a segmentation mask here.

هدف-سی

NSError *error;
MLKSegmentationMask *mask =
    [segmenter resultsInImage:image error:&error];
if (error != nil) {
  // Error.
  return;
}

// Success. Get a segmentation mask here.

برای انجام قطعه‌بندی روی یک تصویر سلفی به صورت غیرهمزمان:

سویفت

segmenter.process(image) { mask, error in
  guard error == nil else {
    // Error.
    return
  }
  // Success. Get a segmentation mask here.

هدف-سی

[segmenter processImage:image
             completion:^(MLKSegmentationMask * _Nullable mask,
                          NSError * _Nullable error) {
               if (error != nil) {
                 // Error.
                 return;
               }
               // Success. Get a segmentation mask here.
             }];

۴. ماسک تقسیم‌بندی را دریافت کنید

شما می‌توانید نتیجه‌ی تقسیم‌بندی را به صورت زیر دریافت کنید:

سویفت

let maskWidth = CVPixelBufferGetWidth(mask.buffer)
let maskHeight = CVPixelBufferGetHeight(mask.buffer)

CVPixelBufferLockBaseAddress(mask.buffer, CVPixelBufferLockFlags.readOnly)
let maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer)
var maskAddress =
    CVPixelBufferGetBaseAddress(mask.buffer)!.bindMemory(
        to: Float32.self, capacity: maskBytesPerRow * maskHeight)

for _ in 0...(maskHeight - 1) {
  for col in 0...(maskWidth - 1) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    let foregroundConfidence: Float32 = maskAddress[col]
  }
  maskAddress += maskBytesPerRow / MemoryLayout<Float32>.size
}

هدف-سی

size_t width = CVPixelBufferGetWidth(mask.buffer);
size_t height = CVPixelBufferGetHeight(mask.buffer);

CVPixelBufferLockBaseAddress(mask.buffer, kCVPixelBufferLock_ReadOnly);
size_t maskBytesPerRow = CVPixelBufferGetBytesPerRow(mask.buffer);
float *maskAddress = (float *)CVPixelBufferGetBaseAddress(mask.buffer);

for (int row = 0; row < height; ++row) {
  for (int col = 0; col < width; ++col) {
    // Gets the confidence of the pixel in the mask being in the foreground.
    float foregroundConfidence = maskAddress[col];
  }
  maskAddress += maskBytesPerRow / sizeof(float);
}

برای مشاهده‌ی مثال کاملی از نحوه‌ی استفاده از نتایج تقسیم‌بندی، لطفاً به نمونه‌ی شروع سریع ML Kit مراجعه کنید.

نکاتی برای بهبود عملکرد

کیفیت نتایج شما به کیفیت تصویر ورودی بستگی دارد:

  • برای اینکه ML Kit نتیجه‌ی قطعه‌بندی دقیقی داشته باشد، تصویر باید حداقل ۲۵۶x۲۵۶ پیکسل باشد.
  • اگر در یک برنامه بلادرنگ، تقسیم‌بندی سلفی را انجام می‌دهید، ممکن است بخواهید ابعاد کلی تصاویر ورودی را نیز در نظر بگیرید. تصاویر کوچکتر می‌توانند سریع‌تر پردازش شوند، بنابراین برای کاهش تأخیر، تصاویر را با وضوح پایین‌تر ضبط کنید، اما الزامات وضوح بالا را در نظر داشته باشید و مطمئن شوید که سوژه تا حد امکان تصویر را اشغال می‌کند.
  • فوکوس ضعیف تصویر نیز می‌تواند بر دقت تأثیر بگذارد. اگر نتایج قابل قبولی به دست نیاوردید، از کاربر بخواهید که دوباره تصویر را ثبت کند.

اگر می‌خواهید از قطعه‌بندی در یک برنامه‌ی بلادرنگ استفاده کنید، برای دستیابی به بهترین نرخ فریم، این دستورالعمل‌ها را دنبال کنید:

  • از حالت تقسیم‌بندی stream استفاده کنید.
  • ضبط تصاویر با وضوح پایین‌تر را در نظر بگیرید. با این حال، الزامات ابعاد تصویر این API را نیز در نظر داشته باشید.
  • برای پردازش فریم‌های ویدیویی، از API همزمان results(in:) مربوط به segmenter استفاده کنید. این متد را از تابع captureOutput(_, didOutput:from:) مربوط به AVCaptureVideoDataOutputDelegate فراخوانی کنید تا نتایج از فریم ویدیویی داده شده به صورت همزمان دریافت شوند. مقدار alwaysDiscardsLateVideoFrames مربوط به AVCaptureVideoDataOutput را برای فراخوانی‌های throttle به segmenter، روی true نگه دارید. اگر فریم ویدیویی جدیدی در حین اجرای segmenter در دسترس قرار گیرد، حذف خواهد شد.
  • اگر از خروجی segmenter برای همپوشانی گرافیک روی تصویر ورودی استفاده می‌کنید، ابتدا نتیجه را از ML Kit دریافت کنید، سپس تصویر و همپوشانی را در یک مرحله رندر کنید. با انجام این کار، برای هر فریم ورودی پردازش شده، فقط یک بار روی سطح نمایشگر رندر می‌کنید. برای مثال، به کلاس‌های previewOverlayView و CameraViewController در نمونه شروع سریع ML Kit مراجعه کنید.