使用 React Native 和 TensorFlow 构建健身动作纠正应用
”基于 CORE-EEAT 标准,结合运动生物力学和 AI 姿态估计技术
概述
健身动作不正确是导致运动损伤的主要原因。据统计,约 60% 的健身房受伤案例源于动作姿势不当。本教程将指导你构建一个基于 AI 的实时健身动作纠正应用,帮助用户安全有效地训练。
应用功能
- 实时姿态检测: 使用摄像头捕捉身体关节点
- 动作模式识别: 识别深蹲、硬拉、卧推、推举等动作
- 错误分析: 检测膝盖内扣、弓背、半程动作等常见错误
- 实时反馈: 语音和视觉提示纠正建议
- 训练统计: 记录组数、次数、动作质量评分
技术架构
code
┌────────────────────────────────────────────────────────────┐
│ React Native 应用层 │
├────────────────────────────────────────────────────────────┤
│ CameraView │ PoseOverlay │ FeedbackPanel │
├────────────────────────────────────────────────────────────┤
│ AI 分析服务层 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ PoseEstimator │ │ FormAnalyzer │ │
│ │ (TensorFlow) │ │ (Biomechanics) │ │
│ └──────────────────┘ └──────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ react-native-vision-camera │ @tensorflow/tfjs-core │
│ @tensorflow-models/pose-detection │
└────────────────────────────────────────────────────────────┘
Code collapsed
项目初始化
1. 创建项目
code
npx react-native@latest init WorkoutFormCorrector
cd WorkoutFormCorrector
Code collapsed
2. 安装依赖
code
# 摄像头和视觉处理
npm install react-native-vision-camera
# TensorFlow 和姿态检测
npm install @tensorflow/tfjs @tensorflow/tfjs-react-native
npm install @tensorflow-models/pose-detection
# 角度计算工具
npm install mathjs
# 动画和 UI
npm install react-native-reanimated
npm install react-native-svg
# 语音反馈
npm install react-native-tts
Code collapsed
3. 配置权限
iOS (ios/YourProject/Info.plist):
code
<key>NSCameraUsageDescription</key>
<string>此应用需要使用摄像头来检测您的健身动作姿势,提供实时纠正建议。</string>
<key>NSMicrophoneUsageDescription</key>
<string>此应用需要使用麦克风来提供语音反馈指导。</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>此应用需要保存您的训练照片用于动作分析。</string>
Code collapsed
Android (android/app/src/main/AndroidManifest.xml):
code
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
Code collapsed
TensorFlow 和姿态检测配置
1. 初始化 TensorFlow
code
// services/tensorflowService.ts
import * as tf from '@tensorflow/tfjs-react-native';
import { poseDetection } from '@tensorflow-models/pose-detection';
export class TensorFlowService {
private static instance: TensorFlowService;
private detector: poseDetection.PoseDetector | null = null;
private isInitialized = false;
private constructor() {}
static getInstance(): TensorFlowService {
if (!TensorFlowService.instance) {
TensorFlowService.instance = new TensorFlowService();
}
return TensorFlowService.instance;
}
async initialize(): Promise<void> {
if (this.isInitialized) return;
try {
// 等待 TensorFlow 准备就绪
await tf.ready();
// 创建姿态检测器
const model = poseDetection.SupportedModels.MoveNet;
const detectorConfig = {
modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING,
enableSmoothing: true,
};
this.detector = await poseDetection.createDetector(model, detectorConfig);
this.isInitialized = true;
console.log('TensorFlow initialized successfully');
} catch (error) {
console.error('Failed to initialize TensorFlow:', error);
throw error;
}
}
getDetector(): poseDetection.PoseDetector {
if (!this.detector) {
throw new Error('Detector not initialized');
}
return this.detector;
}
isReady(): boolean {
return this.isInitialized;
}
}
Code collapsed
2. 姿态检测服务
code
// services/poseDetectionService.ts
import { poseDetection } from '@tensorflow-models/pose-detection';
import { TensorFlowService } from './tensorflowService';
export interface Keypoint {
x: number;
y: number;
score: number;
name: string;
}
export interface Pose {
keypoints: Keypoint[];
score: number;
}
export class PoseDetectionService {
private detector: poseDetection.PoseDetector;
private videoWidth: number;
private videoHeight: number;
constructor(videoWidth: number, videoHeight: number) {
const tfService = TensorFlowService.getInstance();
this.detector = tfService.getDetector();
this.videoWidth = videoWidth;
this.videoHeight = videoHeight;
}
async detectPose(imageData: ImageData): Promise<Pose | null> {
try {
const poses = await this.detector.estimatePoses(imageData, {
flipHorizontal: false,
maxPoses: 1,
});
if (poses.length === 0) return null;
return {
keypoints: poses[0].keypoints.map(kp => ({
x: kp.x / this.videoWidth, // 归一化坐标
y: kp.y / this.videoHeight,
score: kp.score ?? 0,
name: kp.name ?? '',
})),
score: poses[0].score,
};
} catch (error) {
console.error('Pose detection error:', error);
return null;
}
}
/**
* 获取特定关键点
*/
getKeypoint(pose: Pose, name: string): Keypoint | null {
return pose.keypoints.find(kp => kp.name === name) || null;
}
/**
* 检查关键点是否可信
*/
isKeypointValid(keypoint: Keypoint | null, threshold = 0.3): boolean {
return keypoint !== null && keypoint.score > threshold;
}
/**
* 计算两个关键点之间的角度
*/
calculateAngle(
pointA: Keypoint,
pointB: Keypoint,
pointC: Keypoint
): number | null {
if (!this.isKeypointValid(pointA) ||
!this.isKeypointValid(pointB) ||
!this.isKeypointValid(pointC)) {
return null;
}
const radians = Math.atan2(pointC.y - pointB.y, pointC.x - pointB.x) -
Math.atan2(pointA.y - pointB.y, pointA.x - pointB.x);
let angle = Math.abs(radians * 180.0 / Math.PI);
if (angle > 180.0) {
angle = 360 - angle;
}
return angle;
}
/**
* 计算两点之间的距离 (归一化)
*/
calculateDistance(pointA: Keypoint, pointB: Keypoint): number | null {
if (!this.isKeypointValid(pointA) || !this.isKeypointValid(pointB)) {
return null;
}
const dx = pointA.x - pointB.x;
const dy = pointA.y - pointB.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Code collapsed
动作分析引擎
1. 深蹲分析器
code
// analyzers/squatAnalyzer.ts
import { Pose, Keypoint } from '../services/poseDetectionService';
export interface SquatAnalysis {
isCorrect: boolean;
depth: 'shallow' | 'parallel' | 'deep';
kneeValgus: boolean;
backAngle: number;
errors: string[];
suggestions: string[];
}
export class SquatAnalyzer {
/**
* 分析深蹲动作
* 基于运动生物力学原理
*/
analyze(pose: Pose, isLeftSide: boolean): SquatAnalysis {
const errors: string[] = [];
const suggestions: string[] = [];
// 获取关键点
const hip = this.getKeypoint(pose, isLeftSide ? 'left_hip' : 'right_hip');
const knee = this.getKeypoint(pose, isLeftSide ? 'left_knee' : 'right_knee');
const ankle = this.getKeypoint(pose, isLeftSide ? 'left_ankle' : 'right_ankle');
const shoulder = this.getKeypoint(pose, isLeftSide ? 'left_shoulder' : 'right_shoulder');
// 检查关键点有效性
if (!hip || !knee || !ankle || !shoulder) {
return {
isCorrect: false,
depth: 'shallow',
kneeValgus: false,
backAngle: 0,
errors: ['无法检测到完整身体姿态'],
suggestions: ['确保全身在摄像头范围内', '穿着紧身服装以提高识别准确度'],
};
}
// 1. 检查膝盖内扣 (Valgus)
const oppositeKnee = this.getKeypoint(pose, isLeftSide ? 'right_knee' : 'left_knee');
const kneeValgus = this.checkKneeValgus(knee, oppositeKnee, hip);
if (kneeValgus) {
errors.push('膝盖内扣');
suggestions.push('膝盖应向外推,与脚尖方向一致');
suggestions.push('加强臀部外侧肌肉训练');
}
// 2. 计算背部角度
const backAngle = this.calculateBackAngle(shoulder, hip, ankle);
if (backAngle < 45) {
errors.push('背部过度前倾');
suggestions.push('保持背部挺直,核心收紧');
suggestions.push('减轻重量或加强核心力量');
}
// 3. 计算下蹲深度
const kneeAngle = this.calculateKneeAngle(hip, knee, ankle);
const depth = this.assessDepth(kneeAngle);
if (depth === 'shallow') {
errors.push('下蹲深度不足');
suggestions.push('下蹲至大腿与地面平行');
suggestions.push('确保达到有效训练深度');
}
// 4. 检查重心位置
const centerOfMass = this.assessCenterOfMass(hip, knee, ankle);
if (centerOfMass === 'tooForward') {
errors.push('重心过于靠前');
suggestions.push('臀部向后坐,不要膝盖前顶');
}
return {
isCorrect: errors.length === 0,
depth,
kneeValgus,
backAngle: Math.round(backAngle),
errors,
suggestions,
};
}
private getKeypoint(pose: Pose, name: string): Keypoint | null {
return pose.keypoints.find(kp => kp.name === name) || null;
}
/**
* 检查膝盖内扣
* 原理: 对比两侧膝盖相对于髋部的水平位置
*/
private checkKneeValgus(
knee: Keypoint,
oppositeKnee: Keypoint | null,
hip: Keypoint
): boolean {
if (!oppositeKnee) return false;
const hipKneeAngle = Math.atan2(knee.y - hip.y, knee.x - hip.x) * 180 / Math.PI;
// 如果膝盖向内偏移超过一定角度
return hipKneeAngle < -10 || hipKneeAngle > 10;
}
/**
* 计算背部角度
*/
private calculateBackAngle(shoulder: Keypoint, hip: Keypoint, ankle: Keypoint): number {
const radians = Math.atan2(hip.y - ankle.y, hip.x - ankle.x) -
Math.atan2(shoulder.y - hip.y, shoulder.x - hip.x);
let angle = Math.abs(radians * 180.0 / Math.PI);
if (angle > 180) angle = 360 - angle;
return angle;
}
/**
* 计算膝关节角度
*/
private calculateKneeAngle(hip: Keypoint, knee: Keypoint, ankle: Keypoint): number {
const radians = Math.atan2(ankle.y - knee.y, ankle.x - knee.x) -
Math.atan2(hip.y - knee.y, hip.x - knee.x);
let angle = Math.abs(radians * 180.0 / Math.PI);
if (angle > 180) angle = 360 - angle;
return angle;
}
/**
* 评估下蹲深度
* 标准: 平行 = 膝关节约 90 度
*/
private assessDepth(kneeAngle: number): 'shallow' | 'parallel' | 'deep' {
if (kneeAngle > 100) return 'shallow';
if (kneeAngle > 80) return 'parallel';
return 'deep';
}
/**
* 评估重心位置
*/
private assessCenterOfMass(hip: Keypoint, knee: Keypoint, ankle: Keypoint): 'good' | 'tooForward' | 'tooBackward' {
const hipKneeRatio = (knee.y - hip.y) / (ankle.y - hip.y);
if (hipKneeRatio < 0.4) return 'tooBackward';
if (hipKneeRatio > 0.6) return 'tooForward';
return 'good';
}
}
Code collapsed
2. 硬拉分析器
code
// analyzers/deadliftAnalyzer.ts
import { Pose, Keypoint } from '../services/poseDetectionService';
export interface DeadliftAnalysis {
isCorrect: boolean;
backRounded: boolean;
hipHeight: string;
errors: string[];
suggestions: string[];
}
export class DeadliftAnalyzer {
analyze(pose: Pose): DeadliftAnalysis {
const errors: string[] = [];
const suggestions: string[] = [];
const leftShoulder = pose.keypoints.find(kp => kp.name === 'left_shoulder');
const rightShoulder = pose.keypoints.find(kp => kp.name === 'right_shoulder');
const leftHip = pose.keypoints.find(kp => kp.name === 'left_hip');
const rightHip = pose.keypoints.find(kp => kp.name === 'right_hip');
const leftKnee = pose.keypoints.find(kp => kp.name === 'left_knee');
const rightKnee = pose.keypoints.find(kp => kp.name === 'right_knee');
if (!leftShoulder || !rightShoulder || !leftHip || !rightHip || !leftKnee || !rightKnee) {
return {
isCorrect: false,
backRounded: false,
hipHeight: 'unknown',
errors: ['无法检测到完整身体姿态'],
suggestions: ['确保全身在摄像头范围内'],
};
}
// 1. 检查背部是否弓起
const shoulderAvgY = (leftShoulder.y + rightShoulder.y) / 2;
const hipAvgY = (leftHip.y + rightHip.y) / 2;
const kneeAvgY = (leftKnee.y + rightKnee.y) / 2;
const backAngle = Math.atan2(hipAvgY - shoulderAvgY, 0) * 180 / Math.PI;
// 背部应相对平直 (约 15-30 度)
if (Math.abs(backAngle) > 40) {
errors.push('背部弓起');
suggestions.push('保持背部挺直,脊柱中立');
suggestions.push('激活核心肌群');
suggestions.push('考虑减轻重量');
}
// 2. 检查髋部位置
const hipHeightRatio = (kneeAvgY - hipAvgY) / (kneeAvgY - shoulderAvgY);
if (hipHeightRatio > 0.7) {
errors.push('起始位置髋部过高');
suggestions.push('降低髋部,保持背部角度');
} else if (hipHeightRatio < 0.4) {
errors.push('起始位置髋部过低');
suggestions.push('提高髋部,让臀部主动发力');
}
return {
isCorrect: errors.length === 0,
backRounded: Math.abs(backAngle) > 40,
hipHeight: hipHeightRatio > 0.6 ? 'high' : hipHeightRatio < 0.4 ? 'low' : 'optimal',
errors,
suggestions,
};
}
}
Code collapsed
3. 卧推分析器
code
// analyzers/benchPressAnalyzer.ts
import { Pose, Keypoint } from '../services/poseDetectionService';
export interface BenchPressAnalysis {
isCorrect: boolean;
elbowFlare: boolean;
depth: string;
errors: string[];
suggestions: string[];
}
export class BenchPressAnalyzer {
analyze(pose: Pose): BenchPressAnalysis {
const errors: string[] = [];
const suggestions: string[] = [];
const leftShoulder = pose.keypoints.find(kp => kp.name === 'left_shoulder');
const rightShoulder = pose.keypoints.find(kp => kp.name === 'right_shoulder');
const leftElbow = pose.keypoints.find(kp => kp.name === 'left_elbow');
const rightElbow = pose.keypoints.find(kp => kp.name === 'right_elbow');
const leftWrist = pose.keypoints.find(kp => kp.name === 'left_wrist');
const rightWrist = pose.keypoints.find(kp => kp.name === 'right_wrist');
if (!leftElbow || !rightElbow || !leftWrist || !rightWrist) {
return {
isCorrect: false,
elbowFlare: false,
depth: 'unknown',
errors: ['无法检测到完整手臂姿态'],
suggestions: ['确保手臂在摄像头范围内'],
};
}
// 1. 检查肘部外展角度 (建议 45-75 度)
const leftElbowAngle = this.calculateElbowAngle(leftShoulder, leftElbow, leftWrist);
const rightElbowAngle = this.calculateElbowAngle(rightShoulder, rightElbow, rightWrist);
const avgElbowAngle = (leftElbowAngle + rightElbowAngle) / 2;
if (avgElbowAngle < 45) {
errors.push('肘部过于内收');
suggestions.push('肘部外展至约 45-75 度');
suggestions.push('避免肘部贴在身体上');
} else if (avgElbowAngle > 90) {
errors.push('肘部过度外展 (T 字形)');
suggestions.push('减小肘部外展角度至 75 度以内');
suggestions.push('保护肩关节,避免肩峰撞击');
}
// 2. 检查下放深度
// 假设: 手腕最高点是起始位置,最低点应接近胸部
const midPointY = (leftShoulder.y + rightShoulder.y) / 2;
const wristAvgY = (leftWrist.y + rightWrist.y) / 2;
if (wristAvgY < midPointY + 0.1) {
errors.push('下放深度不足');
suggestions.push('将杠铃下放至胸部高度');
}
return {
isCorrect: errors.length === 0,
elbowFlare: avgElbowAngle > 90,
depth: wristAvgY < midPointY + 0.1 ? 'shallow' : 'good',
errors,
suggestions,
};
}
private calculateElbowAngle(shoulder: Keypoint | undefined, elbow: Keypoint, wrist: Keypoint): number {
if (!shoulder) return 60; // 默认值
const radians = Math.atan2(wrist.y - elbow.y, wrist.x - elbow.x) -
Math.atan2(shoulder.y - elbow.y, shoulder.x - elbow.x);
let angle = Math.abs(radians * 180.0 / Math.PI);
if (angle > 180) angle = 360 - angle;
return angle;
}
}
Code collapsed
React Hook 封装
code
// hooks/useWorkoutAnalysis.ts
import { useState, useEffect, useRef } from 'react';
import { Camera } from 'react-native-vision-camera';
import { PoseDetectionService, Pose } from '../services/poseDetectionService';
import { SquatAnalyzer } from '../analyzers/squatAnalyzer';
import { TensorFlowService } from '../services/tensorflowService';
export type ExerciseType = 'squat' | 'deadlift' | 'benchPress' | 'overheadPress';
export interface WorkoutState {
isAnalyzing: boolean;
currentExercise: ExerciseType | null;
repCount: number;
feedback: string[];
qualityScore: number;
}
export function useWorkoutAnalysis() {
const [workoutState, setWorkoutState] = useState<WorkoutState>({
isAnalyzing: false,
currentExercise: null,
repCount: 0,
feedback: [],
qualityScore: 0,
});
const [pose, setPose] = useState<Pose | null>(null);
const camera = useRef<Camera>(null);
const poseDetectionService = useRef<PoseDetectionService | null>(null);
const squatAnalyzer = useRef(new SquatAnalyzer());
useEffect(() => {
initTensorFlow();
}, []);
const initTensorFlow = async () => {
try {
const tfService = TensorFlowService.getInstance();
await tfService.initialize();
} catch (error) {
console.error('Failed to initialize TensorFlow:', error);
}
};
const startAnalysis = async (exercise: ExerciseType) => {
setWorkoutState(prev => ({
...prev,
isAnalyzing: true,
currentExercise: exercise,
repCount: 0,
feedback: [],
qualityScore: 0,
}));
};
const stopAnalysis = () => {
setWorkoutState(prev => ({
...prev,
isAnalyzing: false,
currentExercise: null,
}));
};
const analyzeFrame = async (imageData: ImageData) => {
if (!workoutState.isAnalyzing || !workoutState.currentExercise) return;
try {
// 初始化姿态检测服务 (如果需要)
if (!poseDetectionService.current) {
poseDetectionService.current = new PoseDetectionService(
imageData.width,
imageData.height
);
}
// 检测姿态
const detectedPose = await poseDetectionService.current.detectPose(imageData);
if (detectedPose) {
setPose(detectedPose);
// 分析动作
const analysis = analyzeExercise(detectedPose, workoutState.currentExercise);
// 更新反馈
if (!analysis.isCorrect) {
setWorkoutState(prev => ({
...prev,
feedback: analysis.suggestions,
qualityScore: calculateQualityScore(analysis),
}));
}
}
} catch (error) {
console.error('Frame analysis error:', error);
}
};
const analyzeExercise = (detectedPose: Pose, exercise: ExerciseType) => {
switch (exercise) {
case 'squat':
return squatAnalyzer.current.analyze(detectedPose, true);
// 可以添加其他动作的分析
default:
return { isCorrect: true, errors: [], suggestions: [] };
}
};
const calculateQualityScore = (analysis: any): number => {
if (analysis.isCorrect) return 100;
const errorPenalty = analysis.errors.length * 15;
return Math.max(0, 100 - errorPenalty);
};
const incrementRep = () => {
setWorkoutState(prev => ({
...prev,
repCount: prev.repCount + 1,
}));
};
return {
workoutState,
pose,
camera,
startAnalysis,
stopAnalysis,
analyzeFrame,
incrementRep,
};
}
Code collapsed
UI 组件实现
摄像头和姿态叠加层
code
// components/CameraWithPoseOverlay.tsx
import React, { useEffect, useRef } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { Camera, useCameraDevices } from 'react-native-vision-camera';
import { Canvas, Path, Skia } from '@shopify/react-native-skia';
import { Pose } from '../services/poseDetectionService';
interface CameraWithPoseOverlayProps {
onFrameProcessed: (imageData: ImageData) => void;
pose: Pose | null;
feedback: string[];
}
export function CameraWithPoseOverlay({ onFrameProcessed, pose, feedback }: CameraWithPoseOverlayProps) {
const devices = useCameraDevices();
const device = devices.back;
const camera = useRef<Camera>(null);
const frameProcessor = useRef<any>(null);
useEffect(() => {
if (!device) return;
frameProcessor.current = Camera.runAsync(frame => {
'worklet';
// 将相机帧转换为 ImageData
// 注意: 这需要在原生模块中处理
// 这里简化为调用回调
});
}, [device]);
// 绘制骨架连线
const drawSkeleton = () => {
if (!pose) return null;
const connections = [
['left_shoulder', 'right_shoulder'],
['left_shoulder', 'left_elbow'],
['left_elbow', 'left_wrist'],
['right_shoulder', 'right_elbow'],
['right_elbow', 'right_wrist'],
['left_shoulder', 'left_hip'],
['right_shoulder', 'right_hip'],
['left_hip', 'right_hip'],
['left_hip', 'left_knee'],
['left_knee', 'left_ankle'],
['right_hip', 'right_knee'],
['right_knee', 'right_ankle'],
];
return connections.map(([from, to], index) => {
const fromPoint = pose.keypoints.find(kp => kp.name === from);
const toPoint = pose.keypoints.find(kp => kp.name === to);
if (!fromPoint || !toPoint || fromPoint.score < 0.3 || toPoint.score < 0.3) {
return null;
}
return (
<Line
key={index}
p1={{ x: fromPoint.x * 375, y: fromPoint.y * 812 }}
p2={{ x: toPoint.x * 375, y: toPoint.y * 812 }}
color="#4A90E2"
strokeWidth={3}
/>
);
});
};
// 绘制关键点
const drawKeypoints = () => {
if (!pose) return null;
return pose.keypoints.map((keypoint, index) => {
if (keypoint.score < 0.3) return null;
return (
<Circle
key={index}
cx={keypoint.x * 375}
cy={keypoint.y * 812}
r={6}
color={keypoint.score > 0.5 ? '#4A90E2' : '#FF6B6B'}
/>
);
});
};
if (!device) {
return (
<View style={styles.container}>
<Text>加载相机中...</Text>
</View>
);
}
return (
<View style={styles.container}>
<Camera
ref={camera}
style={styles.camera}
device={device}
isActive={true}
frameProcessor={frameProcessor.current}
frameProcessorFps={30}
/>
{/* 姿态叠加层 */}
<Canvas style={styles.overlay}>
{drawSkeleton()}
{drawKeypoints()}
</Canvas>
{/* 反馈面板 */}
{feedback.length > 0 && (
<View style={styles.feedbackPanel}>
<Text style={styles.feedbackTitle}>动作建议</Text>
{feedback.map((item, index) => (
<Text key={index} style={styles.feedbackText}>• {item}</Text>
))}
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
overlay: { ...StyleSheet.absoluteFillObject },
feedbackPanel: {
position: 'absolute',
bottom: 100,
left: 20,
right: 20,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 12,
padding: 16,
},
feedbackTitle: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
feedbackText: {
color: '#fff',
fontSize: 14,
marginBottom: 4,
},
});
Code collapsed
性能优化建议
- 降低检测频率: 不需要每帧都检测,每 3-5 帧检测一次
- 使用轻量级模型: MoveNet Lightning 比 Thunder 更快
- GPU 加速: 确保 TensorFlow 使用 GPU 后端
- 帧尺寸优化: 使用较低分辨率 (如 640x480) 进行检测
code
// 性能优化示例
const SKIP_FRAMES = 3;
let frameCount = 0;
frameProcessor.current = Camera.runAsync(frame => {
'worklet';
frameCount++;
if (frameCount % SKIP_FRAMES === 0) {
// 只处理每第 3 帧
const imageData = resizeImage(frame, { width: 640, height: 480 });
onFrameProcessed(imageData);
}
});
Code collapsed
临床和训练建议
- 不是医疗设备: 此应用仅供训练参考,不能替代专业教练指导
- 个体差异: 不同体型和柔韧性的用户动作标准可能不同
- 渐进加载: 建议先掌握正确动作再增加重量
- 疼痛停止: 如有任何疼痛应立即停止并咨询专业人士
参考资料
- TensorFlow.js Pose Detection
- MoveNet Model Documentation
- ACE Personal Trainer Manual
- NSCA Essentials of Strength Training
作者: 康心伴技术团队 | 发布日期: 2026年3月8日 | 最后更新: 2026年3月8日