HarmonyOS 6(API 23)实战:基于 Face AR & Body AR 打造沉浸式“虚实融合健身镜“应用

文章目录


每日一句正能量

心随春风暖,景随春光柔。行于温柔的春日,静看繁花满枝头。花色润了心境,花香醉了温柔。不忧花期短长,心有芬芳自赏。一路向阳而行,四季皆有花香。

前言

摘要:HarmonyOS 6(API 23)重磅引入了 Face AR 与 Body AR 能力,为开发者提供了人脸微表情追踪(BlendShape)和人体骨骼关键点识别两大核心能力。本文将带你从零实战,基于 AR Engine 6.1.0 开发一款"虚实融合健身镜"应用,实现实时面部特效叠加、人体姿态检测与运动计数,探索空间交互的无限可能。


一、Face AR & Body AR 能力概述与技术亮点

HarmonyOS 6.1.0(API 23)在 AR Engine 中新增了 Face ARBody AR 两大核心模块 :

能力模块 核心特性 技术参数
Face AR 人脸位姿跟踪、Mesh 建模、微表情(BlendShape)跟踪 4000+ 顶点、7000+ 三角面片、64 种表情参数
Body AR 人体骨骼关键点跟踪、轮廓虚实遮挡 20+ 骨骼关键点、支持单人/多人追踪

技术亮点

  • 端侧实时处理:所有图像数据仅在端侧处理,不上传云端,保障用户隐私安全
  • 高精度 BlendShape:支持眼睛、眉毛、眼球、嘴巴、舌头等 64 种微表情参数实时输出
  • 多模态融合:Face AR 与 Body AR 可并发运行,实现"面部+肢体"的全方位空间交互
  • ARView 组件化 :通过 @hms.core.ar.arview 提供声明式 AR 视图,降低 3D 渲染门槛

二、项目实战:"虚实融合健身镜"架构设计

2.1 应用场景与功能规划

本应用面向家庭健身场景,核心功能包括:

  1. AR 面部特效:实时叠加虚拟面具、动态贴纸,增强运动趣味性
  2. 姿态检测与计数:识别深蹲、开合跳等动作,自动计数并纠正姿势
  3. 虚实融合渲染:虚拟教练形象与真人同屏展示,提供动作指导

2.2 技术架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│                    UI Layer (ArkUI)                       │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐ │
│  │ XComponent  │  │  ARView     │  │  StatsOverlay   │ │
│  │ (相机预览)   │  │ (3D渲染)    │  │  (运动数据面板)  │ │
│  └──────┬──────┘  └──────┬──────┘  └─────────────────┘ │
└─────────┼────────────────┼─────────────────────────────┘
          │                │
┌─────────▼────────────────▼─────────────────────────────┐
│              AR Engine 6.1.0 (API 23)                   │
│  ┌─────────────────┐      ┌─────────────────────────┐  │
│  │   Face AR Track  │      │    Body AR Track         │  │
│  │  · ARFaceAnchor   │      │  · ARBodySkeleton        │  │
│  │  · ARBlendShapes  │      │  · ARBodyLandmark2D      │  │
│  │  · ARGeometry     │      │  · acquireBodySkeleton() │  │
│  └─────────────────┘      └─────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

三、环境配置与权限声明

3.1 模块依赖配置

oh-package.json5 中添加 AR Engine 依赖:

json 复制代码
{
  "dependencies": {
    "@hms.core.ar.arengine": "^6.1.0",
    "@hms.core.ar.arview": "^6.1.0"
  }
}

3.2 权限声明(module.json5)

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_permission_reason"
      },
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:network_permission_reason"
      }
    ]
  }
}

注意:AR Engine 所有数据处理均在端侧完成,无需网络权限即可运行核心功能 。


四、核心代码实战

4.1 AR 会话初始化与双模态配置

代码亮点 :HarmonyOS 6 支持通过 ARFeatureType 同时启用 Face 和 Body 追踪,并可通过 ARConfig 配置多脸模式和最大检测人体数量。

typescript 复制代码
// arengine/ARSessionManager.ets
import { arEngine, ARConfig, ARFeatureType, ARMultiFaceMode } from '@hms.core.ar.arengine';

export class ARSessionManager {
  private session: arEngine.ARSession | null = null;
  private config: ARConfig | null = null;

