适用于 iOS 的图像分割指南

通过 Image Segmenter 任务,您可以根据预定义的 类别,以及应用背景模糊等视觉效果。这些 说明了如何在 iOS 应用中使用图像分割器。

您可在 GitHub

您可以通过查看网页 演示。 如需详细了解功能、模型和配置选项 请参阅 概览

代码示例

MediaPipe Tasks 代码示例包含 iOS 版 Image Segmenter 应用。

该示例实现了可输出类别遮罩的图片分割器。它使用 将相机在 iOS 实体设备上进行实时图像分割 或设备图库中的图片和视频

您可以以此为基础来创建自己的 iOS 应用,也可以作为参考 对现有应用进行了修改。图片分割器示例代码托管在 GitHub

下载代码

以下说明介绍了如何创建示例的本地副本 使用 git 命令行工具运行 git 代码库。

如需下载示例代码,请执行以下操作:

  1. 使用以下命令克隆 git 代码库:

    git clone https://github.com/google-ai-edge/mediapipe-samples/
    
  2. (可选)配置您的 git 实例以使用稀疏检出,这样您 只有 Image Segmenter 示例应用的文件:

    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/image_segmentation/ios/
    

创建示例代码的本地版本后,您可以安装 MediaPipe 任务库,使用 Xcode 打开项目并运行应用。对于 请参阅 iOS 设置指南

关键组件

以下文件包含图像分割器示例的关键代码 应用:

设置

本部分介绍了设置开发环境和 代码项目使用 Image Segmenter。有关如何设置 用于使用 MediaPipe 任务(包括平台版本)的开发环境 要求,请参阅 iOS 设置指南

依赖项

Image Segmenter 使用 MediaPipeTasksVision 库,您必须安装该库 使用 CocoaPods 构建容器。该库与 Swift 和 Objective-C 应用兼容 并且不需要任何额外的语言相关设置。

如需了解如何在 macOS 上安装 CocoaPods,请参阅 CocoaPods 安装指南。 如需了解如何创建包含必要 Pod 的 Podfile,请参阅 请参阅使用 CocoaPods

使用以下代码在 Podfile 中添加 MediaPipeTasksVision pod:

target 'MyImageSegmenterApp' do
  use_frameworks!
  pod 'MediaPipeTasksVision'
end

如果您的应用包含单元测试目标,请参阅应用的设置指南 iOS 设备,详细了解如何设置 您的Podfile

型号

MediaPipe Image Segmenter 任务需要一个与 此任务。如需详细了解适用于 Kubernetes 的 Image Segmenter,请参阅任务概览模型 部分

选择并下载模型,然后使用 Xcode 将其添加到您的项目目录。 有关如何向 Xcode 项目添加文件的说明,请参阅管理 Xcode 中的文件和文件夹 项目

使用 BaseOptions.modelAssetPath 属性指定模型的路径 。如需查看代码示例,请参阅下一部分。

创建任务

您可以通过调用图像分割器任务之一来创建该任务。通过 ImageSegmenter(options:) 初始化程序接受配置的值 选项。

如果您不需要使用自定义配置初始化的图像分割器 可以使用 ImageSegmenter(modelPath:) 初始化程序创建 使用默认选项的图像分割器。如需详细了解配置 选项,请参阅配置概览

Image Segmenter 任务支持 3 种输入数据类型:静态图片、视频文件 和直播视频流默认情况下,ImageSegmenter(modelPath:) 会初始化 执行任务。如果您希望将任务初始化以处理视频 文件或直播视频串流,请使用 ImageSegmenter(options:) 来指定视频 或实时流式传输模式直播模式还要求 imageSegmenterLiveStreamDelegate 配置选项,它启用 图像分割器,用于向受托人传送图像分割结果 异步执行。

选择与运行模式对应的标签页,了解如何创建任务 并进行推理。

Swift

映像

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .image
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

let imageSegmenter = try ImageSegmenter(options: options)
    

视频

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .video
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

let imageSegmenter = try ImageSegmenter(options: options)
    

直播

import MediaPipeTasksVision

// Class that conforms to the `imageSegmenterLiveStreamDelegate` protocol and
// implements the method that the image segmenter calls once it finishes
// performing segmentation of each input frame.
class ImageSegmenterResultProcessor: NSObject, ImageSegmenterLiveStreamDelegate {

  func imageSegmenter(
    _ imageSegmenter: ImageSegmenter,
    didFinishSegmentation result: ImageSegmenterResult?,
    timestampInMilliseconds: Int,
    error: Error?) {

    // Process the image segmentation result or errors here.

  }
}

let modelPath = Bundle.main.path(forResource: "model",
                                      ofType: "tflite")

let options = ImageSegmenterOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .liveStream
options.shouldOutputCategoryMask = true
options.shouldOutputConfidenceMasks = false

