Phân đoạn ảnh tự chụp chân dung bằng Bộ công cụ học máy trên iOS

Bộ công cụ học máy cung cấp SDK được tối ưu hoá cho phân đoạn ảnh chân dung tự chụp. Thành phần của Trình phân đoạn ảnh chân dung tự chụp được liên kết theo phương thức tĩnh với ứng dụng của bạn tại thời điểm xây dựng. Thao tác này sẽ làm tăng kích thước ứng dụng thêm tối đa 24 MB và độ trễ của API có thể thay đổi từ ~ 7 mili giây đến ~ 12 mili giây tuỳ thuộc vào kích thước hình ảnh đầu vào, được đo trên iPhone X.

Dùng thử

Trước khi bắt đầu

  1. Thêm các thư viện Bộ công cụ học máy sau đây vào Podfile của bạn:

    pod 'GoogleMLKit/SegmentationSelfie', '3.2.0'
    
  2. Sau khi cài đặt hoặc cập nhật Nhóm của dự án, hãy mở dự án Xcode của bạn bằng cách sử dụng tệp .xcworkspace. Bộ công cụ học máy được hỗ trợ trong phiên bản Xcode 13.2.1 trở lên.

1. Tạo một bản sao của Trình phân đoạn

Để phân đoạn trên một ảnh chân dung tự chụp, trước tiên, hãy tạo một thực thể của Segmenter bằng SelfieSegmenterOptions rồi chỉ định các chế độ cài đặt phân đoạn (không bắt buộc).

Tùy chọn trình phân đoạn

Chế độ phân đoạn

Segmenter hoạt động ở 2 chế độ. Hãy nhớ chọn cách phù hợp với trường hợp sử dụng của bạn.

STREAM_MODE (default)

Chế độ này được thiết kế để truyền trực tuyến khung hình từ video hoặc máy ảnh. Trong chế độ này, trình phân đoạn sẽ tận dụng kết quả từ các khung hình trước đó để trả về kết quả phân đoạn mượt mà hơn.

SINGLE_IMAGE_MODE (default)

Chế độ này được thiết kế cho các hình ảnh đơn lẻ không có liên quan. Trong chế độ này, trình phân đoạn sẽ xử lý từng hình ảnh một cách độc lập, không làm mượt khung hình.

Bật mặt nạ kích thước thô

Yêu cầu trình phân đoạn trả về mặt nạ kích thước thô phù hợp với kích thước đầu ra của mô hình.

Kích thước mặt nạ thô (ví dụ: 256x256) thường nhỏ hơn kích thước hình ảnh đầu vào.

Nếu không chỉ định tùy chọn này, trình phân đoạn sẽ điều chỉnh tỷ lệ mặt nạ thô để phù hợp với kích thước hình ảnh đầu vào. Hãy cân nhắc dùng phương án này nếu bạn không cần áp dụng logic điều chỉnh tỷ lệ hoặc tính toán lại tỷ lệ tuỳ chỉnh cho trường hợp sử dụng của mình.

Chỉ định các tuỳ chọn trình phân đoạn:

Swift

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

Objective-C

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

Cuối cùng, hãy lấy một thực thể của Segmenter. Chuyển các tuỳ chọn bạn đã chỉ định:

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. Chuẩn bị hình ảnh đầu vào

Để tạo phân đoạn cho ảnh chân dung tự chụp, hãy làm như sau cho từng bức ảnh hoặc khung hình của video. Nếu đã bật chế độ phát trực tuyến, bạn phải tạo các đối tượng VisionImage từ CMSampleBuffer giây.

Tạo đối tượng VisionImage bằng UIImage hoặc CMSampleBuffer.

Nếu bạn sử dụng UIImage, hãy làm theo các bước sau:

  • Tạo đối tượng VisionImage bằng UIImage. Hãy nhớ chỉ định đúng .orientation.

    Swift

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

    Objective-C

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