  /**
   * 初始化AR会话,同时启用Face AR和Body AR
   * 关键配置:
   * 1. featureType: 同时启用 FACE | BODY 能力
   * 2. multiFaceMode: 支持多人脸追踪(健身镜场景可能需要双人PK)
   * 3. maxDetectedBodyNum: 最大检测人体数,建议设为2(支持双人健身)
   */
  async initialize(context: Context): Promise<void> {
    try {
      // 检查AR Engine是否已安装
      const isReady = await arEngine.isAREngineReady(context);
      if (!isReady) {
        // 引导用户前往应用市场安装AR Engine
        await this.promptInstallAREngine(context);
        return;
      }

      // 创建AR会话
      this.session = new arEngine.ARSession(context);
      
      // 配置双模态追踪
      this.config = new ARConfig();
      this.config.featureType = ARFeatureType.ARENGINE_FEATURE_TYPE_FACE | 
                               ARFeatureType.ARENGINE_FEATURE_TYPE_BODY;
      
      // 多人脸模式:支持双人同时健身时的面部特效
      this.config.multiFaceMode = ARMultiFaceMode.MULTIFACE_ENABLE;
      
      // 最大检测人体数:2人(适合家庭双人健身场景)
      this.config.maxDetectedBodyNum = 2;
      
      // 相机镜头:前置摄像头(健身镜场景)
      this.config.cameraLensFacing = arEngine.ARCameraLensFacing.FRONT;

      // 配置并启动会话
      this.session.configure(this.config);
      await this.session.start();
      
      console.info('AR Session 启动成功,Face AR + Body AR 双模态已就绪');
    } catch (error) {
      console.error('AR Session 初始化失败:', error);
      throw error;
    }
  }

  /**
   * 获取当前帧的AR数据(Face + Body)
   */
  acquireFrameData(): { faces: arEngine.ARFaceAnchor[], bodies: arEngine.ARBody[] } | null {
    if (!this.session) return null;

    const frame = this.session.acquireFrame();
    if (!frame) return null;

    // 获取人脸追踪数据
    const faceAnchors = frame.getFaceAnchors ? frame.getFaceAnchors() : [];
    
    // 获取人体骨骼数据(HarmonyOS 6新增API)
    const bodies = frame.acquireBodySkeleton ? frame.acquireBodySkeleton() : [];

    return { faces: faceAnchors, bodies };
  }

  /**
   * 释放资源
   */
  async release(): Promise<void> {
    if (this.session) {
      await this.session.stop();
      this.session = null;
    }
  }

  private async promptInstallAREngine(context: Context): Promise<void> {
    // 跳转应用市场下载AR Engine
    // 具体实现略...
  }
}

4.2 Face AR:微表情追踪与虚拟面具渲染

代码亮点 :通过 ARBlendShapes 获取 64 种表情参数,驱动 3D 虚拟面具实时形变;ARLandmark 提供 2D/3D 关键点用于贴纸精准贴合。

typescript 复制代码
// face/FaceARController.ets
import { arEngine, ARFaceAnchor, ARBlendShapeType } from '@hms.core.ar.arengine';
import { ARViewContext, LandmarkType } from '@hms.core.ar.arview';

export class FaceARController {
  private arViewContext: ARViewContext | null = null;

  /**
   * 处理人脸追踪数据,驱动虚拟面具
   * @param faceAnchor 人脸锚点数据
   */
  processFaceTracking(faceAnchor: ARFaceAnchor): void {
    const face = faceAnchor.getFace();
    if (!face) return;

    // 1. 获取人脸几何网格(4000+顶点,用于3D面具贴合)
    const geometry = face.getGeometry();
    console.info(`人脸Mesh顶点数: ${geometry.verticesSize}, 三角面数: ${geometry.triangleIndicesCount}`);

    // 2. 获取微表情BlendShape参数(64种表情系数)
    const blendShapes = face.getBlendShapes();
    const shapeData = blendShapes.getData();
    const shapeTypes = blendShapes.getTypes();

    // 解析关键表情参数
    const expressionMap = this.parseBlendShapes(shapeTypes, shapeData);
    
    // 3. 获取人脸关键点(用于2D贴纸定位)
    const landmark = face.getLandmark();
    const vertices2D = landmark.getVertices2D(); // 2D屏幕坐标
    const vertices3D = landmark.getVertices3D(); // 3D空间坐标

    // 4. 驱动虚拟形象
    this.driveVirtualAvatar(expressionMap, vertices3D);
    
    // 5. 叠加动态贴纸(如运动时的汗水、火焰特效)
    this.overlayDynamicStickers(vertices2D, expressionMap);
  }

