鸿蒙开发-想识别桌面和地面?AR Engine平面检测和Mesh识别
本文详细介绍了AR Engine中的平面检测和Mesh识别技术。平面检测能识别地面、桌面、墙壁等平面类型,通过配置planeFindingMode可选择水平、垂直或全部平面检测模式。检测结果通过ARPlane对象提供平面类型、大小等属性,支持回调或主动获取两种方式。Mesh识别则能重建环境的三角形网格,实现更精细的AR效果。文章包含完整的平面检测流程图示、代码示例和API说明,帮助开发者理解如何利
想识别桌面和地面?AR Engine 平面检测和 Mesh 识别详解
你有没有想过,AR 家具 APP 是怎么把虚拟沙发稳稳地放在地面上的?它怎么知道哪里是地面、哪里是桌面、哪里是墙壁?
答案就是平面检测。AR Engine 能从摄像头画面中自动识别出各种平面——水平的地面和桌面、垂直的墙壁,甚至天花板。识别出来之后,你就能在这些平面上放置虚拟物体了。
而 Mesh 识别比平面检测更进一步。它不是只给你一个平面,而是把整个环境的表面用三角形网格重建出来。有了网格数据,你就能做更精细的 AR 效果,比如让虚拟物体沿着不规则表面贴合。
平面检测整体流程
下面是平面检测的完整工作流程:
平面检测怎么开启?
在 ARConfig 里设置 planeFindingMode 就行了:
import { arEngine, arViewController } from '@kit.AREngine';
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
type: arEngine.ARType.WORLD,
planeFindingMode: arEngine.ARPlaneFindingMode.HORIZONTAL_AND_VERTICAL,
poseMode: arEngine.ARPoseMode.GRAVITY_AND_HEADING,
powerMode: arEngine.ARPowerMode.POWER_SAVING,
depthMode: arEngine.ARDepthMode.AUTOMATIC
};
planeFindingMode: arEngine.ARPlaneFindingMode.HORIZONTAL_AND_VERTICAL 告诉 AR Engine:“水平面和竖直面我都要”。
ARPlaneFindingMode 枚举有 4 个值:
DISABLED(0):禁用平面检测。如果你不需要平面检测,可以关掉来节省性能。HORIZONTAL(1):只检测水平面,比如地板和桌子。如果你只做家具摆放,用这个就够了。VERTICAL(2):只检测竖直平面,比如墙壁。如果你想在墙上挂虚拟画框,用这个。HORIZONTAL_AND_VERTICAL(3):同时检测水平面和竖直平面。这是默认值,最全面的选项。
注意,planeFindingMode 默认就是 HORIZONTAL_AND_VERTICAL,所以如果你不设置这个字段,平面检测也是开着的。但如果你想关闭它来节省性能,需要显式设成 DISABLED。
配置好之后,初始化 AR 会话:
await context.init();
初始化完成后,AR Engine 就开始在后台默默识别平面了。随着用户移动手机扫描更多环境,检测到的平面会越来越多、越来越精确。
平面有哪几种类型?
AR Engine 检测到的平面有 4 种类型,用 ARPlaneType 枚举来表示:
FACING_HORIZONTAL_UPWARD(0):朝上的水平面,比如地面和桌面。这是最常见的平面类型,大部分虚拟物体都是放在这种平面上的。FACING_HORIZONTAL_DOWNWARD(1):朝下的水平面,比如天花板。你可以在天花板上挂虚拟吊灯。FACING_VERTICAL(2):垂直平面,比如墙壁。适合在墙上挂画、放电视。FACING_INVALID(3):无效或不支持的平面类型。可能是环境变化、光线条件不好等原因导致的。
你拿到一个平面对象后,可以通过 planeType 属性来判断它是哪种类型的平面,然后根据类型来做不同的处理。比如地面可以放家具,墙壁可以挂画,天花板可以挂灯。
怎么获取检测到的平面?
平面检测的结果需要通过 ARFrame 来获取。有两种方式:
方式一:获取所有已检测到的平面:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let trackables: Array<arEngine.ARTrackable> = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
let plane = trackables[0] as arEngine.ARPlane;
plane.getPolygonXZ();
getAllTrackables(ARTrackableType.PLANE) 返回当前所有已检测到的平面对象数组。然后你可以通过类型转换得到 ARPlane 对象。
方式二:通过回调自动通知:
还记得前面说的 ARViewCallback 吗?当 AR Engine 检测到新平面时,会自动触发 onAnchorAdd 回调:
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 会自动为新平面创建一个锚点(Anchor)和对应的场景节点(Node),然后通过这个回调通知你。你可以在回调里拿到锚点的位姿信息,用来在平面上放置虚拟物体。
onAnchorUpdate:当已有平面的范围扩大或位置更新时触发。平面检测是一个持续的过程——随着用户移动手机,AR Engine 会不断优化平面的大小和位置。每次更新都会触发这个回调。
onFrameUpdate:每一帧都会触发。你可以在这里做实时的渲染和逻辑处理。
ARPlane 对象包含哪些信息?
ARPlane 继承自 ARTrackable,包含了检测到的平面的详细信息:
| 属性 | 类型 | 说明 |
|---|---|---|
planeType |
ARPlaneType | 平面类型(朝上水平面、朝下水平面、垂直面等) |
extendX |
number | 平面边界矩形沿 X 轴的长度(米) |
extendZ |
number | 平面边界矩形沿 Z 轴的长度(米) |
label |
ARSemanticPlaneLabel | 平面的语义类型(地面、墙面、桌子等) |
extendX 和 extendZ 描述了平面的大小。打个比方,如果 AR Engine 检测到一张桌子,extendX 可能是 1.2 米(桌子的宽度),extendZ 可能是 0.8 米(桌子的深度)。这两个值是边界矩形的尺寸,不是实际的多边形形状。
label 是平面的语义标签,用 ARSemanticPlaneLabel 枚举表示。这个比 planeType 更具体——planeType 只告诉你"这是个朝上的水平面",而 label 能告诉你"这是一张桌子"或者"这是地面"。语义标签有以下几种:
UNKNOWN(0):未知平面类型WALL(1):墙面FLOOR(2):地面SEAT(3):座位,如椅子或凳子TABLE(4):桌子
有了语义标签,你就能做更智能的交互。比如检测到是 TABLE,就在上面放虚拟杯子;检测到是 FLOOR,就在上面放虚拟家具;检测到是 WALL,就在上面挂虚拟画。
怎么获取平面的实际形状?
前面说的 extendX 和 extendZ 只是边界矩形的尺寸,但实际的平面可能不是矩形的——比如一张 L 形的桌子,或者一个不规则的地面区域。
要获取平面的实际形状,需要用 getPolygonXZ() 方法:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let trackables: Array<arEngine.ARTrackable> = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
let plane = trackables[0] as arEngine.ARPlane;
plane.getPolygonXZ();
这个方法返回一个 ArrayBuffer,里面包含了平面 2D 顶点的坐标数组。这些坐标是在平面局部坐标系的 X-Z 平面上的。
使用的时候,需要把 ArrayBuffer 转换成 float32 类型的数组。每两个连续的值代表一个顶点的 (x, z) 坐标。把所有顶点按顺序连起来,就得到了平面的实际边界形状。
打个比方,如果检测到的桌面是一个矩形,那这个数组里可能有 8 个值(4 个顶点,每个顶点 2 个坐标):[x1, z1, x2, z2, x3, z3, x4, z4]。你可以用这些坐标来绘制平面的轮廓,或者判断某个点是否在平面范围内。
怎么判断一个点在不在平面内?
AR Engine 提供了两个方法来判断一个点是否在平面范围内:
isPoseInExtents(pose):判断一个位姿点是否在平面的边界矩形内。这个判断比较粗略,但速度快。
isPoseInPolygon(pose):判断一个位姿点是否在平面的实际多边形内。这个判断更精确,但计算量稍大。
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let trackables: Array<arEngine.ARTrackable> = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
let pose: arEngine.ARPose = trackables[0].getPose();
let plane: arEngine.ARPlane = trackables[0] as arEngine.ARPlane;
plane.isPoseInExtents(pose);
这段代码先获取了第一个平面的位姿,然后判断这个位姿是否在平面的边界矩形内。注意 getPose() 是 ARTrackable 基类的方法,返回的是可追踪对象的位姿信息。
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let trackables: Array<arEngine.ARTrackable> = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
let pose: arEngine.ARPose = trackables[0].getPose();
let plane: arEngine.ARPlane = trackables[0] as arEngine.ARPlane;
plane.isPoseInPolygon(pose);
这个用法类似,只是换成了 isPoseInPolygon,判断的是是否在实际多边形内。
在实际使用中,你通常会在命中检测(hitTest)之后用这两个方法。比如用户点击屏幕,你通过 hitTest 找到一个交点,然后用 isPoseInPolygon 判断这个交点是否真的在某个平面内,如果在,就在这个位置放虚拟物体。
平面合并
有时候 AR Engine 会把两个相邻的平面合并成一个。比如你先检测到桌子的一部分,然后又检测到另一部分,AR Engine 会把它们合并成一个完整的桌面。
合并之后,原来的平面会被标记为"被合并"状态,新的合并后的平面成为"父平面"。你可以通过 getSubsumedBy() 方法来获取父平面:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let trackables: Array<arEngine.ARTrackable> = arSession.getAllTrackables(arEngine.ARTrackableType.PLANE);
let plane: arEngine.ARPlane = trackables[0] as arEngine.ARPlane;
plane.getSubsumedBy();
如果这个平面没有被合并,调用这个方法可能会返回空值。如果被合并了,返回的就是合并后的父平面对象。
Mesh 识别怎么开启?
平面检测只能识别出平面,但现实世界有很多东西不是平面的——比如沙发、椅子、不规则的物体表面。这时候就需要 Mesh 识别了。
Mesh 识别会把环境的表面用三角形网格重建出来,给你更完整的 3D 几何信息。
在 ARConfig 里设置 meshMode 就行了:
import { arEngine, arViewController } from '@kit.AREngine';
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
type: arEngine.ARType.WORLD,
meshMode: arEngine.ARMeshMode.ENABLE,
depthMode: arEngine.ARDepthMode.AUTOMATIC
};
meshMode: arEngine.ARMeshMode.ENABLE 告诉 AR Engine:“开启网格识别”。
ARMeshMode 枚举有两个值:
DISABLED(0):网格模式关闭,AR Engine 不会处理或显示网格数据。这是默认值。ENABLE(1):网格模式开启,AR Engine 会尝试处理和显示网格数据。
注意,Mesh 识别默认是关闭的,你需要显式设成 ENABLE 才能用。
怎么获取 Mesh 数据?
Mesh 数据通过 ARFrame.acquireSceneMesh() 来获取:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let frame: arEngine.ARFrame = arSession.getFrame();
frame.acquireSceneMesh();
返回的是一个 ARSceneMesh 对象,它包含两个只读属性:
verticesSize:顶点数量,最小为 0,无上限triangleIndicesSize:三角形索引数量,最小为 0,无上限
然后你可以通过三个方法来获取具体的数据:
获取顶点坐标:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let frame: arEngine.ARFrame = arSession.getFrame();
let sceneMesh: arEngine.ARSceneMesh = frame.acquireSceneMesh();
sceneMesh.getVertices();
getVertices() 返回一个 ArrayBuffer,转换成 float32 后,每 3 个连续的值代表一个顶点的 (x, y, z) 坐标。
获取顶点法线:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let frame: arEngine.ARFrame = arSession.getFrame();
let sceneMesh: arEngine.ARSceneMesh = frame.acquireSceneMesh();
sceneMesh.getVertexNormals();
getVertexNormals() 返回顶点法线数据。法线向量垂直于表面,用于光照计算。有了法线,你才能正确地渲染光照效果——比如阳光照在墙上,墙面朝向太阳的那一面应该更亮。
获取三角形索引:
import { arEngine } from '@kit.AREngine';
// arSession创建参考ARSession.getFrame接口示例代码
let frame: arEngine.ARFrame = arSession.getFrame();
let sceneMesh: arEngine.ARSceneMesh = frame.acquireSceneMesh();
sceneMesh.getTriangleIndices();
getTriangleIndices() 返回三角形索引数据,转换成 int32 后,每 3 个连续的索引组成一个三角形,指向 getVertices() 返回的顶点数组中的位置。
打个比方,如果索引数组是 [0, 1, 2, 1, 3, 2],那表示有两个三角形:第一个三角形由顶点 0、1、2 组成,第二个三角形由顶点 1、3、2 组成。
平面检测与Mesh识别对比
两种能力各有特点,可根据需求选择:
平面检测和 Mesh 识别能一起用吗?
当然可以!平面检测和 Mesh 识别是两个独立的能力,可以同时开启。
平面检测适合做"把虚拟物体放在桌面上"这类场景——它给你的是干净的平面信息,方便做交互。
Mesh 识别适合做遮挡、碰撞等效果——它给你的是完整的环境几何,更真实但数据量也更大。
如果你两个都需要,就同时开启:
import { arEngine, arViewController } from '@kit.AREngine';
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
type: arEngine.ARType.WORLD,
planeFindingMode: arEngine.ARPlaneFindingMode.HORIZONTAL_AND_VERTICAL,
meshMode: arEngine.ARMeshMode.ENABLE,
depthMode: arEngine.ARDepthMode.AUTOMATIC
};
不过要注意,同时开启多个能力会消耗更多的计算资源,手机可能会更热、更耗电。如果你的应用只需要其中一个,就只开那个,用 POWER_SAVING 省电模式可以让手机轻松一点。
实际应用场景
AR 家具摆放:平面检测识别出地面和桌面,用户在上面放虚拟家具。这是最经典的 AR 应用场景。
AR 墙面装饰:平面检测识别出墙壁,用户在墙上挂虚拟画框、照片墙或电视。
AR 遮挡效果:Mesh 识别提供环境的完整几何信息,让虚拟物体能被真实物体正确遮挡。比如虚拟小人走到沙发后面,会被沙发挡住。
AR 测量:结合平面检测和命中检测,可以测量现实世界中两个点之间的距离。
AR 游戏:在检测到的平面上做虚拟棋盘、虚拟弹球台等游戏场景。
平面检测和 Mesh 识别是 AR Engine 的基础环境感知能力。有了它们,虚拟物体才能"站"在现实世界的表面上,而不是飘在空中。
更多推荐



所有评论(0)