AI运动计数器
站长
· 阅读数 23
本文正在参加「金石计划 . 瓜分6万现金大奖」
//复制预览
https://code.juejin.cn/pen/7165773210330333199
1、背景介绍
近年来,在人工智能技术更迭及后口罩时代居民生活模式改变的背景之下,智能运动健身行业得到了极大的发展。刘畊宏带起的“本草纲目”健身热潮也正式开启了居家健身时代,随之而来的便是人们对个性化、智能化的健身动作识别、矫正、计数的强烈需求,如何随时随地进行便捷、标准的健身运动逐渐成为了人们热议的话题。
2、方案介绍
本方案采用基于MediaPipe Pose关键点检测的健身动作识别和计数。
3、创建人体姿势模式
//创建模型
createModel() {
return new Promise(async resolve => {
this.modelLoad = true;
const model = poseDetection.SupportedModels.BlazePose;
const detectorConfig = {
runtime: 'mediapipe',
enableSmoothing: true, //默认为真。如果您的输入是静态图像,请将其设置为 false。该标志用于指示是否使用时间滤波器来平滑预测的关键点。
enableSegmentation: true, //一个布尔值,指示是否生成分段掩码。
smoothSegmentation: true,//是否过滤不同输入图像的分割掩码以减少抖动
solutionPath: 'https://unpkg.com/@mediapipe/pose',
modelType: this.modelParams.modelType,
};
this.model = await poseDetection.createDetector(model, detectorConfig);
this.modelLoad = false;
resolve(this.model);
})
}
4、俯卧撑
通过观察我们发现俯卧撑有两个状态“向上”和“向下”。
动作 | 图片 |
---|---|
向上 | |
向下 |
//俯卧撑
pushUp(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===12); //左肩
const place2 = (posesItem.keypoints || []).find((e,i)=>i===32); //左脚
/*
x1,y1(左肩)
|
|
|
x2,y2 -------|------- x4,y4
x3,y3(左脚)
*/
const [x1, y1, x2, y2, x3, y3, x4, y4] = [
place1.x, place1.y,
0, place2.y,
place2.x, place2.y,
this.canvas.width, place2.y
];
const angle = this.getAngle({
x: x1 - x3,
y: y1 - y3,
}, {
x: x4 - x3,
y: y4 - y3,
});
// 左脚 => 左肩
if(angle>150 && angle<180){
const newAngle = 180 - angle;
// console.log('newAngle',newAngle);
const [min,max] = [13,28];
const pro = this.GetPercent(newAngle-min,max-min);
this.statsFRE.update(pro<=0?1:pro>100?100:pro,100);
if(newAngle>=20 && newAngle<=max){
console.log('向上');
}
if(newAngle>=min && newAngle<=14){
console.log('向下');
}
}
},
首先,为了过滤掉与目标样本几乎相同的样本,但在特征向量中只有几个不同的值(这意味着不同的弯曲关节,因此还有其他姿势类),使用最小的每次坐标距离作为距离度量, 然后使用每个坐标的平均距离在第一次搜索中找到最近的姿势集群。
判断条件
- 12(左肩)和32(左脚)产生的夹角。
- 11(右肩)和31(右脚)产生的夹角。
- 12(左肩)和18(左指尖)的距离
- 11(右肩)和17(右指尖)的距离
重复计数
为了计算重复次数,该算法监控目标姿势类的概率。让我们用其“向上”和“向下”终端状态进行俯卧撑:
- 当“向下”姿势类的概率首次超过某个阈值时,算法标记输入了“向下”姿势类。
- 一旦概率下降到阈值以下,算法就会标记“向下”姿势类已被退出并增加计数器。
为了避免概率围绕阈值波动的情况(例如,当用户在“向上”和“向下”状态之间暂停时)导致幻影计数的情况,用于检测状态退出时的阈值实际上略低于用于检测何时进入状态的阈值。它创建一个无法更改姿势类和计数器的间隔。
5、开合跳
动作 | 图片 |
---|---|
张开 | |
闭合 |
//开合跳
jumpingJacks(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===18); //左手
const place2 = (posesItem.keypoints || []).find((e,i)=>i===17); //右手
const place3 = (posesItem.keypoints || []).find((e,i)=>i===32); //左脚
const place4 = (posesItem.keypoints || []).find((e,i)=>i===31); //右脚
const handDistance = this.getDistance(place1.x,place1.y,place2.x,place2.y);
const footDistance = this.getDistance(place3.x,place3.y,place4.x,place4.y);
if(handDistance>90 && footDistance>45){
console.log('张开');
}
if(handDistance<20 && footDistance<20){
console.log('闭合');
}
}
6、高抬腿
动作 | 图片 |
---|---|
左抬腿 | |
右抬腿 |
//高抬腿
highLegLift(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===18); //左手
const place2 = (posesItem.keypoints || []).find((e,i)=>i===17); //右手
const place3 = (posesItem.keypoints || []).find((e,i)=>i===32); //左脚
const place4 = (posesItem.keypoints || []).find((e,i)=>i===31); //右脚
const handDistance = this.getDistance(place1.x,place1.y,place3.x,place3.y);
const footDistance = this.getDistance(place2.x,place2.y,place4.x,place4.y);
if(handDistance<55 && footDistance>155){
console.log('左抬腿');
}
if(handDistance>155 && footDistance<70){
console.log('右抬腿');
}
}
7、深蹲+对角卷腹
动作 | 图片 |
---|---|
左对角卷腹 | |
右对角卷腹 |
//深蹲+对角卷腹
deepSquat(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===14); //左手关节
const place2 = (posesItem.keypoints || []).find((e,i)=>i===13); //右手关节
const place3 = (posesItem.keypoints || []).find((e,i)=>i===26); //左腿关节
const place4 = (posesItem.keypoints || []).find((e,i)=>i===25); //右腿关节
const jointDistance1 = this.getDistance(place1.x,place1.y,place4.x,place4.y); //左手关节到右腿关节的距离
const jointDistance2 = this.getDistance(place2.x,place2.y,place3.x,place3.y); //右手关节到左腿关节的距离
if(jointDistance1>81 && jointDistance2<20){
console.log('左对角卷腹');
}
if(jointDistance1<20 && jointDistance2>85){
console.log('右对角卷腹');
}
},
8、髋部中立位
动作 | 图片 |
---|---|
闭合 | |
张开 |
//髋部中立位
hipUp(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===32); //左脚
const place2 = (posesItem.keypoints || []).find((e,i)=>i===31); //右脚
const footDistance = this.getDistance(place1.x,place1.y,place2.x,place2.y);
if(footDistance>110){
console.log('张开');
}
if(footDistance<25){
console.log('闭合');
}
},
9、5s深蹲保持+踮脚站立
动作 | 图片 |
---|---|
深蹲 | |
站立 |
//5s深蹲保持+踮脚站立
squatHold(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===18); //左手
const place2 = (posesItem.keypoints || []).find((e,i)=>i===17); //右手
const place3 = (posesItem.keypoints || []).find((e,i)=>i===32); //左脚
const place4 = (posesItem.keypoints || []).find((e,i)=>i===31); //右脚
const place5 = (posesItem.keypoints || []).find((e,i)=>i===24); //左髋
const place6 = (posesItem.keypoints || []).find((e,i)=>i===23); //右髋
const handDistance = this.getDistance(place1.x,place1.y,place2.x,place2.y);
const strideDistance = this.getDistance(place5.x,place5.y,place3.x,place3.y)+this.getDistance(place6.x,place6.y,place4.x,place4.y);
if(handDistance>85 && strideDistance>195){
console.log('深蹲');
}
if(handDistance<6 && strideDistance<115){
console.log('站立');
}
},
10、登山者
动作 | 图片 |
---|---|
左抬腿 | |
右抬腿 |
//登山者
climber(posesItem){
const place1 = (posesItem.keypoints || []).find((e,i)=>i===14); //左手
const place2 = (posesItem.keypoints || []).find((e,i)=>i===13); //右手
const place3 = (posesItem.keypoints || []).find((e,i)=>i===26); //左脚
const place4 = (posesItem.keypoints || []).find((e,i)=>i===25); //右脚
const handDistance = this.getDistance(place1.x,place1.y,place3.x,place3.y);
const footDistance = this.getDistance(place2.x,place2.y,place4.x,place4.y);
// console.log(handDistance,footDistance);
this.statsFRE.update(handDistance,200);
if(handDistance<55 && footDistance<65){
console.log('左抬腿');
}
if(handDistance>70 && footDistance>40){
console.log('右抬腿');
}
},
本文正在参加「金石计划 . 瓜分6万现金大奖」