  /**
   * 解析BlendShape数据为可读Map
   * 重点关注健身场景的表情:张嘴呼吸、眨眼、皱眉等
   */
  private parseBlendShapes(types: ARBlendShapeType[], data: ArrayBuffer): Map<ARBlendShapeType, number> {
    const result = new Map<ARBlendShapeType, number>();
    const floatView = new Float32Array(data);
    
    types.forEach((type, index) => {
      result.set(type, floatView[index]);
    });

    // 输出关键表情参数(调试用)
    console.info(`左眼眨眼: ${result.get(ARBlendShapeType.EYE_BLINK_LEFT)?.toFixed(3)}`);
    console.info(`张嘴程度: ${result.get(ARBlendShapeType.JAW_OPEN)?.toFixed(3)}`);
    
    return result;
  }

  /**
   * 驱动虚拟形象表情
   * 利用ARView的setBlendShapeWeight将真实表情映射到3D模型
   */
  private driveVirtualAvatar(expressionMap: Map<ARBlendShapeType, number>, vertices3D: ArrayBuffer): void {
    if (!this.arViewContext) return;

    const avatarNode = this.arViewContext.getScene().getNodeByName('avatar_root');
    if (!avatarNode) return;

    // 映射真实表情到虚拟形象
    expressionMap.forEach((weight, type) => {
      // setBlendShapeWeight: 将真实人脸的blendshape权重应用到3D模型对应节点
      this.arViewContext!.setBlendShapeWeight(avatarNode, type, weight);
    });
  }

  /**
   * 根据表情状态叠加动态贴纸
   * 例如:检测到高强度运动时(张嘴+皱眉),触发火焰特效
   */
  private async overlayDynamicStickers(vertices2D: ArrayBuffer, expressionMap: Map<ARBlendShapeType, number>): Promise<void> {
    if (!this.arViewContext) return;

    // 检测运动强度(通过嘴部开合和眉毛动作判断)
    const jawOpen = expressionMap.get(ARBlendShapeType.JAW_OPEN) || 0;
    const browDown = expressionMap.get(ARBlendShapeType.BROW_DOWN_LEFT) || 0;
    
    const isHighIntensity = jawOpen > 0.6 && browDown > 0.4;

    if (isHighIntensity) {
      // 在额头位置加载火焰特效贴纸
      await this.arViewContext.loadAsset('assets/effects/fire_sticker.gltf', LandmarkType.CENTER_OF_FACE);
    } else {
      // 移除特效
      await this.arViewContext.removeAsset(LandmarkType.CENTER_OF_FACE);
    }
  }

  setARViewContext(context: ARViewContext): void {
    this.arViewContext = context;
  }
}

4.3 Body AR:骨骼关键点识别与动作计数

代码亮点ARBody 提供 20+ 骨骼关键点(含 2D 坐标、置信度、关键点类型),支持实时姿态估计与动作计数。

typescript 复制代码
// body/BodyARController.ets
import { arEngine, ARBody, ARBodyLandmarkType, ARBodyLandmark2D } from '@hms.core.ar.arengine';

/**
 * 动作类型枚举
 */
export enum ExerciseType {
  SQUAT = 'squat',       // 深蹲
  JUMPING_JACK = 'jumping_jack', // 开合跳
  HIGH_KNEE = 'high_knee'  // 高抬腿
}

/**
 * 动作状态机
 */
export enum MotionState {
  IDLE = 'idle',
  PREPARING = 'preparing',
  EXECUTING = 'executing',
  COMPLETING = 'completing'
}

export class BodyARController {
  private currentExercise: ExerciseType = ExerciseType.SQUAT;
  private motionState: MotionState = MotionState.IDLE;
  private repCount: number = 0;
  private lastStateTime: number = 0;

  // 动作阈值配置
  private readonly SQUAT_THRESHOLD = {
    hipKneeAngleMin: 80,   // 深蹲到底角度
    hipKneeAngleMax: 160,    // 站立角度
    confidenceMin: 0.7       // 最小置信度
  };

