# 鸿蒙 AR 人体骨骼关键点识别实战:使用 AREngine 实现实时人体跟踪

> 参考文档:[AR Engine 开发指南 - 人体跟踪与骨骼关键点识别](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arengine-body)

## 一、背景

HarmonyOS 从 6.1.0(23) 版本开始,AR Engine 新增了对人体骨骼关键点识别与跟踪的支持(`ARType.BODY`)。通过该能力,开发者可以实时获取摄像头画面中人体的 20 个骨骼关键点坐标,并在此基础上实现体感游戏、健身姿态矫正、运动分析等应用。

本文以新华字典应用为基础,为其添加 AR 人体骨骼识别功能,完整记录从环境配置、API 调用到设备调试的全过程。

---

## 二、开发环境

| 工具 | 版本 |
|------|------|
| HarmonyOS SDK | 23(API 11)+ |
| AR Engine Kit | @kit.AREngine |
| 3D 图形 Kit | @kit.ArkGraphics3D |
| 目标设备 | 支持 AR Engine 的 HarmonyOS 设备 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |

> **注意**:AR Engine 是 HMS SDK 的一部分,需要通过 DevEco Studio 的 SDK Manager 额外安装。路径位于 `DevEco-Studio.app/Contents/sdk/default/hms/`。

---

## 三、AR Engine Kit 简介

### 3.1 Kit 架构

AR Engine 的 Kit 结构如下:

```
@kit.AREngine              # 入口 Kit
└── @hms.core.ar.arengine  # AR 核心 API(arEngine 命名空间)
└── @hms.core.ar.arview    # AR 视图组件(ARView、arViewController)
```

### 3.2 支持的 AR 能力

| ARType | 能力 | 最低 API 版本 |
|--------|------|:----------:|
| `WORLD` | 环境追踪 | 18 |
| `BODY` | 人体骨骼跟踪 | **23** |
| `FACE` | 人脸跟踪 | 23 |
| `IMAGE` | 图像跟踪 | 18 |

---

## 四、权限配置

AR Engine 需要以下权限,其中 `CAMERA` 属于 **user_grant** 权限,需要运行时动态申请。

### 4.1 在 module.json5 中声明

```json5
{
  "requestPermissions": [
    {
      "name": "ohos.permission.CAMERA",
      "reason": "$string:reason_camera",
      "usedScene": {
        "abilities": ["EntryAbility"],
        "when": "inuse"
      }
    },
    {
      "name": "ohos.permission.GYROSCOPE"
    },
    {
      "name": "ohos.permission.ACCELEROMETER"
    }
  ]
}
```

### 4.2 运行时申请权限

`CAMERA` 是敏感权限,必须使用 `abilityAccessCtrl.requestPermissionsFromUser()` 在运行时动态申请:

```typescript
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';

async requestPermission(): Promise<void> {
  let atManager = abilityAccessCtrl.createAtManager();
  let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
  let permissions: Array<Permissions> = ['ohos.permission.CAMERA'] as Array<Permissions>;

  atManager.requestPermissionsFromUser(context, permissions, (err, result) => {
    if (err) {
      this.errorMsg = '权限请求失败: ' + err.message;
      return;
    }
    if (result.authResults[0] === 0) {
      // 权限已授予,初始化 AR
      this.initARView();
    } else {
      this.errorMsg = '需要相机权限才能使用AR人体骨骼识别';
    }
  });
}
```

> **💥 踩坑**:`user_grant` 权限不仅要在 `module.json5` 中声明,还**必须**在代码中调用 `requestPermissionsFromUser`。只声明不请求,运行时仍然会被拒绝。

---

## 五、核心 API

### 5.1 关键类与接口

| 类/接口 | 说明 |
|---------|------|
| `ARViewContext` | AR 会话上下文,管理 AR Engine 系统状态 |
| `ARView` | 相机预览流组件,展示 AR 画面 |
| `ARViewCallback` | 帧更新回调,需要实现 `onFrameUpdate` 方法 |
| `ARSession` | AR 会话,通过 `getFrame()` 获取帧数据 |
| `ARFrame` | 单帧数据,通过 `acquireBodySkeleton()` 获取人体数据 |
| `ARBody` | 人体跟踪对象,通过 `getLandmarks2D()` 获取骨骼关键点 |
| `ARBodyLandmark2D` | 2D 骨骼关键点,包含坐标、置信度、类型等 |
| `ARBodyLandmarkType` | 骨骼关键点类型枚举(共 20 个点) |

### 5.2 人体骨骼关键点(20 个)