// Set `imageSegmenterLiveStreamDelegate` to the object of the class that
// confirms to the `ImageSegmenterLiveStreamDelegate` protocol.
let processor = ImageSegmenterResultProcessor()
options.imageSegmenterLiveStreamDelegate = processor

let imageSegmenter = try ImageSegmenter(options: options)
    

Objective-C

映像

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeImage;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

视频

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeVideo;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

直播

@import MediaPipeTasksVision;

// Class that conforms to the `MPPImageSegmenterLiveStreamDelegate` protocol
// and implements the method that the image segmenter calls once it finishes
// performing segmentation of each input frame.

@interface APPImageSegmenterResultProcessor : NSObject 

@end

@implementation APPImageSegmenterResultProcessor

-   (void)imageSegmenter:(MPPImageSegmenter *)imageSegmenter
    didFinishSegmentationWithResult:(MPPImageSegmenterResult *)imageSegmenterResult
         timestampInMilliseconds:(NSInteger)timestampInMilliseconds
                           error:(NSError *)error {

    // Process the image segmentation result or errors here.

}

@end

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];

MPPImageSegmenterOptions *options = [[MPPImageSegmenterOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeLiveStream;
options.shouldOutputCategoryMask = YES;
options.shouldOutputConfidenceMasks = NO;

// Set `imageSegmenterLiveStreamDelegate` to the object of the class that
// confirms to the `MPPImageSegmenterLiveStreamDelegate` protocol.
APPImageSegmenterResultProcessor *processor =
  [APPImageSegmenterResultProcessor new];
options.imageSegmenterLiveStreamDelegate = processor;

MPPImageSegmenter *imageSegmenter =
  [[MPPImageSegmenter alloc] initWithOptions:options error:nil];
    

图片分割器示例代码实现允许用户在 处理模式这种方法使得任务创建代码更加复杂, 可能不适合您的用例。

配置选项

此任务具有以下适用于 iOS 应用的配置选项:

选项名称 说明 值范围 默认值
runningMode 设置任务的运行模式。有三个 模式:

IMAGE:单图输入的模式。

VIDEO:视频已解码帧的模式。

LIVE_STREAM:输入流媒体直播模式 例如来自相机的数据。 在此模式下,ImageSegmenterLiveStreamDelegate 必须设置为实现 ImageSegmenterLiveStreamDelegate,用于接收细分 异步结果。
{RunningMode.image, RunningMode.video, RunningMode.liveStream} RunningMode.image
shouldOutputCategoryMask 如果设置为 True,则输出将包含细分掩码 表示为 uint8 图片,其中每个像素值表示胜出的类别 值。 {True, False} False
shouldOutputConfidenceMasks 如果设置为 True,则输出将包含细分掩码 表示为浮点值图片,其中每个浮点值表示 该类别的得分图。 {True, False} True
displayNamesLocale 设置要用于 任务模型的元数据(如果有)。默认值为 en, 英语。您可以向自定义模型的元数据中添加本地化标签 使用 TensorFlow Lite Metadata Writer API 语言区域代码 en
result_callback 设置结果监听器以接收细分结果 在图像分割器处于 LIVE_STREAM 模式时异步执行。 仅在跑步模式设为“LIVE_STREAM”时才能使用 不适用 不适用

当运行模式设置为 LIVE_STREAM 时,图像分割器需要 额外的 imageSegmenterLiveStreamDelegate 配置选项 可让图像分割器异步传送图像分割结果。 代理必须实现 imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:) 方法、 由 Image Segmenter 处理生成的结果, 对每一帧进行分割的过程。

选项名称 说明 值范围 默认值
imageSegmenterLiveStreamDelegate 启用图片分割器以接收执行图片处理的结果 在直播模式下异步细分。实例所属的类 必须实现 imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:) 方法。 不适用 未设置

准备数据

您需要先将输入图片或帧转换为 MPImage 对象,然后才能 传递给图像分割器。MPImage 支持不同类型的 iOS 图片 并且可在任何运行模式下使用它们进行推理。有关 有关 MPImage 的信息,请参阅 MPImage API

根据您的使用情形和应用的运行模式选择 iOS 图片格式 MPImage 接受 UIImageCVPixelBufferCMSampleBuffer iOS 图片格式。

UIImage

UIImage 格式非常适合以下运行模式:

  • 图片:来自 app bundle、用户图库或文件系统的图片,格式为 UIImage 图片可转换为 MPImage 对象。

  • 视频:使用 AVAssetImageGenerator 将视频帧提取到 CGImage 格式,然后将其转换为 UIImage 图片。

Swift

// Load an image on the user's device as an iOS `UIImage` object.

// Convert the `UIImage` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(uiImage: image)
    

Objective-C

// Load an image on the user's device as an iOS `UIImage` object.

// Convert the `UIImage` object to a MediaPipe's Image object having the default
// orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
    

本示例使用默认值初始化 MPImageUIImage.Orientation.Up 屏幕方向。您可以使用任何受支持的MPImage UIImage.Orientation 值。Image Segmenter 不支持镜像方向,例如 .upMirrored.downMirrored.leftMirrored.rightMirrored