  /**
   * 处理人体骨骼数据
   * @param bodies 检测到的人体数组(支持多人)
   */
  processBodyTracking(bodies: ARBody[]): void {
    if (bodies.length === 0) {
      console.info('未检测到人体');
      return;
    }

    // 取置信度最高的人体进行追踪(健身镜通常聚焦单人)
    const primaryBody = this.selectPrimaryBody(bodies);
    const landmarks = primaryBody.getLandmarks2D();

    // 过滤低置信度关键点
    const validLandmarks = landmarks.filter(lm => lm.confidence > 0.6);

    // 根据当前训练类型执行动作识别
    switch (this.currentExercise) {
      case ExerciseType.SQUAT:
        this.detectSquat(validLandmarks);
        break;
      case ExerciseType.JUMPING_JACK:
        this.detectJumpingJack(validLandmarks);
        break;
      case ExerciseType.HIGH_KNEE:
        this.detectHighKnee(validLandmarks);
        break;
    }
  }

  /**
   * 深蹲动作识别算法
   * 核心逻辑:通过髋-膝-踝三点计算膝关节角度,判断下蹲深度
   */
  private detectSquat(landmarks: ARBodyLandmark2D[]): void {
    // 提取关键骨骼点
    const leftHip = this.findLandmark(landmarks, ARBodyLandmarkType.LEFT_HIP);
    const leftKnee = this.findLandmark(landmarks, ARBodyLandmarkType.LEFT_KNEE);
    const leftAnkle = this.findLandmark(landmarks, ARBodyLandmarkType.LEFT_ANKLE);
    const rightHip = this.findLandmark(landmarks, ARBodyLandmarkType.RIGHT_HIP);
    const rightKnee = this.findLandmark(landmarks, ARBodyLandmarkType.RIGHT_KNEE);
    const rightAnkle = this.findLandmark(landmarks, ARBodyLandmarkType.RIGHT_ANKLE);

    if (!leftHip || !leftKnee || !leftAnkle || !rightHip || !rightKnee || !rightAnkle) {
      return;
    }

    // 计算左右膝关节角度
    const leftKneeAngle = this.calculateAngle(leftHip, leftKnee, leftAnkle);
    const rightKneeAngle = this.calculateAngle(rightHip, rightKnee, rightAnkle);
    const avgKneeAngle = (leftKneeAngle + rightKneeAngle) / 2;

    // 状态机转换
    const now = Date.now();
    
    switch (this.motionState) {
      case MotionState.IDLE:
        if (avgKneeAngle < this.SQUAT_THRESHOLD.hipKneeAngleMax - 20) {
          this.motionState = MotionState.PREPARING;
          console.info('准备下蹲...');
        }
        break;

      case MotionState.PREPARING:
        if (avgKneeAngle < this.SQUAT_THRESHOLD.hipKneeAngleMin + 10) {
          this.motionState = MotionState.EXECUTING;
          console.info('下蹲到位!');
        }
        break;

      case MotionState.EXECUTING:
        if (avgKneeAngle > this.SQUAT_THRESHOLD.hipKneeAngleMax - 10) {
          this.motionState = MotionState.COMPLETING;
          this.repCount++;
          this.lastStateTime = now;
          console.info(`完成第 ${this.repCount} 个深蹲!`);
          
          // 触发完成特效(通过Face AR叠加庆祝贴纸)
          this.triggerRepCompleteEffect();
        }
        break;

      case MotionState.COMPLETING:
        // 防抖:500ms后才能开始下一次
        if (now - this.lastStateTime > 500) {
          this.motionState = MotionState.IDLE;
        }
        break;
    }
  }

  /**
   * 开合跳动作识别
   * 核心逻辑:通过手腕与肩膀的相对位置判断开合状态
   */
  private detectJumpingJack(landmarks: ARBodyLandmark2D[]): void {
    const leftWrist = this.findLandmark(landmarks, ARBodyLandmarkType.LEFT_WRIST);
    const rightWrist = this.findLandmark(landmarks, ARBodyLandmarkType.RIGHT_WRIST);
    const leftShoulder = this.findLandmark(landmarks, ARBodyLandmarkType.LEFT_SHOULDER);
    const rightShoulder = this.findLandmark(landmarks, ARBodyLandmarkType.RIGHT_SHOULDER);

    if (!leftWrist || !rightWrist || !leftShoulder || !rightShoulder) return;

    // 判断手臂是否举过头顶(手腕Y坐标 < 肩膀Y坐标,注意屏幕坐标系Y向下)
    const isArmsUp = leftWrist.y < leftShoulder.y - 50 && rightWrist.y < rightShoulder.y - 50;
    // 判断手臂是否放下
    const isArmsDown = leftWrist.y > leftShoulder.y + 20 && rightWrist.y > rightShoulder.y + 20;

    if (this.motionState === MotionState.IDLE && isArmsUp) {
      this.motionState = MotionState.EXECUTING;
    } else if (this.motionState === MotionState.EXECUTING && isArmsDown) {
      this.repCount++;
      this.motionState = MotionState.IDLE;
      console.info(`完成第 ${this.repCount} 个开合跳!`);
    }
  }

