
鸿蒙AI健身动作矫正系统开发指南 原创
鸿蒙AI健身动作矫正系统开发指南
一、系统架构设计
基于HarmonyOS的AI健身动作矫正系统,利用分布式能力和姿态识别API,实现多设备协同的健身指导:
多设备协同:手机摄像头捕捉动作,大屏设备展示矫正反馈
实时分析:通过@ohos.ai.body进行姿态识别与动作分析
跨端同步:分布式数据同步确保多设备状态一致
3D展示:在智慧屏上展示标准动作与用户动作对比
!https://example.com/harmony-ai-fitness-arch.png
二、核心代码实现
姿态识别服务封装
// PoseDetectionService.ets
import body from ‘@ohos.ai.body’;
import image from ‘@ohos.multimedia.image’;
import common from ‘@ohos.app.ability.common’;
class PoseDetectionService {
private static instance: PoseDetectionService = null;
private bodyDetector: body.BodyDetector;
private isDetecting: boolean = false;
private detectionCallbacks: DetectionCallback[] = [];
private constructor() {
this.initBodyDetector();
public static getInstance(): PoseDetectionService {
if (!PoseDetectionService.instance) {
PoseDetectionService.instance = new PoseDetectionService();
return PoseDetectionService.instance;
private async initBodyDetector(): Promise<void> {
try {
const context = getContext() as common.Context;
this.bodyDetector = await body.createBodyDetector(context);
// 配置检测参数
const config: body.BodyDetectConfig = {
processMode: body.ProcessMode.PROCESS_MODE_VIDEO,
detectorMode: body.DetectorMode.DETECTOR_MODE_BACKGROUND,
preferredResolution: body.PreferredResolution.PREFERRED_RESOLUTION_1080P
};
await this.bodyDetector.setConfig(config);
catch (err) {
console.error('初始化姿态检测器失败:', JSON.stringify(err));
}
public async startDetection(imageSource: image.ImageSource): Promise<void> {
if (this.isDetecting || !this.bodyDetector) return;
this.isDetecting = true;
try {
// 创建图像数组
const images: image.Image[] = [];
const imageObj = await imageSource.createPixelMap();
images.push(imageObj);
// 开始检测
const results = await this.bodyDetector.detect(images);
if (results && results.length > 0) {
this.handleDetectionResult(results[0]);
} catch (err) {
console.error('姿态检测失败:', JSON.stringify(err));
finally {
this.isDetecting = false;
}
private handleDetectionResult(result: body.BodyDetectionResult): void {
if (!result || result.bodyList.length === 0) return;
// 获取第一个检测到的人体
const bodyInfo = result.bodyList[0];
const keyPoints = this.parseKeyPoints(bodyInfo.keyPoints);
// 通知所有回调
this.detectionCallbacks.forEach(callback => {
callback.onDetectionResult(keyPoints);
});
private parseKeyPoints(points: body.KeyPoint[]): Map<body.PointType, body.Point> {
const keyPointsMap = new Map<body.PointType, body.Point>();
points.forEach(point => {
keyPointsMap.set(point.pointType, {
x: point.coordinateX,
y: point.coordinateY,
score: point.score
});
});
return keyPointsMap;
public addDetectionCallback(callback: DetectionCallback): void {
if (!this.detectionCallbacks.includes(callback)) {
this.detectionCallbacks.push(callback);
}
public removeDetectionCallback(callback: DetectionCallback): void {
this.detectionCallbacks = this.detectionCallbacks.filter(cb => cb !== callback);
public async release(): Promise<void> {
if (this.bodyDetector) {
await this.bodyDetector.release();
this.bodyDetector = null;
}
interface DetectionCallback {
onDetectionResult(keyPoints: Map<body.PointType, body.Point>): void;
interface Point {
x: number;
y: number;
score: number;
export const poseService = PoseDetectionService.getInstance();
摄像头采集服务
// CameraService.ets
import camera from ‘@ohos.multimedia.camera’;
import image from ‘@ohos.multimedia.image’;
import { poseService } from ‘./PoseDetectionService’;
class CameraService {
private static instance: CameraService = null;
private cameraManager: camera.CameraManager;
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private imageReceiver: image.ImageReceiver | null = null;
private captureSession: camera.CaptureSession | null = null;
private constructor() {
this.initCameraManager();
public static getInstance(): CameraService {
if (!CameraService.instance) {
CameraService.instance = new CameraService();
return CameraService.instance;
private async initCameraManager(): Promise<void> {
try {
this.cameraManager = await camera.getCameraManager(getContext(this));
catch (err) {
console.error('初始化相机管理器失败:', JSON.stringify(err));
}
public async startCameraPreview(surfaceId: string): Promise<void> {
if (!this.cameraManager) return;
try {
// 获取摄像头列表
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
throw new Error('未找到可用摄像头');
// 使用后置摄像头
const cameraDevice = cameras.find(c => c.position === camera.CameraPosition.CAMERA_POSITION_BACK) || cameras[0];
// 创建相机输入
this.cameraInput = this.cameraManager.createCameraInput(cameraDevice);
await this.cameraInput.open();
// 创建预览输出
this.previewOutput = this.cameraManager.createPreviewOutput(surfaceId);
// 创建图像接收器用于姿态分析
this.imageReceiver = image.createImageReceiver(
1080, 1920,
image.ImageFormat.JPEG,
);
const captureOutput = this.cameraManager.createPhotoOutput(this.imageReceiver);
// 创建会话
this.captureSession = this.cameraManager.createCaptureSession();
await this.captureSession.beginConfig();
// 添加输入输出
await this.captureSession.addInput(this.cameraInput);
await this.captureSession.addOutput(this.previewOutput);
await this.captureSession.addOutput(captureOutput);
await this.captureSession.commitConfig();
await this.captureSession.start();
// 开始帧捕获
this.startFrameCapture();
catch (err) {
console.error('启动相机预览失败:', JSON.stringify(err));
this.release();
}
private async startFrameCapture(): Promise<void> {
if (!this.imageReceiver) return;
this.imageReceiver.on('imageArrival', async () => {
try {
const img = await this.imageReceiver.readNextImage();
const imageSource = image.createImageSource(img);
await poseService.startDetection(imageSource);
img.release();
catch (err) {
console.error('处理图像帧失败:', JSON.stringify(err));
});
public async release(): Promise<void> {
if (this.captureSession) {
await this.captureSession.stop();
this.captureSession = null;
if (this.cameraInput) {
await this.cameraInput.close();
this.cameraInput = null;
this.previewOutput = null;
this.imageReceiver = null;
}
export const cameraService = CameraService.getInstance();
动作矫正分析服务
// MotionAnalysisService.ets
import { poseService } from ‘./PoseDetectionService’;
import distributedData from ‘@ohos.distributedData’;
class MotionAnalysisService {
private static instance: MotionAnalysisService = null;
private currentExercise: Exercise | null = null;
private analysisCallbacks: AnalysisCallback[] = [];
private dataManager: distributedData.DataManager;
private constructor() {
this.initDataManager();
this.registerPoseListener();
public static getInstance(): MotionAnalysisService {
if (!MotionAnalysisService.instance) {
MotionAnalysisService.instance = new MotionAnalysisService();
return MotionAnalysisService.instance;
private initDataManager(): void {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.aifitness',
area: distributedData.Area.GLOBAL,
isEncrypted: true
});
// 监听矫正数据同步
this.dataManager.registerDataListener('motion_correction', (data) => {
this.handleSyncData(data);
});
private registerPoseListener(): void {
poseService.addDetectionCallback({
onDetectionResult: (keyPoints) => {
this.analyzePose(keyPoints);
});
public setCurrentExercise(exercise: Exercise): void {
this.currentExercise = exercise;
// 同步到其他设备
this.syncExercise(exercise);
private analyzePose(keyPoints: Map<body.PointType, body.Point>): void {
if (!this.currentExercise) return;
// 获取关键点
const leftShoulder = keyPoints.get(body.PointType.POINT_TYPE_SHOULDER_LEFT);
const rightShoulder = keyPoints.get(body.PointType.POINT_TYPE_SHOULDER_RIGHT);
const leftElbow = keyPoints.get(body.PointType.POINT_TYPE_ELBOW_LEFT);
const rightElbow = keyPoints.get(body.PointType.POINT_TYPE_ELBOW_RIGHT);
const leftWrist = keyPoints.get(body.PointType.POINT_TYPE_WRIST_LEFT);
const rightWrist = keyPoints.get(body.PointType.POINT_TYPE_WRIST_RIGHT);
const leftHip = keyPoints.get(body.PointType.POINT_TYPE_HIP_LEFT);
const rightHip = keyPoints.get(body.PointType.POINT_TYPE_HIP_RIGHT);
const leftKnee = keyPoints.get(body.PointType.POINT_TYPE_KNEE_LEFT);
const rightKnee = keyPoints.get(body.PointType.POINT_TYPE_KNEE_RIGHT);
const leftAnkle = keyPoints.get(body.PointType.POINT_TYPE_ANKLE_LEFT);
const rightAnkle = keyPoints.get(body.PointType.POINT_TYPE_ANKLE_RIGHT);
// 根据当前训练动作进行分析
let corrections: Correction[] = [];
switch (this.currentExercise.id) {
case 'squat':
corrections = this.analyzeSquat(
leftShoulder, rightShoulder,
leftHip, rightHip,
leftKnee, rightKnee,
leftAnkle, rightAnkle
);
break;
case 'pushup':
corrections = this.analyzePushup(
leftShoulder, rightShoulder,
leftElbow, rightElbow,
leftWrist, rightWrist,
leftHip, rightHip
);
break;
case 'lunge':
corrections = this.analyzeLunge(
leftHip, rightHip,
leftKnee, rightKnee,
leftAnkle, rightAnkle
);
break;
// 通知回调
this.analysisCallbacks.forEach(callback => {
callback.onAnalysisResult(corrections);
});
// 同步矫正数据
this.syncCorrections(corrections);
private analyzeSquat(
leftShoulder: Point | undefined,
rightShoulder: Point | undefined,
leftHip: Point | undefined,
rightHip: Point | undefined,
leftKnee: Point | undefined,
rightKnee: Point | undefined,
leftAnkle: Point | undefined,
rightAnkle: Point | undefined
): Correction[] {
const corrections: Correction[] = [];
// 检查膝盖是否超过脚尖
if (leftKnee && leftAnkle && leftKnee.x < leftAnkle.x) {
corrections.push({
part: 'left_knee',
message: '左膝盖超过脚尖,请调整',
severity: 'high'
});
if (rightKnee && rightAnkle && rightKnee.x < rightAnkle.x) {
corrections.push({
part: 'right_knee',
message: '右膝盖超过脚尖,请调整',
severity: 'high'
});
// 检查背部是否挺直
if (leftShoulder && rightShoulder && leftHip && rightHip) {
const shoulderMidY = (leftShoulder.y + rightShoulder.y) / 2;
const hipMidY = (leftHip.y + rightHip.y) / 2;
if (Math.abs(shoulderMidY - hipMidY) < 50) {
corrections.push({
part: 'back',
message: '背部未挺直,请保持脊柱中立',
severity: 'medium'
});
}
// 检查膝盖内扣
if (leftKnee && rightKnee && leftAnkle && rightAnkle) {
const kneeDistance = Math.abs(leftKnee.x - rightKnee.x);
const ankleDistance = Math.abs(leftAnkle.x - rightAnkle.x);
if (kneeDistance < ankleDistance * 0.7) {
corrections.push({
part: 'knees',
message: '膝盖内扣,请保持膝盖与脚尖方向一致',
severity: 'high'
});
}
return corrections;
private analyzePushup(
leftShoulder: Point | undefined,
rightShoulder: Point | undefined,
leftElbow: Point | undefined,
rightElbow: Point | undefined,
leftWrist: Point | undefined,
rightWrist: Point | undefined,
leftHip: Point | undefined,
rightHip: Point | undefined
): Correction[] {
const corrections: Correction[] = [];
// 检查身体是否成直线
if (leftShoulder && rightShoulder && leftHip && rightHip && leftAnkle && rightAnkle) {
const shoulderMidY = (leftShoulder.y + rightShoulder.y) / 2;
const hipMidY = (leftHip.y + rightHip.y) / 2;
const ankleMidY = (leftAnkle.y + rightAnkle.y) / 2;
const angle1 = this.calculateAngle(shoulderMidY, hipMidY, ankleMidY);
if (Math.abs(angle1 - 180) > 10) {
corrections.push({
part: 'body',
message: '身体未成直线,收紧核心',
severity: 'high'
});
}
// 检查肘部角度
if (leftShoulder && leftElbow && leftWrist) {
const angle = this.calculateAngle(
leftShoulder.x, leftShoulder.y,
leftElbow.x, leftElbow.y,
leftWrist.x, leftWrist.y
);
if (angle < 70) {
corrections.push({
part: 'left_elbow',
message: '左肘部角度过小,可能导致肩部压力过大',
severity: 'medium'
});
}
if (rightShoulder && rightElbow && rightWrist) {
const angle = this.calculateAngle(
rightShoulder.x, rightShoulder.y,
rightElbow.x, rightElbow.y,
rightWrist.x, rightWrist.y
);
if (angle < 70) {
corrections.push({
part: 'right_elbow',
message: '右肘部角度过小,可能导致肩部压力过大',
severity: 'medium'
});
}
return corrections;
private calculateAngle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): number {
const v1x = x1 - x2;
const v1y = y1 - y2;
const v2x = x3 - x2;
const v2y = y3 - y2;
const dot = v1x v2x + v1y v2y;
const mag1 = Math.sqrt(v1x v1x + v1y v1y);
const mag2 = Math.sqrt(v2x v2x + v2y v2y);
const angle = Math.acos(dot / (mag1 mag2)) (180 / Math.PI);
return angle;
private syncExercise(exercise: Exercise): void {
this.dataManager.syncData('motion_correction', {
type: 'exercise',
exercise: exercise,
timestamp: Date.now()
});
private syncCorrections(corrections: Correction[]): void {
this.dataManager.syncData('motion_correction', {
type: 'corrections',
corrections: corrections,
timestamp: Date.now()
});
private handleSyncData(data: any): void {
if (!data) return;
switch (data.type) {
case 'exercise':
this.currentExercise = data.exercise;
break;
case 'corrections':
this.analysisCallbacks.forEach(callback => {
callback.onAnalysisResult(data.corrections);
});
break;
}
public addAnalysisCallback(callback: AnalysisCallback): void {
if (!this.analysisCallbacks.includes(callback)) {
this.analysisCallbacks.push(callback);
}
public removeAnalysisCallback(callback: AnalysisCallback): void {
this.analysisCallbacks = this.analysisCallbacks.filter(cb => cb !== callback);
}
interface AnalysisCallback {
onAnalysisResult(corrections: Correction[]): void;
interface Exercise {
id: string;
name: string;
description: string;
difficulty: ‘beginner’ ‘intermediate’
‘advanced’;
interface Correction {
part: string;
message: string;
severity: ‘low’ ‘medium’
‘high’;
export const motionService = MotionAnalysisService.getInstance();
手机端摄像头界面
// PhoneCameraView.ets
import { cameraService } from ‘./CameraService’;
import { motionService } from ‘./MotionAnalysisService’;
@Component
export struct PhoneCameraView {
@State currentExercise: Exercise | null = null;
@State corrections: Correction[] = [];
@State surfaceId: string = ‘’;
aboutToAppear() {
// 创建XComponent表面
this.surfaceId = ‘camera_preview_’ + Date.now();
// 监听矫正结果
motionService.addAnalysisCallback({
onAnalysisResult: (corrections) => {
this.corrections = corrections;
});
build() {
Stack() {
// 相机预览
XComponent({
id: this.surfaceId,
type: 'surface',
libraryname: 'camera_preview'
})
.width('100%')
.height('100%')
.onLoad(() => {
cameraService.startCameraPreview(this.surfaceId);
})
// 矫正提示
Column() {
ForEach(this.corrections, (correction) => {
if (correction.severity === 'high') {
Text(correction.message)
.fontSize(16)
.fontColor(Color.Red)
.backgroundColor(Color.White)
.padding(10)
.borderRadius(8)
.margin({ bottom: 10 })
})
.width(‘100%’)
.alignItems(HorizontalAlign.Start)
.padding(20)
// 底部控制栏
Column() {
// 练习选择器
ExerciseSelector({
onExerciseSelect: (exercise) => {
this.currentExercise = exercise;
motionService.setCurrentExercise(exercise);
})
// 控制按钮
Row() {
Button('切换摄像头')
.width('40%')
.height(50)
.margin({ right: 10 })
Button('结束训练')
.width('40%')
.height(50)
.onClick(() => {
cameraService.release();
router.back();
})
.margin({ top: 20 })
.width(‘100%’)
.position({ bottom: 30 })
.width(‘100%’)
.height('100%')
.onDisappear(() => {
cameraService.release();
})
}
@Component
struct ExerciseSelector {
@Link onExerciseSelect: (exercise: Exercise) => void;
@State exercises: Exercise[] = [
id: ‘squat’,
name: '深蹲',
description: '锻炼下肢力量的基础动作',
difficulty: 'beginner'
},
id: ‘pushup’,
name: '俯卧撑',
description: '锻炼上肢和核心力量的标准动作',
difficulty: 'beginner'
},
id: ‘lunge’,
name: '弓步蹲',
description: '单侧下肢训练动作',
difficulty: 'intermediate'
];
@State selectedIndex: number = 0;
build() {
Row() {
Text(‘当前练习:’)
.fontSize(16)
.margin({ right: 10 })
Select(this.exercises.map(ex => ex.name))
.selected(this.selectedIndex)
.onSelect((index) => {
this.selectedIndex = index;
this.onExerciseSelect(this.exercises[index]);
})
.padding(10)
.backgroundColor(Color.White)
.borderRadius(8)
}
大屏3D矫正视图
// TVCorrectionView.ets
import { motionService } from ‘./MotionAnalysisService’;
import { poseService } from ‘./PoseDetectionService’;
@Component
export struct TVCorrectionView {
@State currentExercise: Exercise | null = null;
@State corrections: Correction[] = [];
@State standardPose: Map<body.PointType, Point> = new Map();
@State userPose: Map<body.PointType, Point> = new Map();
aboutToAppear() {
// 监听练习变化
motionService.addAnalysisCallback({
onAnalysisResult: (corrections) => {
this.corrections = corrections;
});
// 监听姿态变化
poseService.addDetectionCallback({
onDetectionResult: (keyPoints) => {
this.userPose = keyPoints;
});
build() {
Column() {
// 标题
Text(this.currentExercise?.name || '请选择训练动作')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
// 3D对比视图
Row() {
// 标准动作
Column() {
Text('标准动作')
.fontSize(18)
.margin({ bottom: 10 })
PoseView(this.standardPose, '#4CAF50')
.width('90%')
.height(300)
.width(‘50%’)
// 用户动作
Column() {
Text('你的动作')
.fontSize(18)
.margin({ bottom: 10 })
PoseView(this.userPose, '#2196F3')
.width('90%')
.height(300)
.width(‘50%’)
.margin({ bottom: 20 })
// 矫正提示
if (this.corrections.length > 0) {
Column() {
Text('矫正建议:')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
ForEach(this.corrections, (correction) => {
Row() {
Image(this.getSeverityIcon(correction.severity))
.width(20)
.height(20)
.margin({ right: 10 })
Text(correction.message)
.fontSize(16)
.margin({ bottom: 8 })
})
.width(‘90%’)
.padding(20)
.backgroundColor('#FFF3E0')
.borderRadius(8)
else if (this.currentExercise) {
Text('动作标准,继续保持!')
.fontSize(18)
.fontColor('#4CAF50')
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
.onAppear(() => {
// 加载标准姿势
this.loadStandardPose();
})
private loadStandardPose(): void {
// 根据当前训练动作加载标准姿势
if (!this.currentExercise) return;
const standardPose = new Map<body.PointType, Point>();
switch (this.currentExercise.id) {
case 'squat':
// 深蹲标准姿势关键点
standardPose.set(body.PointType.POINT_TYPE_SHOULDER_LEFT, { x: 300, y: 200, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_SHOULDER_RIGHT, { x: 500, y: 200, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_HIP_LEFT, { x: 320, y: 400, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_HIP_RIGHT, { x: 480, y: 400, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_KNEE_LEFT, { x: 350, y: 600, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_KNEE_RIGHT, { x: 450, y: 600, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_ANKLE_LEFT, { x: 350, y: 800, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_ANKLE_RIGHT, { x: 450, y: 800, score: 1 });
break;
case 'pushup':
// 俯卧撑标准姿势关键点
standardPose.set(body.PointType.POINT_TYPE_SHOULDER_LEFT, { x: 300, y: 300, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_SHOULDER_RIGHT, { x: 500, y: 300, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_ELBOW_LEFT, { x: 250, y: 400, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_ELBOW_RIGHT, { x: 550, y: 400, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_WRIST_LEFT, { x: 200, y: 500, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_WRIST_RIGHT, { x: 600, y: 500, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_HIP_LEFT, { x: 350, y: 400, score: 1 });
standardPose.set(body.PointType.POINT_TYPE_HIP_RIGHT, { x: 450, y: 400, score: 1 });
break;
this.standardPose = standardPose;
private getSeverityIcon(severity: string): Resource {
switch (severity) {
case 'high': return $r('app.media.ic_error');
case 'medium': return $r('app.media.ic_warning');
default: return $r('app.media.ic_info');
}
@Component
struct PoseView {
private pose: Map<body.PointType, Point>;
private color: string;
build() {
Canvas()
.width(‘100%’)
.height(‘100%’)
.onReady((context: CanvasRenderingContext2D) => {
this.drawPose(context);
})
private drawPose(ctx: CanvasRenderingContext2D): void {
if (!this.pose || this.pose.size === 0) return;
const width = ctx.width;
const height = ctx.height;
// 清空画布
ctx.clearRect(0, 0, width, height);
// 绘制骨骼连线
this.drawBone(ctx,
body.PointType.POINT_TYPE_SHOULDER_LEFT,
body.PointType.POINT_TYPE_ELBOW_LEFT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_ELBOW_LEFT,
body.PointType.POINT_TYPE_WRIST_LEFT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_SHOULDER_RIGHT,
body.PointType.POINT_TYPE_ELBOW_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_ELBOW_RIGHT,
body.PointType.POINT_TYPE_WRIST_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_SHOULDER_LEFT,
body.PointType.POINT_TYPE_SHOULDER_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_SHOULDER_LEFT,
body.PointType.POINT_TYPE_HIP_LEFT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_SHOULDER_RIGHT,
body.PointType.POINT_TYPE_HIP_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_HIP_LEFT,
body.PointType.POINT_TYPE_HIP_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_HIP_LEFT,
body.PointType.POINT_TYPE_KNEE_LEFT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_HIP_RIGHT,
body.PointType.POINT_TYPE_KNEE_RIGHT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_KNEE_LEFT,
body.PointType.POINT_TYPE_ANKLE_LEFT,
width, height
);
this.drawBone(ctx,
body.PointType.POINT_TYPE_KNEE_RIGHT,
body.PointType.POINT_TYPE_ANKLE_RIGHT,
width, height
);
// 绘制关节点
this.pose.forEach((point, type) => {
if (point.score > 0.3) { // 置信度阈值
const x = point.x * width / 1000; // 假设坐标范围0-1000
const y = point.y * height / 1000;
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
});
private drawBone(
ctx: CanvasRenderingContext2D,
type1: body.PointType,
type2: body.PointType,
width: number,
height: number
): void {
const point1 = this.pose.get(type1);
const point2 = this.pose.get(type2);
if (point1 && point2 && point1.score > 0.3 && point2.score > 0.3) {
const x1 = point1.x * width / 1000;
const y1 = point1.y * height / 1000;
const x2 = point2.x * width / 1000;
const y2 = point2.y * height / 1000;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = this.color;
ctx.lineWidth = 3;
ctx.stroke();
}
三、项目配置与权限
权限配置
// module.json5
“module”: {
"requestPermissions": [
“name”: “ohos.permission.CAMERA”,
"reason": "使用摄像头捕捉动作"
},
“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,
"reason": "同步矫正数据"
},
“name”: “ohos.permission.INTERNET”,
"reason": "加载标准动作数据"
},
“name”: “ohos.permission.READ_MEDIA”,
"reason": "读取媒体文件"
},
“name”: “ohos.permission.MEDIA_LOCATION”,
"reason": "访问媒体位置信息"
},
“name”: “ohos.permission.ACCESS_DISTRIBUTED_DEVICE_MANAGER”,
"reason": "发现和连接其他设备"
],
"abilities": [
“name”: “MainAbility”,
"type": "page",
"visible": true
},
“name”: “CameraAbility”,
"type": "page",
"visible": true
},
“name”: “TVAbility”,
"type": "page",
"visible": true
},
“name”: “PoseDetectionAbility”,
"type": "service",
"backgroundModes": ["dataTransfer"]
]
}
资源文件
// resources/base/media/media.json
“media”: [
“name”: “ic_error”,
"type": "svg",
"src": "media/error.svg"
},
“name”: “ic_warning”,
"type": "svg",
"src": "media/warning.svg"
},
“name”: “ic_info”,
"type": "svg",
"src": "media/info.svg"
},
“name”: “squat_pose”,
"type": "png",
"src": "media/squat.png"
},
“name”: “pushup_pose”,
"type": "png",
"src": "media/pushup.png"
},
“name”: “lunge_pose”,
"type": "png",
"src": "media/lunge.png"
]
四、总结与扩展
本AI健身动作矫正系统实现了以下核心功能:
实时姿态识别:通过@ohos.ai.body精准捕捉人体关键点
智能分析:针对不同训练动作提供专业矫正建议
多设备协同:手机捕捉动作,大屏展示3D对比与矫正提示
分布式同步:实时同步训练状态与矫正数据
扩展方向:
训练计划:根据用户水平制定个性化训练计划
进度追踪:记录训练数据并可视化进步曲线
社交功能:分享训练成果,好友间挑战
VR集成:结合VR设备提供沉浸式训练体验
健康数据:接入穿戴设备监测心率等健康指标
通过HarmonyOS的分布式能力与AI框架,我们构建了一个智能、互联的健身指导系统,让家庭健身更加科学高效。