Nếu bạn sử dụng CMSampleBuffer, hãy làm theo các bước sau:

  • Chỉ định hướng của dữ liệu hình ảnh có trong CMSampleBuffer.

    Cách lấy hướng ảnh:

    Swift

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

    Objective-C

    - (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;
      }
    }
          
  • Tạo đối tượng VisionImage bằng cách sử dụng Đối tượng và hướng CMSampleBuffer:

    Swift

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

    Objective-C

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

3. Xử lý hình ảnh

Truyền đối tượng VisionImage đến một trong các phương thức xử lý hình ảnh của Segmenter. Bạn có thể sử dụng phương thức process(image:) không đồng bộ hoặc phương thức results(in:) đồng bộ.

Cách phân đoạn một ảnh chân dung tự chụp một cách đồng bộ:

Swift

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.

Objective-C

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

// Success. Get a segmentation mask here.

Cách phân đoạn trên một ảnh chân dung tự chụp một cách không đồng bộ:

Swift

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

Objective-C

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

4. Lấy mặt nạ phân đoạn

Bạn có thể nhận được kết quả phân đoạn như sau:

Swift

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
}

Objective-C

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

Để có ví dụ đầy đủ về cách sử dụng kết quả phân đoạn, vui lòng xem Mẫu bắt đầu nhanh cho Bộ công cụ học máy.

Mẹo cải thiện hiệu suất

Chất lượng của kết quả tìm kiếm phụ thuộc vào chất lượng của hình ảnh đầu vào:

  • Để Bộ công cụ học máy nhận được kết quả phân đoạn chính xác, hình ảnh phải có kích thước tối thiểu là 256x256 pixel.
  • Nếu thực hiện phân đoạn ảnh chân dung tự chụp trong một ứng dụng theo thời gian thực, bạn cũng nên xem xét kích thước tổng thể của các hình ảnh nhập vào. Hình ảnh nhỏ hơn có thể được xử lý nhanh hơn, vì vậy để giảm độ trễ, hãy chụp ảnh ở độ phân giải thấp hơn. Tuy nhiên, hãy lưu ý các yêu cầu về độ phân giải ở trên và đảm bảo rằng đối tượng chiếm nhiều hình ảnh nhất có thể.
  • Tiêu điểm ảnh kém cũng có thể ảnh hưởng đến độ chính xác. Nếu bạn không nhận được kết quả chấp nhận được, hãy yêu cầu người dùng chụp lại hình ảnh.

Nếu bạn muốn sử dụng tính năng phân đoạn trong ứng dụng theo thời gian thực, hãy làm theo các nguyên tắc sau để đạt được tốc độ khung hình tốt nhất:

  • Sử dụng chế độ phân đoạn stream.
  • Hãy cân nhắc chụp ảnh ở độ phân giải thấp hơn. Tuy nhiên, bạn cũng cần lưu ý các yêu cầu về kích thước hình ảnh của API này.
  • Để xử lý khung hình video, hãy sử dụng API đồng bộ results(in:) của trình phân đoạn. Hãy gọi phương thức này từ hàm captureOutput(_, didOutput:from:) của AVCaptureVideoDataOutputSampleBufferDelegate để nhận kết quả một cách đồng bộ từ khung video đã cho. Giữ nguyên giá trị thực của alwaysDiscardsLateVideoFrames của AVCaptureVideoDataOutput để điều tiết các lệnh gọi đến trình phân đoạn. Nếu có một khung hình video mới trong khi trình phân đoạn đang chạy, thì khung hình đó sẽ bị loại bỏ.
  • Nếu bạn sử dụng dữ liệu đầu ra của trình phân đoạn để phủ đồ hoạ lên hình ảnh đầu vào, trước tiên, hãy lấy kết quả từ Bộ công cụ học máy, sau đó kết xuất hình ảnh và lớp phủ chỉ trong một bước duy nhất. Bằng cách này, bạn chỉ kết xuất lên bề mặt hiển thị một lần cho mỗi khung đầu vào đã xử lý. Hãy xem các lớp previewOverlayViewCameraViewController trong mẫu bắt đầu nhanh của Bộ công cụ học máy để xem ví dụ.