  /**
   * 三点计算角度(余弦定理)
   */
  private calculateAngle(p1: ARBodyLandmark2D, p2: ARBodyLandmark2D, p3: ARBodyLandmark2D): number {
    const radians = Math.atan2(p3.y - p2.y, p3.x - p2.x) - 
                    Math.atan2(p1.y - p2.y, p1.x - p2.x);
    let angle = Math.abs(radians * 180.0 / Math.PI);
    if (angle > 180.0) angle = 360 - angle;
    return angle;
  }

  /**
   * 在骨骼点数组中查找指定类型的关键点
   */
  private findLandmark(landmarks: ARBodyLandmark2D[], type: ARBodyLandmarkType): ARBodyLandmark2D | undefined {
    return landmarks.find(lm => lm.type === type && lm.isValid);
  }

  /**
   * 选择置信度最高的人体
   */
  private selectPrimaryBody(bodies: ARBody[]): ARBody {
    return bodies.reduce((prev, current) => {
      const prevLandmarks = prev.getLandmarks2D();
      const currLandmarks = current.getLandmarks2D();
      const prevConfidence = prevLandmarks.reduce((sum, lm) => sum + lm.confidence, 0) / prevLandmarks.length;
      const currConfidence = currLandmarks.reduce((sum, lm) => sum + lm.confidence, 0) / currLandmarks.length;
      return currConfidence > prevConfidence ? current : prev;
    });
  }

  /**
   * 触发完成特效(与Face AR联动)
   */
  private triggerRepCompleteEffect(): void {
    // 通过事件总线通知Face AR控制器叠加庆祝特效
    // 具体实现略...
  }

  getRepCount(): number { return this.repCount; }
  getCurrentExercise(): ExerciseType { return this.currentExercise; }
  setExercise(type: ExerciseType): void { 
    this.currentExercise = type; 
    this.repCount = 0;
    this.motionState = MotionState.IDLE;
  }
}

4.4 主页面:AR 视图与 UI 叠加层整合

代码亮点 :使用 XComponent 承载相机预览,ARView 负责 3D 渲染,通过 Stack 叠加运动数据面板,实现"虚实融合"的沉浸体验。

typescript 复制代码
// pages/FitnessMirrorPage.ets
import { arEngine } from '@hms.core.ar.arengine';
import { ARView, ARViewContext } from '@hms.core.ar.arview';
import { ARSessionManager } from '../arengine/ARSessionManager';
import { FaceARController } from '../face/FaceARController';
import { BodyARController, ExerciseType } from '../body/BodyARController';

@Entry
@Component
struct FitnessMirrorPage {
  private sessionManager: ARSessionManager = new ARSessionManager();
  private faceController: FaceARController = new FaceARController();
  private bodyController: BodyARController = new BodyARController();
  
  @State repCount: number = 0;
  @State currentExercise: string = '深蹲';
  @State calories: number = 0;
  @State faceStatus: string = '未检测到人脸';
  @State bodyStatus: string = '未检测到人体';

  // ARView引用
  private arViewContext: ARViewContext | null = null;

  aboutToAppear() {
    this.initializeAR();
  }

  aboutToDisappear() {
    this.sessionManager.release();
  }

  async initializeAR() {
    const context = getContext(this);
    await this.sessionManager.initialize(context);
    
    // 启动AR数据循环
    this.startARLoop();
  }