| 类型 | 部位 | 类型 | 部位 |
|------|------|------|------|
| `NECK` | 颈部 | `RIGHT_SHOULDER` | 右肩 |
| `RIGHT_ELBOW` | 右肘 | `RIGHT_WRIST` | 右腕 |
| `LEFT_SHOULDER` | 左肩 | `LEFT_ELBOW` | 左肘 |
| `LEFT_WRIST` | 左腕 | `RIGHT_HIP` | 右髋 |
| `RIGHT_KNEE` | 右膝 | `RIGHT_ANKLE` | 右踝 |
| `LEFT_HIP` | 左髋 | `LEFT_KNEE` | 左膝 |
| `LEFT_ANKLE` | 左踝 | `MID_HIP` | 髋中 |
| `RIGHT_EAR` | 右耳 | `RIGHT_EYE` | 右眼 |
| `NOSE` | 鼻子 | `LEFT_EYE` | 左眼 |
| `LEFT_EAR` | 左耳 | `SPINE` | 脊柱 |

---

## 六、实现步骤

### 6.1 导入模块

```typescript
import { arEngine, ARView, arViewController } from '@kit.AREngine';
import { Node, Scene } from '@kit.ArkGraphics3D';
import { BusinessError } from '@kit.BasicServicesKit';
import { display, router } from '@kit.ArkUI';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
```

### 6.2 实现 ARViewCallback

`ARViewCallback` 是 AR Engine 的回调类,需要在 `onFrameUpdate` 中获取帧数据并提取人体骨骼信息:

```typescript
class ARViewCallbackImpl extends arViewController.ARViewCallback {
  private callback?: OnBodyInfoCallback;

  setCallback(cb: OnBodyInfoCallback) {
    this.callback = cb;
  }

  async onFrameUpdate(ctx: arViewController.ARViewContext, sysBootTs: number): Promise<void> {
    if (!ctx.session) return;

    const arSession: arEngine.ARSession = ctx.session;
    try {
      const frame: arEngine.ARFrame = arSession.getFrame();
      const bodies: arEngine.ARBody[] = frame.acquireBodySkeleton();
      if (this.callback) {
        this.callback(bodies);
      }
    } catch (error) {
      const err: BusinessError = error as BusinessError;
      console.error(`Failed to update body data. Code: ${err.code}, message: ${err.message}`);
    }
  }
}
```

**执行流程**:
```
onFrameUpdate
  → arSession.getFrame()          // 获取当前帧
  → frame.acquireBodySkeleton()   // 获取人体对象数组
  → body.getLandmarks2D()         // 获取骨骼关键点
```

### 6.3 初始化 ARViewContext

```typescript
async initARView(): Promise<void> {
  await Scene.load().then(async (scene: Scene) => {
    let viewContext: arViewController.ARViewContext = new arViewController.ARViewContext();
    viewContext.scene = scene;

    // 设置回调
    let callback = new ARViewCallbackImpl();
    callback.setCallback(this.onBodyInfoCb);
    viewContext.callback = callback;

    // 配置 AR 参数
    viewContext.config = {
      type: arEngine.ARType.BODY,              // 人体跟踪模式
      planeFindingMode: arEngine.ARPlaneFindingMode.DISABLED,
      powerMode: arEngine.ARPowerMode.NORMAL,
      semanticMode: arEngine.ARSemanticMode.NONE,
      poseMode: arEngine.ARPoseMode.GRAVITY,
      depthMode: arEngine.ARDepthMode.DISABLED,
      meshMode: arEngine.ARMeshMode.DISABLED,
      focusMode: arEngine.ARFocusMode.AUTO,
      maxDetectedBodyNum: 1,                    // 最多检测 1 个人体
      cameraLensFacing: 0                       // 后置摄像头
    };

    await viewContext.init();
    this.arContext = viewContext;
    this.isReady = true;
  });
}
```

### 6.4 展示 AR 预览流

使用 `ARView` 组件展示摄像头预览流:

```typescript
if (this.arContext) {
  ARView({ context: this.arContext })
    .height('100%')
    .width('100%')
    .alignRules({
      center: { anchor: '__container__', align: VerticalAlign.Center },
      middle: { anchor: '__container__', align: HorizontalAlign.Center }
    })
}
```

### 6.5 绘制骨骼关键点和骨骼连线

将获取到的骨骼关键点坐标(归一化值)转换为屏幕坐标,然后使用 `Shape` + `Circle` + `Line` 组件绘制:

```typescript
// 坐标转换:归一化 → 屏幕像素
landmarks.forEach((landmark) => {
  landmark.x = landmark.x * this.displayWidth;
  landmark.y = landmark.y * this.displayHeight;
});

// 绘制关键点
@Builder
drawBodyLandmarks(bodyLandmarks: arEngine.ARBodyLandmark2D[]) {
  ForEach(bodyLandmarks, (landmark: arEngine.ARBodyLandmark2D) => {
    Circle({ width: 6, height: 6 })
      .position({
        x: px2vp(landmark.x) - 3,
        y: px2vp(landmark.y) - 3
      })
      .fill(Color.White).stroke(Color.Red).strokeWidth(1)
  })
}

// 绘制骨骼连线
@Builder
drawBodyBoneLine(..., start, end, color) {
  if (isValidBoneLine(start, end)) {
    Line()
      .startPoint([px2vp(start.x), px2vp(start.y)])
      .endPoint([px2vp(end.x), px2vp(end.y)])
      .stroke(color).strokeWidth(3)
  }
}
```

**颜色方案**:

| 部位 | 颜色 |
|------|:----:|
| 躯干 | 🟠 橙色 |
| 左臂 + 左腿 | 🟢 绿色 |
| 右臂 + 右腿 | 🔵 蓝色 |
| 头部 | 🟡 黄色 |

---

## 七、完整代码结构

```
entry/src/main/ets/pages/
└── ARBodyPage.ets                # AR 人体骨骼识别页面
    ├── BodyInfo                   # 数据结构定义
    ├── arLandmarksToMap()         # 工具函数
    ├── ARViewCallbackImpl         # 帧回调实现
    ├── ARBodyPage(struct)         # 主页面
    │   ├── requestPermission()    # 权限申请
    │   ├── initARView()           # AR 初始化
    │   ├── stopARView()           # AR 停止
    │   ├── drawBodyPerception()   # 绘制入口
    │   ├── drawBodyLandmarks()    # 绘制关键点
    │   ├── drawBodyBones()        # 绘制骨骼线
    │   └── drawBodyBoneLine()     # 绘制单条骨骼线
    └── ARView + Shape              # UI 组件
```

---

## 八、遇到的坑与解决方案

### 8.1 权限 denied

**现象**:AR 初始化失败,日志输出:
```
permission check failed, permission:ohos.permission.CAMERA, result:-1
Error: Permission denied. create native session failed.
```

**原因**:`CAMERA` 是 `user_grant` 权限,需要在代码中运行时申请,仅声明不够。

**解决**:调用 `abilityAccessCtrl.requestPermissionsFromUser()` 动态请求权限,用户授权后再初始化 AR。

### 8.2 @Builder 内不能使用 let/const

**现象**:编译错误 `Only UI component syntax can be written here`

**原因**:ArkTS 的 `@Builder` 函数内不能包含 `let`/`const` 声明语句,只能有 UI 组件语法。

**解决**:将逻辑提取到普通方法中,`@Builder` 只包含 UI 组件调用:

```typescript
// ✅ 正确:逻辑放在普通方法
getBonePoint(type): number[] {
  const p = map.get(type);
  return p ? [px2vp(p.x), px2vp(p.y)] : [0, 0];
}

// UI 渲染放在 @Builder
@Builder
drawBoneLine(start, end, color) {
  if (this.isValid(start, end)) {
    Line().startPoint(this.getBonePoint(start))...
  }
}
```

### 8.3 AREngine 在 OpenHarmony SDK 中不存在

**现象**:找不到 `@kit.AREngine` 模块。

**原因**:AR Engine 属于 HarmonyOS(HMS)SDK,不在 OpenHarmony SDK 中。

**解决**:SDK 路径为 `DevEco-Studio.app/Contents/sdk/default/hms/`,确保通过 SDK Manager 安装了 AR Engine。

### 8.4 Object literal 类型错误

**现象**:编译错误 `Object literal must correspond to some explicitly declared class or interface`

**解决**:使用 `as` 显式指定类型:

```typescript
return { trackId, landmarks } as BodyInfo;
```

---

## 九、设备兼容性

AR Engine 的人体骨骼识别功能对硬件有一定要求:

| 要求 | 说明 |
|------|------|
| 系统版本 | HarmonyOS 6.1.0(23)+ |
| 摄像头 | 支持自动对焦的后置摄像头 |
| 处理器 | 麒麟系列处理器(推荐) |
| 传感器 | 陀螺仪 + 加速度计 |

> 可以通过 `ARViewContext.init()` 的异常信息判断设备是否支持。

---

## 十、总结

本文详细介绍了使用 HarmonyOS AR Engine 实现人体骨骼关键点识别与跟踪的完整过程,包括:

- AR Engine Kit 的架构和 API
- 权限配置(声明 + 运行时申请)
- 帧数据获取与骨骼关键点提取
- 实时绘制骨骼点和连线
- 4 个实战踩坑经验

人体骨骼识别在体感游戏、运动健康、虚拟试穿等领域有广泛的应用前景,希望本文能帮助鸿蒙开发者快速上手 AR Engine。

---

## 项目地址

本文所有代码已开源:[https://gitcode.com/jianguoxu/myapplication](https://gitcode.com/jianguoxu/myapplication)
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