适用于 Android 的手部特征检测指南

借助 MediaPipe Hand Landmarker 任务,您可以检测图片中的手的特征点。 以下说明介绍了如何将手部地标检测器与 Android 应用搭配使用。这些说明中介绍的代码示例可在 GitHub 上找到。

如需详细了解此任务的功能、模型和配置选项,请参阅概览

代码示例

MediaPipe Tasks 示例代码是对 Android 手部地标定位器应用的简单实现。该示例使用实体 Android 设备上的摄像头持续检测手部地标,还可以使用设备图库中的图片和视频静态检测手部地标。

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

下载代码

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

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

  1. 使用以下命令克隆 Git 代码库:
    git clone https://github.com/google-ai-edge/mediapipe-samples
    
  2. 您可以选择将 Git 实例配置为使用稀疏检出,以便您只保留 Hand Landmarker 示例应用的文件:
    cd mediapipe
    git sparse-checkout init --cone
    git sparse-checkout set examples/hand_landmarker/android
    

创建示例代码的本地版本后,您可以将项目导入 Android Studio 并运行应用。如需了解相关说明,请参阅 Android 设置指南

关键组件

以下文件包含此手部地标检测示例应用的重要代码:

设置

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

依赖项

手部地标任务使用 com.google.mediapipe:tasks-vision 库。将以下依赖项添加到 Android 应用的 build.gradle 文件中:

dependencies {
    implementation 'com.google.mediapipe:tasks-vision:latest.release'
}

型号

MediaPipe 手部标记任务需要与此任务兼容的训练模型软件包。如需详细了解手部地标检测器的可用训练模型,请参阅任务概览的“模型”部分

选择并下载该模型,然后将其存储在项目目录中:

<dev-project-root>/src/main/assets

ModelAssetPath 参数中指定模型的路径。在示例代码中,模型在 HandLandmarkerHelper.kt 文件中定义:

baseOptionBuilder.setModelAssetPath(MP_HAND_LANDMARKER_TASK)

创建任务

MediaPipe Hand Landmarker 任务使用 createFromOptions() 函数来设置任务。createFromOptions() 函数接受配置选项的值。如需详细了解配置选项,请参阅配置选项

手部地标检测器支持 3 种输入数据类型:静态图片、视频文件和直播。创建任务时,您需要指定与输入数据类型对应的运行模式。选择与输入数据类型对应的标签页,了解如何创建任务和运行推理。

Image

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_HAND_LANDMARKER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    HandLandmarker.HandLandmarkerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setNumHands(maxNumHands)
        .setRunningMode(RunningMode.IMAGE)

val options = optionsBuilder.build()

handLandmarker =
    HandLandmarker.createFromOptions(context, options)
    

视频

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_HAND_LANDMARKER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    HandLandmarker.HandLandmarkerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setNumHands(maxNumHands)
        .setRunningMode(RunningMode.VIDEO)

val options = optionsBuilder.build()

handLandmarker =
    HandLandmarker.createFromOptions(context, options)
    

直播

val baseOptionsBuilder = BaseOptions.builder().setModelAssetPath(MP_HAND_LANDMARKER_TASK)
val baseOptions = baseOptionBuilder.build()

val optionsBuilder =
    HandLandmarker.HandLandmarkerOptions.builder()
        .setBaseOptions(baseOptions)
        .setMinHandDetectionConfidence(minHandDetectionConfidence)
        .setMinTrackingConfidence(minHandTrackingConfidence)
        .setMinHandPresenceConfidence(minHandPresenceConfidence)
        .setNumHands(maxNumHands)
        .setResultListener(this::returnLivestreamResult)
        .setErrorListener(this::returnLivestreamError)
        .setRunningMode(RunningMode.VIDEO)

val options = optionsBuilder.build()

handLandmarker =
    HandLandmarker.createFromOptions(context, options)
    

手部地标示例代码实现允许用户在处理模式之间切换。这种方法会使任务创建代码变得更复杂,可能不适合您的用例。您可以在 HandLandmarkerHelper.kt 文件的 setupHandLandmarker() 函数中看到此代码。

配置选项

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

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

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

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

LIVE_STREAM:输入数据(例如来自摄像头)的直播模式。在此模式下,必须调用 resultListener 以设置监听器以异步接收结果。
{IMAGE, VIDEO, LIVE_STREAM} IMAGE
numHands 手部特征点检测器检测到的手的数量上限。 Any integer > 0 1
minHandDetectionConfidence 在手掌检测模型中,手检测被视为成功所需的最低置信度得分。 0.0 - 1.0 0.5
minHandPresenceConfidence 手掌地标检测模型中手掌存在得分的最小置信度得分。在视频模式和直播模式下,如果手部地标模型的手部存在置信度得分低于此阈值,手部地标定位器会触发手掌检测模型。否则,轻量级手部跟踪算法会确定手的具体位置,以便进行后续的特征点检测。 0.0 - 1.0 0.5
minTrackingConfidence 手部跟踪被视为成功所需的最低置信度得分。这是当前帧和上一帧中手部之间的边界框 IoU 阈值。在手部特征点检测器的“视频”模式和“流式传输”模式下,如果跟踪失败,手部特征点检测器会触发手部检测。否则,它会跳过手部检测。 0.0 - 1.0 0.5
resultListener 设置结果监听器,以便在手部地标检测器处于实时流式传输模式时异步接收检测结果。 仅在运行模式设置为 LIVE_STREAM 时适用 不适用 不适用
errorListener 设置可选的错误监听器。 不适用 不适用