  /**
   * AR数据主循环:每帧获取Face + Body数据并处理
   */
  startARLoop() {
    const loop = () => {
      const frameData = this.sessionManager.acquireFrameData();
      if (frameData) {
        // 处理Face AR
        if (frameData.faces.length > 0) {
          this.faceStatus = `检测到 ${frameData.faces.length} 张人脸`;
          frameData.faces.forEach(face => this.faceController.processFaceTracking(face));
        } else {
          this.faceStatus = '未检测到人脸';
        }

        // 处理Body AR
        if (frameData.bodies.length > 0) {
          this.bodyStatus = `检测到 ${frameData.bodies.length} 人`;
          this.bodyController.processBodyTracking(frameData.bodies);
          
          // 更新UI状态
          this.repCount = this.bodyController.getRepCount();
          this.calories = Math.floor(this.repCount * 0.5); // 简单热量估算
        } else {
          this.bodyStatus = '未检测到人体';
        }
      }
      
      // 下一帧
      requestAnimationFrame(loop);
    };
    
    requestAnimationFrame(loop);
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      // 层1:相机预览(XComponent)
      XComponent({
        id: 'camera_preview',
        type: XComponentType.SURFACE,
        controller: new XComponentController()
      })
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Black)
        .onLoad(() => {
          // 绑定相机预览Surface
          this.sessionManager.bindPreviewSurface(/* surfaceId */);
        })

      // 层2:AR 3D渲染视图(虚拟面具、骨骼连线、虚拟教练)
      ARView({
        onReady: (context: ARViewContext) => {
          this.arViewContext = context;
          this.faceController.setARViewContext(context);
          
          // 加载虚拟教练模型
          context.loadAsset('assets/models/coach_avatar.gltf', LandmarkType.CENTER_OF_FACE);
        }
      })
        .width('100%')
        .height('100%')
        .backgroundColor(Color.Transparent)

      // 层3:运动数据叠加层(沉浸光感设计)
      Column() {
        // 顶部状态栏
        Row() {
          Text(this.faceStatus)
            .fontSize(14)
            .fontColor(Color.White)
            .padding(8)
            .backgroundColor('rgba(0,0,0,0.5)')
            .borderRadius(4)
          
          Text(this.bodyStatus)
            .fontSize(14)
            .fontColor(Color.White)
            .padding(8)
            .backgroundColor('rgba(0,0,0,0.5)')
            .borderRadius(4)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
        .padding(16)

        Blank()

        // 底部运动面板(悬浮卡片 + 沉浸光感)
        Column() {
          // 当前动作类型
          Text(this.currentExercise)
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontColor(Color.White)
          
          // 计数器大数字
          Text(`${this.repCount}`)
            .fontSize(80)
            .fontWeight(FontWeight.Bold)
            .fontColor('#00D26A') // 鸿蒙品牌绿
            .shadow({ radius: 10, color: 'rgba(0,210,106,0.5)' })
          
          Text('次')
            .fontSize(16)
            .fontColor('rgba(255,255,255,0.7)')
          
          // 热量消耗
          Row() {
            Image($r('app.media.icon_fire'))
              .width(20)
              .height(20)
            Text(`${this.calories} kcal`)
              .fontSize(14)
              .fontColor('#FF6B6B')
          }
          .margin({ top: 8 })

          // 动作切换按钮
          Row({ space: 12 }) {
            Button('深蹲')
              .type(ButtonType.Capsule)
              .backgroundColor(this.currentExercise === '深蹲' ? '#00D26A' : 'rgba(255,255,255,0.2)')
              .onClick(() => this.switchExercise(ExerciseType.SQUAT))
            
            Button('开合跳')
              .type(ButtonType.Capsule)
              .backgroundColor(this.currentExercise === '开合跳' ? '#00D26A' : 'rgba(255,255,255,0.2)')
              .onClick(() => this.switchExercise(ExerciseType.JUMPING_JACK))
            
            Button('高抬腿')
              .type(ButtonType.Capsule)
              .backgroundColor(this.currentExercise === '高抬腿' ? '#00D26A' : 'rgba(255,255,255,0.2)')
              .onClick(() => this.switchExercise(ExerciseType.HIGH_KNEE))
          }
          .margin({ top: 16 })
        }
        .width('90%')
        .padding(24)
        .margin({ bottom: 40 })
        .backgroundColor('rgba(20,20,30,0.85)')
        .borderRadius(24)
        .backdropBlur(20) // 背景模糊,增强沉浸感
        .shadow({ radius: 20, color: 'rgba(0,0,0,0.3)' })
      }
      .width('100%')
      .height('100%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Black)
  }

  switchExercise(type: ExerciseType) {
    this.bodyController.setExercise(type);
    const nameMap = {
      [ExerciseType.SQUAT]: '深蹲',
      [ExerciseType.JUMPING_JACK]: '开合跳',
      [ExerciseType.HIGH_KNEE]: '高抬腿'
    };
    this.currentExercise = nameMap[type];
  }
}

五、关键性能优化策略

5.1 双模态并发性能调优

Face AR 与 Body AR 同时运行时,需关注以下优化点:

typescript 复制代码
// 优化1:降低相机分辨率(健身镜场景无需超高精度)
this.config.imageResolution = { width: 1280, height: 720 };

// 优化2:控制检测频率(人体追踪可降频至15fps,人脸保持30fps)
this.config.bodyTrackingFPS = 15;
this.config.faceTrackingFPS = 30;

// 优化3:及时释放ARFrame资源,防止内存泄漏
frame.release(); // 每帧处理完后必须调用

5.2 骨骼点渲染优化

typescript 复制代码
// 使用ARKit的GPU实例化渲染骨骼连线,避免每帧创建销毁绘制对象
// 预创建骨骼连线Mesh,运行时仅更新顶点坐标

六、调试与常见问题排查

问题现象 可能原因 解决方案
AR Session 启动失败 AR Engine 未安装 引导用户前往华为应用市场安装
人脸追踪抖动 光线不足或逆光 建议用户面向光源,或启用自动曝光补偿
骨骼点漂移 人体部分遮挡 提示用户全身入镜,避免遮挡关节
表情映射不自然 BlendShape权重未归一化 检查 getData() 返回的 Float32Array 范围
多人场景性能下降 maxDetectedBodyNum 设置过大 根据场景需求合理设置(建议 ≤ 2)

七、总结与展望

本文基于 HarmonyOS 6(API 23)的 Face ARBody AR 能力,完整实战了一款"虚实融合健身镜"应用。核心要点总结:

  1. 双模态并发架构 :通过 ARFeatureType 同时启用 Face 和 Body 追踪,实现"面部表情+肢体动作"的全方位交互
  2. 微表情驱动 :利用 ARBlendShapes 的 64 种表情参数,精准驱动虚拟形象,实现"真人表情→虚拟面具"的实时映射
  3. 骨骼姿态估计:基于 20+ 关键点的 2D/3D 坐标,构建动作状态机,实现深蹲、开合跳等常见健身动作的自动计数
  4. 端侧隐私安全:所有图像数据本地处理,不上传云端,符合鸿蒙系统的隐私设计理念

未来扩展方向

  • 结合 HarmonyOS PC 的大屏优势,将健身镜应用扩展为家庭智慧屏应用
  • 接入 运动健康服务,将运动数据同步至华为运动健康生态
  • 利用 分布式软总线,实现手机、平板、智慧屏的多设备协同健身

转载自:https://blog.csdn.net/u014727709/article/details/160360756

欢迎 👍点赞✍评论⭐收藏,欢迎指正

相关推荐
南村群童欺我老无力.2 小时前
鸿蒙开发中的@Builder装饰器函数中的UI语法限制
ui·华为·harmonyos
南村群童欺我老无力.2 小时前
鸿蒙开发中紧凑写法的括号灾难——深度追踪法定位问题
华为·harmonyos
Lanren的编程日记2 小时前
Flutter 鸿蒙应用数据验证功能实战:完善表单验证体系,全方位提升数据质量
flutter·华为·harmonyos
key_3_feng2 小时前
HarmonyOS 6.0 轻量化服务卡片交互设计方案
华为·交互·harmonyos
前端不太难2 小时前
鸿蒙游戏如何设计可扩展架构?
游戏·架构·harmonyos
互联网散修12 小时前
鸿蒙星闪实战:从零构建跨设备文件传输——拆解文件传输数据流
华为·harmonyos
南村群童欺我老无力.12 小时前
鸿蒙PC - 资源文件引用路径的隐蔽陷阱
华为·harmonyos
南村群童欺我老无力.13 小时前
鸿蒙PC开发的Scroll组件maxHeight属性不存在
华为·harmonyos
Swift社区17 小时前
鸿蒙游戏多设备发布流程详解
游戏·华为·harmonyos