想给用户戴面具?AR Engine 人体骨骼检测详解

你有没有想过,那些短视频 APP 里的"一键换装"、"人体特效"是怎么做到的?比如给用户身上套一层盔甲,或者在手上放一个发光的魔法球——这些特效的核心,就是人体骨骼检测

AR Engine 提供了一套人体骨骼追踪能力,能实时识别出人体的 21 个关键点(脖子、肩膀、肘部、手腕、髋部、膝盖、脚踝、脊柱、眼睛、耳朵、鼻子等等),然后告诉你每个关键点在屏幕上的坐标和置信度。有了这些数据,你就可以在人体的任意位置叠加虚拟内容了。

人体骨骼追踪整体流程

下面是人体骨骼追踪的整体流程概览:

创建ARViewContext

配置ARConfig

设置type为BODY

初始化AR会话

启动摄像头和传感器

等待onFrameUpdate回调

获取ARFrame

调用acquireBodySkeleton

检测到人体?

获取21个骨骼关键点

在关键点位置叠加虚拟内容

怎么开启人体骨骼追踪?

跟其他 AR 能力一样,你需要先配置 ARConfig,把 type 设成 BODY

import { arEngine, arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
  type: arEngine.ARType.BODY,
  poseMode: arEngine.ARPoseMode.GRAVITY_AND_HEADING,
  powerMode: arEngine.ARPowerMode.POWER_SAVING,
  depthMode: arEngine.ARDepthMode.AUTOMATIC
};

这里最关键的是 type: arEngine.ARType.BODY。这一行告诉 AR Engine:“我要用人体追踪能力”。BODY 这个类型对应的是人体跟踪与骨骼关键点识别能力,起始版本是 6.1.0(23)。

其他几行配置跟 SLAM 追踪差不多:

  • poseMode:世界坐标系的建立方式,GRAVITY_AND_HEADING 表示 Y 轴跟重力对齐,X 轴指向指南针北向。
  • powerMode:省电模式,AR 运算比较吃性能,省电模式能让手机不那么烫。
  • depthMode:深度模式,AUTOMATIC 会自动获取深度信息。

另外,如果你需要同时检测多个人体,可以配置 maxDetectedBodyNum

import { arEngine, arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
  type: arEngine.ARType.BODY,
  maxDetectedBodyNum: 2
};

maxDetectedBodyNum 表示要检测的人体数量最大值,默认是 1,最大是 2。如果你设成超过 2 的值,也会被自动限制为 2。所以目前最多同时追踪两个人。

还有一个有用的配置是 cameraLensFacing。默认情况下用的是后置摄像头(REAR),但如果你要做自拍特效(比如给自己戴面具),可以把它设成 FRONT

import { arEngine, arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
  type: arEngine.ARType.BODY,
  cameraLensFacing: arEngine.ARCameraLensFacing.FRONT
};

cameraLensFacing 配置为 FRONT 时,type 必须是 ARType.FACEARType.BODY 才会生效。

配置好之后,初始化 AR 会话:

await context.init();

这一步会启动摄像头和传感器,开始人体骨骼追踪。

怎么获取人体骨骼关键点?

开启追踪之后,你需要在每一帧的回调里获取骨骼数据。AR Engine 提供了 onFrameUpdate 回调,每一帧渲染前都会触发:

import { arEngine, arViewController } from '@kit.AREngine';
import { Node } from '@kit.ArkGraphics3D';

class ARViewCallbackImpl extends arViewController.ARViewCallback {

  onAnchorAdd(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
    console.info('onAnchorAdd');
    console.info(`add anchor id = ${String(anchor.id)}`);
    console.info(`add anchor translation = ${anchor.getPose().translation}`);
    console.info(`add node pose = ${node.position}`);
  }

  onAnchorUpdate(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
    console.info('onAnchorUpdate');
    console.info(`update anchor id = ${String(anchor.id)}`);
    console.info(`update anchor translation = ${anchor.getPose().translation}`);
    console.info(`update node pose = ${node.position}`);
  }

  async onFrameUpdate(ctx: arViewController.ARViewContext, sysBootTs: number): Promise<void> {
    let arSession: arEngine.ARSession | undefined = ctx.session;
    if (arSession) {
      let frame: arEngine.ARFrame = arSession.getFrame();
      if (!frame) {
        console.error('Failed to get arSession.frame, it is undefined or null');
      } else {
        console.info(`Succeeded in getting arSession.frame = ${frame.timestamp}`);
        await frame.release();
      }
    } else {
      console.error('Failed to get arSession, arSession is undefined');
    }
  }
}

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.callback = new ARViewCallbackImpl();

这段代码看起来挺长,但核心就是继承 arViewController.ARViewCallback,然后重写三个回调方法:

onAnchorAdd:当 AR Engine 检测到新的人体时,会自动创建一个锚点和对应的场景节点,然后触发这个回调。你可以在回调里拿到锚点的位姿信息。

onAnchorUpdate:人体的位置会随着运动不断更新,每更新一次就触发这个回调。你可以在这里拿到最新的位姿。

onFrameUpdate:每一帧都会触发,这是你获取骨骼数据的地方。注意用完 ARFrame 之后要调用 frame.release() 释放资源,不然内存会一直涨。

接下来,在 onFrameUpdate 回调里,通过 frame.acquireBodySkeleton() 获取人体骨骼数据:

import { arEngine } from '@kit.AREngine';

// arSession创建参考ARSession.getFrame接口示例代码
const frame: arEngine.ARFrame = arSession.getFrame();
const arbodys: arEngine.ARBody[] = frame.acquireBodySkeleton();
if (arbodys.length > 0) {
    arbodys[0].getLandmarks2D();

这一行代码做了三件事:

  1. frame.acquireBodySkeleton():从当前帧中获取所有被检测到的人体对象,返回一个 ARBody 数组。如果你配置了 maxDetectedBodyNum: 2,那这个数组最多会有两个元素。
  2. arbodys.length > 0:检查是否检测到了人体。如果没有人出现在摄像头画面里,这个数组就是空的。
  3. arbodys[0].getLandmarks2D():获取第一个人体的所有骨骼关键点信息。

ARBody 是什么?

ARBody 是人体跟踪的结果对象,包含两个只读属性:

  • trackId:当前人体骨骼关键点的 ID。这个 ID 可以用来区分不同的人——如果你同时追踪两个人,第一个人的 trackId 始终是同一个值,第二个人也是。
  • timeStamp:当前帧的时间戳,单位是纳秒(ns)。

调用 getLandmarks2D() 方法后,会返回一个 ARBodyLandmark2D 数组,每个元素代表一个骨骼关键点。

ARBodyLandmark2D 每个字段是什么意思?

每个 ARBodyLandmark2D 对象包含 5 个字段:

字段 类型 说明
x number 骨骼关键点的 x 坐标
y number 骨骼关键点的 y 坐标
confidence number 置信度,取值范围 [0, 1]。越接近 1 表示这个关键点越可信
type ARBodyLandmarkType 关键点类型,比如脖子、左肩、右肘等
isValid boolean 这个关键点是否有效。true 表示有效,false 表示无效

置信度这个东西很重要。打个比方,如果用户把手背到身后,AR Engine 看不到手腕,那手腕关键点的 confidence 就会很低,甚至 isValid 会变成 false。你在使用的时候,一定要先检查 isValidconfidence,不然可能会在错误的位置放虚拟物体。

21 个关键点分别在哪?

ARBodyLandmarkType 枚举定义了人体的 21 个关键点。我把它们按部位整理一下:

头部区域(5 个):

  • RIGHT_EAR(15):右耳
  • RIGHT_EYE(16):右眼
  • NOSE(17):鼻子
  • LEFT_EYE(18):左眼
  • LEFT_EAR(19):左耳

躯干区域(3 个):

  • NECK(1):颈部
  • SPINE(20):脊柱
  • MID_HIP(14):中髋部(两髋之间的中点)

右臂(3 个):

  • RIGHT_SHOULDER(2):右肩
  • RIGHT_ELBOW(3):右肘
  • RIGHT_WRIST(4):右腕

左臂(3 个):

  • LEFT_SHOULDER(5):左肩
  • LEFT_ELBOW(6):左肘
  • LEFT_WRIST(7):左腕

右腿(3 个):

  • RIGHT_HIP(8):右髋部
  • RIGHT_KNEE(9):右膝
  • RIGHT_ANKLE(10):右脚踝

左腿(3 个):

  • LEFT_HIP(11):左髋部
  • LEFT_KNEE(12):左膝
  • LEFT_ANKLE(13):左脚踝

有了这 21 个关键点,你就能勾勒出人体的完整骨架。打个比方,如果你想在用户手上放一个虚拟魔法球,你只需要找到 RIGHT_WRISTLEFT_WRIST 关键点的坐标,然后在那个位置渲染一个球就行了。

怎么在关键点位置放模型?

AR Engine 还提供了一个更方便的方式——直接在指定的关键点处放置 3D 模型。你可以用 ARViewContext.loadAsset() 方法:

import { arEngine, arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
// 此处为用户本地存放模型路径
let resourcePath:ResourceStr = $rawfile('xxxx/xxxx');
context.loadAsset(resourcePath, arViewController.LandmarkType.LEFT_EYE);

这行代码会在左眼位置放置一个 3D 模型。$rawfile('xxxx/xxxx') 是你本地存放模型文件的路径。

LandmarkType 枚举定义了 6 个可以放置模型的关键点:

  • LEFT_EYE(0):左眼
  • LEFT_SIDE_OF_MOUTH(1):嘴巴左侧
  • RIGHT_EYE(2):右眼
  • RIGHT_SIDE_OF_MOUTH(3):嘴巴右侧
  • TIP_OF_NOSE(4):鼻尖
  • CENTER_OF_FACE(5):人脸中心

这些关键点主要集中在面部区域,所以特别适合做"面具"、“墨镜”、"兔耳朵"之类的面部特效。

如果你想移除某个关键点上已经放置的模型,可以用 removeAsset()

import { arEngine, arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.removeAsset(arViewController.LandmarkType.LEFT_EYE);

如果你想一次性清除所有已放置的模型,可以用 clearResource()

import { arViewController } from '@kit.AREngine';

let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.clearResource();

怎么做表情同步?

AR Engine 还支持微表情追踪。你可以用 setBlendShapeWeight() 方法来设置特定表情的权重:

import { arEngine, arViewController } from '@kit.AREngine';
import { Node } from '@kit.ArkGraphics3D';

onAnchorUpdate(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
    ctx.setBlendShapeWeight(node, arEngine.ARBlendShapeType.EYE_BLINK_LEFT, 0.1);
  }

这里 EYE_BLINK_LEFT 是左眼眨眼的微表情类型,0.1 是权重值(范围是 0 到 1)。你可以在 onAnchorUpdate 回调里持续更新这个值,让你的虚拟面具跟着用户的表情动起来。

你也可以用 getBlendShapeWeight() 来获取当前的微表情权重:

import { arEngine, arViewController } from '@kit.AREngine';
import { Node } from '@kit.ArkGraphics3D';

onAnchorUpdate(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
    ctx.getBlendShapeWeight(node, arEngine.ARBlendShapeType.EYE_BLINK_LEFT);
  }

如果传入的 nodetype 有误,会返回 null

设备兼容性检查

不是所有设备都支持人体骨骼追踪。在使用之前,建议先进行兼容性检查:

支持

不支持

正常

异常

应用启动

调用isARTypeSupported

设备是否支持?

初始化AR会话

提示用户设备不兼容

开启人体骨骼追踪

追踪是否正常?

获取骨骼数据

检查801错误码

提示设备算力不足

不是所有设备都支持人体骨骼追踪。在使用之前,你可以用 isARTypeSupported() 来检查当前设备是否支持:

import { arEngine, arViewController } from '@kit.AREngine';

arViewController.isARTypeSupported(arEngine.ARFeatureType.ARENGINE_FEATURE_TYPE_BODY);

返回 true 表示支持,false 表示不支持。ARENGINE_FEATURE_TYPE_BODY 对应的就是人体骨骼点识别与跟踪特性。

这个检查很重要,因为人体骨骼追踪需要设备有比较强的算力。如果在不支持的设备上强行使用,可能会报 801(Device not compatible)错误。

骨骼关键点应用流程

获取到骨骼关键点后,可以根据不同需求进行应用:

有效

无效

AR面具

AR健身

AR试衣

AR游戏

获取骨骼关键点数据

检查isValid和confidence

选择应用场景

跳过该关键点

在面部关键点放置3D模型

计算关节角度判断动作

估算人体体型尺寸

映射身体动作到游戏角色

使用setBlendShapeWeight同步表情

给出动作纠正提示

叠加虚拟衣服

实时同步动作

实际应用场景

说了这么多 API,来看看人体骨骼检测能做什么:

AR 面具/贴纸:在面部关键点位置放置 3D 模型,配合微表情追踪,就能做出跟着用户表情动的虚拟面具。这是最直接的"给用户戴面具"的玩法。

AR 健身教练:通过追踪全身 21 个关键点,判断用户的动作是否标准。比如用户在做深蹲,你可以检查髋部和膝盖的角度,如果角度不对就提示用户调整姿势。

AR 虚拟试衣:追踪人体的肩部、髋部等关键点,估算人体的体型和尺寸,然后在用户身上叠加虚拟衣服。

AR 游戏:让用户用身体动作来控制游戏角色。比如用户举起左手,游戏里的角色也举起左手。

人体骨骼检测是 AR Engine 比较新的能力,起始版本是 6.1.0(23)。它跟人脸追踪(FACE)是独立的两个能力,但可以配合使用——比如你同时开启 FACE 和 BODY,既能追踪人脸做面具,又能追踪身体做全身特效。

需要注意的是,人体骨骼追踪目前只能获取 2D 坐标(屏幕坐标),还没有 3D 坐标的能力。如果你需要在 3D 空间中定位人体的位置,可能需要结合深度信息来做推算。

Logo

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

更多推荐