姿势分类选项

借助机器学习套件姿势检测 API,您可以得出有意义的解释 检查身体各个部位的相对位置。当前页面 演示了几个示例。

使用 k-NN 算法进行姿态分类和重复计数

姿势检测最常见的应用之一就是健身跟踪。 构建可识别特定健身姿势和计数的姿势分类器 对开发者来说,重复是一件很有挑战性的任务。

在本部分中,我们将介绍如何构建自定义姿势 使用 MediaPipe Colab 训练分类器,以及 在我们的机器学习套件示例应用中演示了一个可正常运行的分类器。

如果您不熟悉 Google Colaboratory,请查看 入门指南

为了识别姿势,我们使用 k-最近邻算法 (k-NN), 而且很容易上手该算法会根据 距离最近的样本。

如需构建和训练识别器,请按以下步骤操作:

1. 收集图片样本

我们从各种来源收集了目标运动的图片样本。周三 为每次锻炼选择几百张图片,例如“向上”和“down”位置 做俯卧撑。请务必收集涵盖不同摄像头的样本 角度、环境条件、体型和锻炼变化。

图 1. 上下俯卧撑姿势

2. 对示例图片运行姿势检测

这会生成一组用于训练的姿势特征点。我们不是 对姿势检测本身很感兴趣,因为我们将训练我们的 构建自己的模型。

我们为自定义姿势分类选择的 k-NN 算法需要一个 每个样本的特征向量表示法以及要计算的指标 两个向量之间的距离,以查找最接近姿势样本的目标。 这意味着我们必须转换刚刚获取的姿势特征点。

为了将姿势特征点转换为特征向量,我们使用两对距离 在预定义的姿势关节列表之间,如手腕和 肩膀、脚踝、臀部以及左右手腕。由于图片的缩放比例 可以变化,我们对姿势进行了标准化处理,使其具有相同的躯干大小和垂直躯干 然后再转换地标。

3. 训练模型并计算重复次数

我们使用 MediaPipe Colab 访问分类器的代码, 训练模型。

为了统计重复次数,我们使用了另一种 Colab 算法来监控 目标姿势位置的阈值。例如:

  • 当“下降”概率姿态类达到指定阈值后, 算法第一次将“down”姿势类。
  • 当概率降至阈值以下时,算法会将 “down”姿态类已退出并增加计数器。
图 2. 重复计数示例

4. 与机器学习套件快速入门应用集成

上面的 Colab 会生成一个 CSV 文件,您可以在其中填充自己的所有姿势 示例。在本部分中,您将了解如何将 CSV 文件与 Android 机器学习套件快速入门应用,用于实时查看自定义姿势分类。

尝试使用快速入门应用中捆绑的样本进行姿势分类

  • 获取机器学习套件 Android 快速入门应用项目 并确保它能顺利构建并运行
  • 前往 LivePreviewActivity 并启用姿势检测 Run classification 在“设置”中页面。现在,您应该能够对俯卧撑和下蹲进行分类了。

添加您自己的 CSV 文件

  • 将您的 CSV 文件添加到应用的素材资源文件夹中。
  • PoseClassifierProcessor 中, 更新 POSE_SAMPLES_FILEPOSE_CLASSES 变量,以匹配 CSV 文件和姿势示例。
  • 构建并运行应用。

请注意,如果样本数量不足,分类效果可能不会很理想。 通常,每个姿势类别需要大约 100 个样本。

如需了解详情并亲自尝试,请访问 MediaPipe ColabMediaPipe 分类指南

通过计算地标距离识别简单手势

当两个或多个位置相距较近时,它们可用于 手势识别。例如,当一根或多根手指上的 就可以推断出用户最接近 都有可能碰过自己的脸

图 3. 解释姿势

通过角度启发法识别瑜伽姿势

您可以通过计算各个关节的角度来识别瑜伽姿势。对于 例如,下面的图 2 显示了 Warrior II 瑜伽姿势。大致角度 分别用以下语言编写:

图 4. 将姿势分解成多个角度

此姿势可以描述为以下近似身体组合 零件角度:

  • 两侧肩膀都 90 度角
  • 双肘 180 度
  • 前腿和腰部呈 90 度角
  • 后膝关节 180 度角
  • 腰部 135 度角