有关 UIImage 的详细信息,请参阅 UIImage Apple Developer 文档

CVPixelBuffer

CVPixelBuffer 格式非常适合生成帧的应用 并使用 iOS CoreImage 处理框架

CVPixelBuffer 格式非常适合以下运行模式:

  • 图片:应用会在经过一些处理后生成 CVPixelBuffer 图片 使用 iOS 的 CoreImage 框架发送到 Image Segmenter, 映像运行模式

  • 视频:可将视频帧转换为 CVPixelBuffer 格式,以便 处理,然后在视频模式下发送到图像分割器。

  • 直播:使用 iOS 相机生成帧的应用可能会被转换 转换为 CVPixelBuffer 格式进行处理,然后再发送到 实时模式下的图像分割器。

Swift

// Obtain a CVPixelBuffer.

// Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(pixelBuffer: pixelBuffer)
    

Objective-C

// Obtain a CVPixelBuffer.

// Convert the `CVPixelBuffer` object to a MediaPipe's Image object having the
// default orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithUIImage:image error:nil];
    

如需详细了解 CVPixelBuffer,请参阅 CVPixelBuffer Apple 开发者 文档

CMSampleBuffer

CMSampleBuffer 格式用于存储统一媒体类型的媒体样本, 非常适合直播运行模式。来自 iOS 相机的实时帧 由 iOS 以 CMSampleBuffer 格式异步传送 AVCaptureVideoDataOutput

Swift

// Obtain a CMSampleBuffer.

// Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the default
// orientation `UIImage.Orientation.up`.
let image = try MPImage(sampleBuffer: sampleBuffer)
    

Objective-C

// Obtain a `CMSampleBuffer`.

// Convert the `CMSampleBuffer` object to a MediaPipe's Image object having the
// default orientation `UIImageOrientationUp`.
MPImage *image = [[MPPImage alloc] initWithSampleBuffer:sampleBuffer error:nil];
    

如需详细了解 CMSampleBuffer,请参阅 CMSampleBuffer Apple 开发者 文档

运行任务

要运行图像分割器,请使用特定于指定的 segment() 方法 跑步模式:

  • 静态图片:segment(image:)
  • 视频:segment(videoFrame:timestampInMilliseconds:)
  • 直播:segmentAsync(image:timestampInMilliseconds:)

以下代码示例显示了如何在 Google Cloud 中运行图片分割器 不同的跑步模式:

Swift

映像

let result = try imageSegmenter.segment(image: image)
    

视频

let result = try imageSegmenter.segment(
  videoFrame: image,
  timestampInMilliseconds: timestamp)
    

直播

try imageSegmenter.segmentAsync(
  image: image,
  timestampInMilliseconds: timestamp)
    

Objective-C

映像

MPPImageSegmenterResult *result =
  [imageSegmenter segmentImage:image error:nil];
    

视频

MPPImageSegmenterResult *result =
  [imageSegmenter segmentVideoFrame:image
            timestampInMilliseconds:timestamp
                              error:nil];
    

直播

BOOL success =
  [imageSegmenter segmentAsyncImage:image
            timestampInMilliseconds:timestamp
                              error:nil];
    

图像分割器代码示例展示了以上每种模式的实现 segment(image:)segment(videoFrame:timestampInMilliseconds:)segmentAsync(image:timestampInMilliseconds:)

请注意以下几点:

  • 在视频模式或直播模式下投放广告时,您还必须提供 Image Segmenter 任务的输入帧的时间戳。

  • 在图片或视频模式下运行时,图片分割器任务会阻止 当前线程,直到处理完输入图像或帧为止。接收者 避免阻塞当前线程,在后台执行处理 使用 iOS 的会话串 DispatchNSOperation 框架。

  • 在实时流模式下运行时,Image Segmenter 任务会立即返回 并且不会阻塞当前线程。它会调用 imageSegmenter(_:didFinishSegmentation:timestampInMilliseconds:error:) 方法。通过 Image Segmenter 会在专用序列上异步调用此方法。 调度队列。要在界面上显示结果,请分派 并在处理后将结果发送到主队列中。如果 当图像分割器任务繁忙时,系统会调用 segmentAsync 函数 处理其他帧时,图像分割器会忽略新的输入帧。

处理和显示结果

运行推理时,Image Segmenter 任务会返回 ImageSegmenterResult 对象,该对象包含细分任务的结果。该 取决于您在构建新规则时 已配置 任务。

以下图片显示了某个类别的任务输出的可视化图表 值掩码。类别掩码范围为 [0, 255],每个像素值 表示模型输出的胜出类别索引。获奖类别 索引在模型可以识别的类别中得分最高。

原始图片和类别遮罩输出。来自 2012 年 Pascal VOC 数据集。

图像分割器示例代码演示了如何显示图像分割器 请参阅代码 示例 了解详情。