适用于 iOS 的姿势特征点检测指南

借助“姿态地标”任务,您可以检测图片或视频中人体的地标。您可以使用此任务来识别关键身体部位、分析姿势和对动作进行分类。此任务使用可处理单张图片或视频的机器学习 (ML) 模型。该任务会以图片坐标和 3 维世界坐标输出身体姿势地标。

以下说明介绍了如何将姿势地标注入器与 iOS 应用搭配使用。这些说明中介绍的代码示例可在 GitHub 上找到。

您可以查看此 Web 演示,了解此任务的实际运作方式。如需详细了解此任务的功能、模型和配置选项,请参阅概览

代码示例

MediaPipe Tasks 示例代码是对适用于 iOS 的姿势地标注点应用的基本实现。该示例使用实体 iOS 设备上的摄像头检测连续视频流中的姿势。该应用还可以检测设备图库中的图片和视频中的姿势。

您可以将该应用用作自己的 iOS 应用的起点,也可以在修改现有应用时参考该应用。Pose Landmarker 示例代码托管在 GitHub 上。

下载代码

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

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

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

    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. (可选)将您的 Git 实例配置为使用稀疏检出,以便您只保留 Pose Landmarker 示例应用的文件:

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

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

关键组件

以下文件包含姿势特征点示例应用的关键代码:

设置

本部分介绍了设置开发环境和编写代码项目以使用姿势地标注点的关键步骤。如需了解如何设置开发环境以使用 MediaPipe 任务(包括平台版本要求)的一般信息,请参阅 适用于 iOS 的设置指南

依赖项

姿势地标检测器使用 MediaPipeTasksVision 库,必须使用 CocoaPods 进行安装。该库与 Swift 和 Objective-C 应用兼容,并且无需任何额外的语言专用设置。

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

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

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

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

型号

MediaPipe 姿势地标任务需要与此任务兼容的训练有素的软件包。如需详细了解适用于姿势地标检测器的已训练模型,请参阅任务概览的“模型”部分

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

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

创建任务

您可以通过调用其某个初始化程序来创建姿势地标任务。PoseLandmarker(options:) 初始化程序接受配置选项的值。

如果您不需要使用自定义配置选项初始化姿势地标,可以使用 PoseLandmarker(modelPath:) 初始化程序使用默认选项创建姿势地标。如需详细了解配置选项,请参阅配置概览

姿势地标任务支持 3 种输入数据类型:静态图片、视频文件和实时视频流。默认情况下,PoseLandmarker(modelPath:) 会为静态图片初始化任务。如果您希望任务在初始化后处理视频文件或实时视频串流,请使用 PoseLandmarker(options:) 指定视频或直播运行模式。直播模式还需要额外的 poseLandmarkerLiveStreamDelegate 配置选项,该选项可让姿势地标检测器异步将姿势地标检测结果传递给代理。

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

Swift

Image

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "pose_landmarker",
                                      ofType: "task")

let options = PoseLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .image
options.minPoseDetectionConfidence = minPoseDetectionConfidence
options.minPosePresenceConfidence = minPosePresenceConfidence
options.minTrackingConfidence = minTrackingConfidence
options.numPoses = numPoses

let poseLandmarker = try PoseLandmarker(options: options)
    

视频

import MediaPipeTasksVision

let modelPath = Bundle.main.path(forResource: "pose_landmarker",
                                      ofType: "task")

let options = PoseLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .video
options.minPoseDetectionConfidence = minPoseDetectionConfidence
options.minPosePresenceConfidence = minPosePresenceConfidence
options.minTrackingConfidence = minTrackingConfidence
options.numPoses = numPoses

let poseLandmarker = try PoseLandmarker(options: options)
    

直播

import MediaPipeTasksVision

// Class that conforms to the `PoseLandmarkerLiveStreamDelegate` protocol and
// implements the method that the pose landmarker calls once it finishes
// performing pose landmark detection in each input frame.
class PoseLandmarkerResultProcessor: NSObject, PoseLandmarkerLiveStreamDelegate {

  func poseLandmarker(
    _ poseLandmarker: PoseLandmarker,
    didFinishDetection result: PoseLandmarkerResult?,
    timestampInMilliseconds: Int,
    error: Error?) {

    // Process the pose landmarker result or errors here.

  }
}

let modelPath = Bundle.main.path(forResource: "pose_landmarker",
                                      ofType: "task")

let options = PoseLandmarkerOptions()
options.baseOptions.modelAssetPath = modelPath
options.runningMode = .liveStream
options.minPoseDetectionConfidence = minPoseDetectionConfidence
options.minPosePresenceConfidence = minPosePresenceConfidence
options.minTrackingConfidence = minTrackingConfidence
options.numPoses = numPoses

// Assign an object of the class to the `poseLandmarkerLiveStreamDelegate`
// property.
let processor = PoseLandmarkerResultProcessor()
options.poseLandmarkerLiveStreamDelegate = processor

let poseLandmarker = try PoseLandmarker(options: options)
    

Objective-C

Image

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"pose_landmarker"
                                                      ofType:@"task"];

