康心伴Logo
康心伴WellAlly
Health

使用 React Native 和 TensorFlow 构建健身动作纠正应用

5 分钟阅读

使用 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

性能优化建议

  1. 降低检测频率: 不需要每帧都检测,每 3-5 帧检测一次
  2. 使用轻量级模型: MoveNet Lightning 比 Thunder 更快
  3. GPU 加速: 确保 TensorFlow 使用 GPU 后端
  4. 帧尺寸优化: 使用较低分辨率 (如 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

临床和训练建议

  1. 不是医疗设备: 此应用仅供训练参考,不能替代专业教练指导
  2. 个体差异: 不同体型和柔韧性的用户动作标准可能不同
  3. 渐进加载: 建议先掌握正确动作再增加重量
  4. 疼痛停止: 如有任何疼痛应立即停止并咨询专业人士

参考资料


作者: 康心伴技术团队 | 发布日期: 2026年3月8日 | 最后更新: 2026年3月8日

免责声明: 本内容仅供教育参考,不能替代专业医疗建议。请咨询医生获取个性化诊断和治疗方案。

#

文章标签

health
wellness
react-native
tensorflow
ai
fitness
machine-learning

觉得这篇文章有帮助?

立即体验康心伴,开始您的健康管理之旅