借助机器学习套件姿势检测 API,您可以得出有意义的解释 检查身体各个部位的相对位置。本页面演示了一些示例。
使用 k-NN 算法进行姿势分类和重复计数
姿势检测最常见的应用之一就是健身跟踪。构建能够识别特定健身姿势并统计重复次数的姿势分类器对开发者来说是一项具有挑战性的任务。
在本部分中,我们将介绍如何构建自定义姿势 使用 MediaPipe Colab 训练分类器,以及 在我们的机器学习套件示例应用中演示了一个可正常运行的分类器。
如果您不熟悉 Google Colaboratory,请参阅简介指南。
为了识别姿势,我们使用了 k 最近邻算法 (k-NN),因为它简单易用。该算法会根据训练集中最接近的样本确定对象的类别。
请按照以下步骤构建和训练识别器:
1. 收集图片样本
我们从各种来源收集了目标运动的图片样本。我们为每项锻炼选择了数百张图片,例如俯卧撑的“上”和“下”位置。请务必收集涵盖不同摄像头的样本 角度、环境条件、体型和锻炼变化。

2. 对示例图片运行姿势检测
这会生成一组用于训练的姿势特征点。我们不是 对姿势检测本身很感兴趣,因为我们将训练我们的 生成自己的模型。
我们为自定义姿势分类选择的 k-NN 算法需要为每个样本提供特征矢量表示法,并提供一个用于计算两个矢量之间距离的指标,以便找到与姿势样本最接近的目标。这意味着我们必须转换刚刚获取的姿势特征点。
为了将姿势特征点转换为特征向量,我们使用两对距离 在预定义的姿势关节列表之间,如手腕和 肩膀、脚踝、臀部以及左右手腕。由于图片的缩放比例可能会有所不同,因此在转换地标之前,我们会对姿势进行标准化处理,使其具有相同的躯干大小和垂直躯干方向。
3. 训练模型并统计重复次数
我们使用 MediaPipe Colab 访问分类器的代码, 训练模型。
为了统计重复次数,我们使用了另一种 Colab 算法来监控目标姿势位置的概率阈值。例如:
- 当“下降”概率姿态类达到指定阈值后, 算法第一次将“down”姿势类。
- 当概率低于阈值时,算法会标记“下”姿势类已退出,并增加计数器。

4. 与 ML Kit 快速入门应用集成
上面的 Colab 会生成一个 CSV 文件,您可以将所有姿势样本填充到该文件中。在本部分中,您将了解如何将 CSV 文件与 Android 机器学习套件快速入门应用,用于实时查看自定义姿势分类。
尝试使用快速入门应用中捆绑的示例进行姿势分类
- 获取机器学习套件 Android 快速入门应用项目 并确保它能顺利构建并运行
- 前往
LivePreviewActivity
并启用姿势检测Run classification
在“设置”中页面。现在,您应该能够对俯卧撑和下蹲进行分类了。
添加您自己的 CSV 文件
- 将您的 CSV 文件添加到应用的素材资源文件夹中。
- 在 PoseClassifierProcessor 中,更新
POSE_SAMPLES_FILE
和POSE_CLASSES
变量,使其与 CSV 文件和姿势示例相匹配。 - 构建并运行应用。
请注意,如果样本数量不足,分类效果可能不会很理想。 通常,每个姿势类别需要大约 100 个样本。
如需了解详情并亲自尝试,请访问 MediaPipe Colab 和 MediaPipe 分类指南。
通过计算地标距离来识别简单的手势
当两个或多个地标彼此靠近时,它们可用于识别手势。例如,当手上一个或多个手指的关键点靠近鼻子的关键点时,您可以推断用户最有可能在触摸自己的脸部。

通过角度启发法识别瑜伽姿势
您可以通过计算各种关节的角度来识别瑜伽姿势。对于 例如,下面的图 2 显示了勇士 II 瑜伽姿势。用于识别此姿势的近似角度以以下格式写入:

此姿势可以描述为大致身体部位角度的以下组合:
- 两侧肩膀都 90 度角
- 双肘 180 度
- 前腿和腰部呈 90 度角
- 后膝处的角度为 180 度
- 腰部 135 度角
您可以使用姿势特征点来计算这些角度。例如,角度 右前腿和右腰之间的夹角 以及从右髋至右膝的线条。
计算出识别姿势所需的所有角度后,您可以检查是否有匹配项,如果有,则表示您已识别出姿势。
以下代码段展示了如何使用 X 和 Y 坐标来 计算两个身体部位之间的角度。这种分类方法 存在一些限制只检查 X 和 Y,计算出的角度会发生变化 自动添加对象。您将获得 图片的效果最好。您还可以尝试利用 Z 坐标来扩展此算法,看看它在您的用例中的效果是否更好。
在 Android 上计算地标角度
以下方法计算任意三点之间的夹角, 地标。它确保返回的角度介于 0 度到 180 度。
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
}
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;
}
计算右腰的角度的方法如下:
val rightHipAngle = getAngle(
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE))
double rightHipAngle = getAngle(
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_SHOULDER),
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_HIP),
pose.getPoseLandmark(PoseLandmark.Type.RIGHT_KNEE));
在 iOS 上计算地标角度
以下方法计算任意三点之间的夹角, 地标。这可确保返回的角度介于 0 到 180 度之间。
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
}
(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;
}
右髋关节的角度的计算方法如下:
let rightHipAngle = angle(
firstLandmark: pose.landmark(ofType: .rightShoulder),
midLandmark: pose.landmark(ofType: .rightHip),
lastLandmark: pose.landmark(ofType: .rightKnee))
CGFloat rightHipAngle =
[self angleFromFirstLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightShoulder]
midLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightHip]
lastLandmark:[pose landmarkOfType:MLKPoseLandmarkTypeRightKnee]];