准备数据

手部地标检测功能适用于图片、视频文件和直播视频。该任务会处理数据输入预处理,包括调整大小、旋转和值归一化。

以下代码演示了如何将数据交接给其他进程进行处理。这些示例详细介绍了如何处理来自图片、视频文件和直播视频流的数据。

Image

import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.framework.image.MPImage

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(image).build()
    

视频

import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.framework.image.MPImage

val argb8888Frame =
    if (frame.config == Bitmap.Config.ARGB_8888) frame
    else frame.copy(Bitmap.Config.ARGB_8888, false)

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(argb8888Frame).build()
    

直播

import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.framework.image.MPImage

// Convert the input Bitmap object to an MPImage object to run inference
val mpImage = BitmapImageBuilder(rotatedBitmap).build()
    

在手部地标示例代码中,数据准备是在 HandLandmarkerHelper.kt 文件中处理的。

运行任务

根据您要处理的数据类型,使用特定于该数据类型的 HandLandmarker.detect...() 方法。detect() 适用于单个图片,detectForVideo() 适用于视频文件中的帧,detectAsync() 适用于视频流。对视频流执行检测时,请务必在单独的线程中运行检测,以免阻塞界面线程。

以下代码示例展示了如何在这些不同的数据模式下运行手部地标检测器的简单示例:

Image

val result = handLandmarker?.detect(mpImage)
    

视频

val timestampMs = i * inferenceIntervalMs

handLandmarker?.detectForVideo(mpImage, timestampMs)
    ?.let { detectionResult ->
        resultList.add(detectionResult)
    }
    

直播

val mpImage = BitmapImageBuilder(rotatedBitmap).build()
val frameTime = SystemClock.uptimeMillis()

handLandmarker?.detectAsync(mpImage, frameTime)
    

请注意以下几点:

  • 在视频模式或直播模式下运行时,您还必须向手部地标定位任务提供输入帧的时间戳。
  • 在图片或视频模式下运行时,手部地标定位器任务将阻塞当前线程,直到其处理完输入图片或帧。为避免阻塞界面,请在后台线程中执行处理。
  • 在直播模式下运行时,手部地标任务不会阻塞当前线程,而是会立即返回。每当它处理完输入帧后,都会调用其结果监听器并传递检测结果。如果在手部地标定位任务忙于处理其他帧时调用检测函数,该任务将忽略新的输入帧。

在手部地标示例代码中,detectdetectForVideodetectAsync 函数在 HandLandmarkerHelper.kt 文件中定义。

处理和显示结果

手部地标检测器会为每次运行检测生成一个手部地标检测结果对象。结果对象包含图片坐标中的手部特征点、世界坐标中的手部特征点,以及检测到的手的左右手性。

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

HandLandmarkerResult 输出包含三个组成部分。每个组件都是一个数组,其中每个元素都包含单个检测到的手的以下结果:

  • 惯用手

    惯用手表示检测到的手是左手还是右手。

  • 地标

    手部地标共有 21 个,每个地标由 xyz 坐标组成。xy 坐标分别按图片宽度和高度归一化为 [0.0, 1.0]。z 坐标表示地标深度,其中手腕处的深度为原点。值越小,地标离相机越近。z 的大小与 x 大致相同。

  • 世界地标

    21 个手部特征点也以世界坐标表示。每个地标均由 xyz 组成,表示以米为单位的真实 3D 坐标,其原点位于手的几何中心。

HandLandmarkerResult:
  Handedness:
    Categories #0:
      index        : 0
      score        : 0.98396
      categoryName : Left
  Landmarks:
    Landmark #0:
      x            : 0.638852
      y            : 0.671197
      z            : -3.41E-7
    Landmark #1:
      x            : 0.634599
      y            : 0.536441
      z            : -0.06984
    ... (21 landmarks for a hand)
  WorldLandmarks:
    Landmark #0:
      x            : 0.067485
      y            : 0.031084
      z            : 0.055223
    Landmark #1:
      x            : 0.063209
      y            : -0.00382
      z            : 0.020920
    ... (21 world landmarks for a hand)

下图显示了任务输出的可视化结果:

一只手做出竖起大拇指的动作,手部的骨骼结构已绘制出来

Hand Landmarker 示例代码演示了如何显示从任务返回的结果,如需了解详情,请参阅 OverlayView 类。