MPPPoseLandmarkerOptions *options = [[MPPPoseLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeImage;
options.minPoseDetectionConfidence = minPoseDetectionConfidence;
options.minPosePresenceConfidence = minPosePresenceConfidence;
options.minTrackingConfidence = minTrackingConfidence;
options.numPoses = numPoses;

MPPPoseLandmarker *poseLandmarker =
  [[MPPPoseLandmarker alloc] initWithOptions:options error:nil];
    

视频

@import MediaPipeTasksVision;

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"pose_landmarker"
                                                      ofType:@"task"];

MPPPoseLandmarkerOptions *options = [[MPPPoseLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeVideo;
options.minPoseDetectionConfidence = minPoseDetectionConfidence;
options.minPosePresenceConfidence = minPosePresenceConfidence;
options.minTrackingConfidence = minTrackingConfidence;
options.numPoses = numPoses;

MPPPoseLandmarker *poseLandmarker =
  [[MPPPoseLandmarker alloc] initWithOptions:options error:nil];
    

直播

@import MediaPipeTasksVision;

// Class that conforms to the `MPPPoseLandmarkerLiveStreamDelegate` protocol
// and implements the method that the pose landmarker calls once it finishes
// performing pose landmarks= detection in each input frame.

@interface APPPoseLandmarkerResultProcessor : NSObject 

@end

@implementation APPPoseLandmarkerResultProcessor

-   (void)poseLandmarker:(MPPPoseLandmarker *)poseLandmarker
    didFinishDetectionWithResult:(MPPPoseLandmarkerResult *)poseLandmarkerResult
         timestampInMilliseconds:(NSInteger)timestampInMilliseconds
                           error:(NSError *)error {

    // Process the pose landmarker result or errors here.

}

@end

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"pose_landmarker"
                                                      ofType:@"task"];

MPPPoseLandmarkerOptions *options = [[MPPPoseLandmarkerOptions alloc] init];
options.baseOptions.modelAssetPath = modelPath;
options.runningMode = MPPRunningModeLiveStream;
options.minPoseDetectionConfidence = minPoseDetectionConfidence;
options.minPosePresenceConfidence = minPosePresenceConfidence;
options.minTrackingConfidence = minTrackingConfidence;
options.numPoses = numPoses;

// Assign an object of the class to the `poseLandmarkerLiveStreamDelegate`
// property.
APPPoseLandmarkerResultProcessor *processor =
  [APPPoseLandmarkerResultProcessor new];
options.poseLandmarkerLiveStreamDelegate = processor;

MPPPoseLandmarker *poseLandmarker =
  [[MPPPoseLandmarker alloc] initWithOptions:options error:nil];
    

注意:如果您使用视频模式或直播模式,Pose Landmarker 会使用跟踪功能,以避免在每个帧上触发手掌检测模型,这有助于缩短延迟时间。

配置选项

此任务针对 iOS 应用提供了以下配置选项:

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

IMAGE:适用于单张图片输入的模式。

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

LIVE_STREAM:输入数据实时流式传输的模式,例如来自摄像头的模式。 在此模式下,poseLandmarkerLiveStreamDelegate 必须设置为实现 PoseLandmarkerLiveStreamDelegate 的类的实例,以接收异步执行姿势地标检测的结果。
{RunningMode.image, RunningMode.video, RunningMode.liveStream} RunningMode.image
num_poses 姿势地标点检测器可检测的姿势数量上限。 Integer > 0 1
min_pose_detection_confidence 姿势检测被视为成功的最低置信度分数。 Float [0.0,1.0] 0.5
min_pose_presence_confidence 姿势特征点检测中姿势存在分数的最低置信度分数。 Float [0.0,1.0] 0.5
min_tracking_confidence 姿势跟踪被视为成功所需的最低置信度得分。 Float [0.0,1.0] 0.5
output_segmentation_masks 姿势地标检测器是否为检测到的姿势输出分割掩码。 Boolean False
result_callback 设置结果监听器,以便在姿势地标在实时流模式下时异步接收地标结果。 仅当运行模式设置为 LIVE_STREAM 时才能使用 ResultListener N/A

直播配置

将运行模式设置为直播时,姿势地标检测器需要额外的 poseLandmarkerLiveStreamDelegate 配置选项,以便异步提供姿势地标检测结果。代理必须实现 poseLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法,Pose Landmarker 会在处理对每个帧执行姿势地标检测的结果后调用该方法。

选项名称 说明 值范围 默认值
poseLandmarkerLiveStreamDelegate 使姿势地标器能够接收在直播模式下异步执行姿势特征点检测的结果。实例设为此属性的类必须实现 poseLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法。 不适用 未设置

准备数据

您需要先将输入图片或帧转换为 MPImage 对象,然后才能将其传递给姿势地标定位器。MPImage 支持不同类型的 iOS 图片格式,并且可以在任何运行模式下使用这些格式进行推理。如需详细了解 MPImage,请参阅 MPImage API

根据您的用例和应用所需的运行模式选择 iOS 映像格式。MPImage 接受 UIImageCVPixelBufferCMSampleBuffer iOS 映像格式。

UIImage

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

  • 图片:应用软件包、用户图库或文件系统中格式为 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];
    

该示例使用默认的 UIImage.Orientation.Up 方向初始化 MPImage。您可以使用任何受支持的 UIImage.Orientation 值初始化 MPImage。姿势地标器不支持 .upMirrored.downMirrored.leftMirrored.rightMirrored 等镜像方向。

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

CVPixelBuffer

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

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

  • 图片:在使用 iOS 的 CoreImage 框架进行一些处理后生成 CVPixelBuffer 图片的应用可以在图片运行模式下发送到姿势特征点。

  • 视频:视频帧可以转换为 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 AVCaptureVideoDataOutputCMSampleBuffer 格式异步传送。

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 开发者文档

运行任务

如需运行姿势地标定位器,请使用特定于分配的运行模式的 detect() 方法:

  • 静态图片:detect(image:)
  • 视频:detect(videoFrame:timestampInMilliseconds:)
  • 直播:detectAsync(image:timestampInMilliseconds:)

以下代码示例展示了如何在这些不同的运行模式下运行姿势地标注点的简单示例:

Swift

Image

let result = try poseLandmarker.detect(image: image)
    

视频

let result = try poseLandmarker.detect(
  videoFrame: image,
  timestampInMilliseconds: timestamp)
    

直播

try poseLandmarker.detectAsync(
  image: image,
  timestampInMilliseconds: timestamp)
    

Objective-C

Image

MPPPoseLandmarkerResult *result =
  [poseLandmarker detectImage:image error:nil];
    

视频

MPPPoseLandmarkerResult *result =
  [poseLandmarker detectVideoFrame:image
           timestampInMilliseconds:timestamp
                             error:nil];
    

直播

BOOL success =
  [poseLandmarker detectAsyncImage:image
           timestampInMilliseconds:timestamp
                             error:nil];
    

姿势地标器代码示例更详细地展示了以上每种模式的实现:detect(image:)detect(videoFrame:timestampInMilliseconds:)detectAsync(image:timestampInMilliseconds:)。示例代码允许用户在处理模式之间切换,但您的用例可能不需要这样做。

请注意以下几点:

  • 在视频模式或直播模式下运行时,您还必须向姿势地标任务提供输入帧的时间戳。

  • 在图片或视频模式下运行时,Pose Landmarker 任务会阻塞当前线程,直到其处理完输入图片或帧。为避免阻塞当前线程,请使用 iOS DispatchNSOperation 框架在后台线程中执行处理。

  • 在直播模式下运行时,Pose Landmarker 任务会立即返回,并且不会阻塞当前线程。它会在处理每个输入帧后,使用姿势地标结果调用 poseLandmarker(_:didFinishDetection:timestampInMilliseconds:error:) 方法。姿势地标在专用串行调度队列上异步调用此方法。如需在界面上显示结果,请在处理结果后将结果调度到主队列。如果在姿势地标任务忙于处理另一个帧时调用 detectAsync 函数,则姿势地标会忽略新的输入帧。

处理和显示结果

运行推理后,Pose Landmarker 任务会返回一个 PoseLandmarkerResult,其中包含每个姿势地标的坐标。

以下示例展示了此任务的输出数据:

PoseLandmarkerResult:
  Landmarks:
    Landmark #0:
      x            : 0.638852
      y            : 0.671197
      z            : 0.129959
      visibility   : 0.9999997615814209
      presence     : 0.9999984502792358
    Landmark #1:
      x            : 0.634599
      y            : 0.536441
      z            : -0.06984
      visibility   : 0.999909
      presence     : 0.999958
    ... (33 landmarks per pose)
  WorldLandmarks:
    Landmark #0:
      x            : 0.067485
      y            : 0.031084
      z            : 0.055223
      visibility   : 0.9999997615814209
      presence     : 0.9999984502792358
    Landmark #1:
      x            : 0.063209
      y            : -0.00382
      z            : 0.020920
      visibility   : 0.999976
      presence     : 0.999998
    ... (33 world landmarks per pose)
  SegmentationMasks:
    ... (pictured below)

输出包含每个地标的归一化坐标 (Landmarks) 和世界坐标 (WorldLandmarks)。

输出包含以下标准化坐标 (Landmarks):

  • xy:根据图片宽度 (x) 和高度 (y) 将地标坐标标准化为介于 0.0 和 1.0 之间的值。

  • z:地标深度,以髋部中点的深度为原点。值越小,地标离相机越近。z 的大小与 x 大致相同。

  • visibility:地标在图片中可见的可能性。

输出包含以下世界坐标 (WorldLandmarks):

  • xyz:真实的三维坐标(以米为单位),以臀部的中点为原点。

  • visibility:地标在图片中可见的可能性。

下图直观显示了任务输出:

可选的分割掩码表示每个像素属于检测到的人物的可能性。以下图片是任务输出的分割掩码:

姿势地标示例代码演示了如何显示姿势地标结果。