-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExerciseDetector.js
More file actions
208 lines (179 loc) · 7.51 KB
/
Copy pathExerciseDetector.js
File metadata and controls
208 lines (179 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 动作识别器 - 支持高抬腿和侧滑步
export class ExerciseDetector {
constructor() {
// 动作配置
this.config = {
high_knees: {
upRatio: 0.15, // 抬腿比例阈值
downRatio: 0.05, // 下落比例阈值
minInterval: 150, // 最小计数间隔(ms)
speedCompensation: 0.1, // 速度补偿
side: {
left: { up: false, lastCountTime: 0 },
right: { up: false, lastCountTime: 0 }
}
},
side_slide: {
leftThreshold: 0.35, // 左侧阈值
rightThreshold: 0.65, // 右侧阈值
centerMin: 0.45, // 中心区域最小
centerMax: 0.55, // 中心区域最大
state: 'center', // 当前状态
lastMoveTime: 0,
minInterval: 300 // 最小移动间隔
}
};
// 状态缓存
this.stateCache = new Map();
}
// 检测动作
detectExercise(landmarks, metrics, exerciseType, currentState) {
if (!landmarks || landmarks.length < 29) {
return { detected: false, newState: currentState, feedback: '' };
}
switch (exerciseType) {
case 'high_knees':
return this.detectHighKnees(landmarks, metrics, currentState);
case 'side_slide':
return this.detectSideSlide(landmarks, currentState);
default:
return { detected: false, newState: currentState, feedback: '' };
}
}
// 检测高抬腿
detectHighKnees(landmarks, metrics, currentState) {
const config = this.config.high_knees;
const now = Date.now();
// 计算腿部比例
const leftHip = landmarks[23];
const rightHip = landmarks[24];
const leftKnee = landmarks[25];
const rightKnee = landmarks[26];
const leftAnkle = landmarks[27];
const rightAnkle = landmarks[28];
if (!leftHip || !rightHip || !leftKnee || !rightKnee) {
return { detected: false, newState: currentState, feedback: '' };
}
// 计算腿长(髋到踝距离)
const leftLegLength = Math.sqrt(
Math.pow(leftHip.x - leftAnkle.x, 2) +
Math.pow(leftHip.y - leftAnkle.y, 2)
);
const rightLegLength = Math.sqrt(
Math.pow(rightHip.x - rightAnkle.x, 2) +
Math.pow(rightHip.y - rightAnkle.y, 2)
);
// 计算抬腿比例
const leftLiftRatio = (leftHip.y - leftKnee.y) / leftLegLength;
const rightLiftRatio = (rightHip.y - rightKnee.y) / rightLegLength;
// 动态调整阈值(考虑速度)
const timeSinceLastCount = Math.min(
now - config.side.left.lastCountTime,
now - config.side.right.lastCountTime
);
const speedFactor = Math.max(0.7, 1 - (timeSinceLastCount / 1000) * config.speedCompensation);
const dynamicUpRatio = config.upRatio * speedFactor;
let detected = false;
let newState = currentState;
let feedback = '';
// 检测左腿抬腿
if (!config.side.left.up && leftLiftRatio > dynamicUpRatio) {
if (now - config.side.left.lastCountTime > config.minInterval) {
config.side.left.up = true;
config.side.left.lastCountTime = now;
detected = true;
newState = 'up_left';
feedback = this.getRandomFeedback('high_knees');
}
} else if (config.side.left.up && leftLiftRatio < config.downRatio) {
config.side.left.up = false;
}
// 检测右腿抬腿
if (!config.side.right.up && rightLiftRatio > dynamicUpRatio) {
if (now - config.side.right.lastCountTime > config.minInterval) {
config.side.right.up = true;
config.side.right.lastCountTime = now;
detected = true;
newState = 'up_right';
feedback = this.getRandomFeedback('high_knees');
}
} else if (config.side.right.up && rightLiftRatio < config.downRatio) {
config.side.right.up = false;
}
// 如果双腿都放下,更新状态
if (!config.side.left.up && !config.side.right.up && newState.includes('up')) {
newState = 'down';
}
return { detected, newState, feedback };
}
// 检测侧滑步
detectSideSlide(landmarks, currentState) {
const config = this.config.side_slide;
const now = Date.now();
// 计算髋部中心水平位置
const leftHip = landmarks[23];
const rightHip = landmarks[24];
if (!leftHip || !rightHip) {
return { detected: false, newState: currentState, feedback: '' };
}
const hipCenterX = (leftHip.x + rightHip.x) / 2;
let detected = false;
let newState = config.state;
let feedback = '';
// 状态机
if (config.state === 'center' || config.state === 'idle') {
// 从中间移动到左侧或右侧
if (hipCenterX < config.leftThreshold && now - config.lastMoveTime > config.minInterval) {
config.state = 'left';
config.lastMoveTime = now;
detected = true;
newState = 'left';
feedback = this.getRandomFeedback('side_slide');
} else if (hipCenterX > config.rightThreshold && now - config.lastMoveTime > config.minInterval) {
config.state = 'right';
config.lastMoveTime = now;
detected = true;
newState = 'right';
feedback = this.getRandomFeedback('side_slide');
}
} else if (config.state === 'left' || config.state === 'right') {
// 回到中间区域
if (hipCenterX > config.centerMin && hipCenterX < config.centerMax) {
config.state = 'center';
newState = 'center';
}
}
return { detected, newState, feedback };
}
// 获取随机反馈
getRandomFeedback(exerciseType) {
const feedbacks = {
high_knees: [
'标准!', '优秀!', '完美!', '加油!', 'Nice!',
'快速!', '厉害!', '准确!', '漂亮!', '继续!'
],
side_slide: [
'侧移!', '滑步!', '流畅!', '标准!', 'Good!',
'敏捷!', '迅速!', '准确!', '稳定!', '继续!'
]
};
const list = feedbacks[exerciseType] || feedbacks.high_knees;
return list[Math.floor(Math.random() * list.length)];
}
// 重置检测器
reset(exerciseType) {
if (exerciseType === 'high_knees') {
this.config.high_knees.side = {
left: { up: false, lastCountTime: 0 },
right: { up: false, lastCountTime: 0 }
};
} else if (exerciseType === 'side_slide') {
this.config.side_slide.state = 'center';
this.config.side_slide.lastMoveTime = 0;
}
}
// 获取配置
getConfig() {
return JSON.parse(JSON.stringify(this.config));
}
}