iOS에서 ML Kit를 사용한 셀카 세분화

ML Kit는 셀카 분할에 최적화된 SDK를 제공합니다. 셀카 분할기 애셋은 빌드 시 앱에 정적으로 연결됩니다. 이렇게 하면 앱 크기가 최대 24MB 증가하며 API 지연 시간은 입력 이미지 크기에 따라 iPhone X에서 측정한 대로 최대 7~12밀리초가 될 수 있습니다.

사용해 보기

  • 샘플 앱을 살펴보고 이 API의 사용 예를 확인합니다.

시작하기 전에

  1. Podfile에 다음 ML Kit 라이브러리를 포함합니다.

    pod 'GoogleMLKit/SegmentationSelfie', '3.2.0'
    
  2. 프로젝트의 포드를 설치하거나 업데이트한 후 .xcworkspace을 사용하여 Xcode 프로젝트를 엽니다. ML Kit는 Xcode 버전 13.2.1 이상에서 지원됩니다.

1. Segmenter 인스턴스 만들기

셀카 이미지에 대해 분할을 실행하려면 먼저 SelfieSegmenterOptionsSegmenter의 인스턴스를 만들고 원하는 경우 분할 설정을 지정합니다.

세그멘터 옵션

세그먼테이터 모드

Segmenter는 두 가지 모드로 작동합니다. 사용 사례에 맞는 이름을 선택해야 합니다.

STREAM_MODE (default)

이 모드는 동영상 또는 카메라의 프레임을 스트리밍하기 위해 설계되었습니다. 이 모드에서 세그먼테이터는 이전 프레임의 결과를 활용하여 보다 원활한 세분화 결과를 반환합니다.

SINGLE_IMAGE_MODE (default)

이 모드는 관련이 없는 단일 이미지를 위해 설계되었습니다. 이 모드에서 분할기는 각 이미지를 독립적으로 처리하며, 프레임 위로 부드럽게 처리하지 않습니다.

원본 크기 마스크 사용 설정

세그먼테이터에 모델 출력 크기와 일치하는 원시 크기 마스크를 반환하도록 요청합니다.

원시 마스크 크기 (예: 256x256)는 일반적으로 입력 이미지 크기보다 작습니다.

이 옵션을 지정하지 않으면 세그먼트러가 입력 이미지 크기와 일치하도록 원시 마스크의 배율을 다시 조정합니다. 맞춤설정된 크기 조정 로직을 적용하고 싶거나 사용 사례에 크기 조정이 필요하지 않은 경우 이 옵션을 사용하는 것이 좋습니다.

분류기 옵션을 지정합니다.

Swift

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

Objective-C

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

마지막으로 Segmenter의 인스턴스를 가져옵니다. 지정한 옵션을 전달합니다.

Swift

let segmenter = Segmenter.segmenter(options: options)

Objective-C

MLKSegmenter *segmenter = [MLKSegmenter segmenterWithOptions:options];

2. 입력 이미지 준비

셀카를 분할하려면 각 이미지 또는 동영상 프레임에 대해 다음 단계를 따르세요. 스트림 모드를 사용 설정한 경우 CMSampleBuffer에서 VisionImage 객체를 만들어야 합니다.

UIImage 또는 CMSampleBuffer를 사용하여 VisionImage 객체를 만듭니다.

UIImage를 사용하는 경우 다음 단계를 따르세요.

  • UIImage를 사용하여 VisionImage 객체를 만듭니다. 올바른 .orientation를 지정해야 합니다.

    Swift

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

    Objective-C

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

CMSampleBuffer를 사용하는 경우 다음 단계를 따르세요.

  • CMSampleBuffer에 포함된 이미지 데이터의 방향을 지정합니다.

    이미지 방향을 가져오려면 다음 단계를 따르세요.

    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;
      }
    }
          
  • CMSampleBuffer 객체와 방향을 사용하여 VisionImage 객체를 만듭니다.

    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. 이미지 처리

VisionImage 객체를 Segmenter의 이미지 처리 메서드 중 하나에 전달합니다. 비동기 process(image:) 메서드 또는 동기 results(in:) 메서드를 사용할 수 있습니다.

셀카 이미지에서 분할 작업을 동시에 수행하려면 다음 단계를 따르세요.

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.

셀카 이미지를 비동기식으로 분할하려면 다음 단계를 따르세요.

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. 세분화 마스크 가져오기

다음과 같이 세분화 결과를 얻을 수 있습니다.

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

세분화 결과를 사용하는 방법에 대한 전체 예시는 ML Kit 빠른 시작 샘플을 참조하세요.

실적 개선을 위한 팁

결과의 품질은 입력 이미지의 품질에 따라 달라집니다.

  • ML Kit에서 정확한 세분화 결과를 얻으려면 이미지가 256x256픽셀 이상이어야 합니다.
  • 실시간 애플리케이션에서 셀카 분할을 수행하는 경우 입력 이미지의 전체 크기도 고려해야 합니다. 이미지 크기가 작을수록 더 빠르게 처리될 수 있으므로 지연 시간을 줄이려면 더 낮은 해상도에서 이미지를 캡처하세요. 하지만 위의 해상도 요구사항을 염두에 두고 피사체가 이미지의 최대한 많은 부분을 차지하도록 하세요.
  • 이미지 초점이 잘 맞지 않으면 정확도에 영향을 줄 수 있습니다. 허용 가능한 수준의 결과를 얻지 못하면 사용자에게 이미지를 다시 캡처하도록 요청합니다.

실시간 애플리케이션에서 세분화를 사용하려는 경우 최상의 프레임 속도를 얻으려면 다음 가이드라인을 따르세요.

  • stream 분류기 모드를 사용합니다.
  • 낮은 해상도에서 이미지를 캡처하는 것이 좋습니다. 단, 이 API의 이미지 크기 요구사항도 유의해야 합니다.
  • 동영상 프레임을 처리하려면 세그먼테이션의 results(in:) 동기 API를 사용합니다. AVCaptureVideoDataOutputSampleBufferDelegatecaptureOutput(_, didOutput:from:) 함수에서 이 메서드를 호출하여 지정된 동영상 프레임에서 결과를 동기식으로 가져옵니다. 세그먼트러 호출을 제한하려면 AVCaptureVideoDataOutputalwaysDiscardsLateVideoFrames를 true로 유지합니다. 세그먼테이터가 실행 중일 때 새 동영상 프레임을 사용할 수 있게 되면 삭제됩니다.
  • 세그먼테이터의 출력을 사용하여 입력 이미지에서 그래픽을 오버레이하는 경우 먼저 ML Kit에서 결과를 가져온 후 이미지를 렌더링하고 단일 단계로 오버레이합니다. 이렇게 하면 처리된 입력 프레임마다 한 번만 디스플레이 표면에 렌더링됩니다. 예시는 ML Kit 빠른 시작 샘플previewOverlayViewCameraViewController 클래스를 참조하세요.