您可以使用姿势特征点来计算这些角度。例如,角度 右前腿和右腰之间的夹角 以及从右髋至右膝的线条。

计算出识别姿势所需的全部角度后, 看看是否有匹配,如果是这样,您就已经识别出了姿势。

以下代码段演示了如何使用 X 和 Y 坐标 计算两个身体部位之间的角度。这种分类方法 存在一些限制只检查 X 和 Y,计算出的角度会发生变化 确定拍摄对象与相机之间的角度。您将获得 使用水平、正向的正面图片实现最佳效果。您还可以 请尝试利用 Z 坐标 看看它是否更适合您的用例。

在 Android 上计算地标角度

以下方法计算任意三点之间的夹角, 地标。它确保返回的角度介于 0 度到 180 度。

Kotlin

fun getAngle(firstPoint: PoseLandmark, midPoint: PoseLandmark, lastPoint: PoseLandmark): Double {
        var result = Math.toDegrees(atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                firstPoint.getPosition().x - midPoint.getPosition().x))
        result = Math.abs(result) // Angle should never be negative
        if (result > 180) {
            result = 360.0 - result // Always get the acute representation of the angle
        }
        return result
    }

Java

static double getAngle(PoseLandmark firstPoint, PoseLandmark midPoint, PoseLandmark lastPoint) {
  double result =
        Math.toDegrees(
            atan2(lastPoint.getPosition().y - midPoint.getPosition().y,
                      lastPoint.getPosition().x - midPoint.getPosition().x)
                - atan2(firstPoint.getPosition().y - midPoint.getPosition().y,
                      firstPoint.getPosition().x - midPoint.getPosition().x));
  result = Math.abs(result); // Angle should never be negative
  if (result > 180) {
      result = (360.0 - result); // Always get the acute representation of the angle
  }
  return result;
}

计算右腰的角度的方法如下:

Kotlin

val rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE))

Java

double rightHipAngle = getAngle(
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
                pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE));

在 iOS 上计算地标角度

以下方法计算任意三点之间的夹角, 地标。它确保返回的角度介于 0 度到 180 度。

Swift

func angle(
      firstLandmark: PoseLandmark,
      midLandmark: PoseLandmark,
      lastLandmark: PoseLandmark
  ) -> CGFloat {
      let radians: CGFloat =
          atan2(lastLandmark.position.y - midLandmark.position.y,
                    lastLandmark.position.x - midLandmark.position.x) -
            atan2(firstLandmark.position.y - midLandmark.position.y,
                    firstLandmark.position.x - midLandmark.position.x)
      var degrees = radians * 180.0 / .pi
      degrees = abs(degrees) // Angle should never be negative
      if degrees > 180.0 {
          degrees = 360.0 - degrees // Always get the acute representation of the angle
      }
      return degrees
  }

Objective-C

(CGFloat)angleFromFirstLandmark:(MLKPoseLandmark *)firstLandmark
                      midLandmark:(MLKPoseLandmark *)midLandmark
                     lastLandmark:(MLKPoseLandmark *)lastLandmark {
    CGFloat radians = atan2(lastLandmark.position.y - midLandmark.position.y,
                            lastLandmark.position.x - midLandmark.position.x) -
                      atan2(firstLandmark.position.y - midLandmark.position.y,
                            firstLandmark.position.x - midLandmark.position.x);
    CGFloat degrees = radians * 180.0 / M_PI;
    degrees = fabs(degrees); // Angle should never be negative
    if (degrees > 180.0) {
        degrees = 360.0 - degrees; // Always get the acute representation of the angle
    }
    return degrees;
}

计算右腰的角度的方法如下:

Swift

let rightHipAngle = angle(
      firstLandmark: pose.landmark(ofType: .rightShoulder),
      midLandmark: pose.landmark(ofType: .rightHip),
      lastLandmark: pose.landmark(ofType: .rightKnee))

Objective-C

CGFloat rightHipAngle =
    [self angleFromFirstLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightShoulder]
                     midLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightHip]
                    lastLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightKnee]];