ARCore 中文文档 下载 App

平面检测与边界获取详解

平面检测的工作机制

ARCore 的平面检测建立在它的 SLAM 特征点地图之上。整个过程:

  1. 特征点收集:每一帧从摄像头画面提取数千个特征点,跟踪它们的空间位置
  2. 共面性检测:ARCore 不断检查特征点是否呈现共面趋势——如果一批特征点都大致落在同一个平面上,就会创建一个 Plane 对象
  3. 边界扩展:随着看到更多特征点,Plane 的 polygon(边界多边形)会逐渐扩大,直到完整覆盖实际物理平面
  4. 方向判定:ARCore 用表面法线方向把 Plane 分类为水平朝上、水平朝下、垂直、倾斜等类型

这个过程完全是 ARCore 内部自动运行的,开发者只需要在每一帧的 session.update() 之后读取 Plane 列表。

四种 Plane 类型

类型Plane.Type 常量常见物体表面法线方向
水平朝上HORIZONTAL_UPWARD_FACING桌面、地板、茶几、灶台大致朝上
水平朝下HORIZONTAL_DOWNWARD_FACING天花板、吊灯上方大致朝下
垂直VERTICAL墙壁、柜门、窗户大致水平
倾斜INCLINED楼梯台阶、书架斜角非水平非垂直

还有一种特殊的 UNKNOWN 类型——ARCore 在检测初期还无法确定方向时会返回这个类型,通常是暂时的。

用法:根据类型做交互

session.update(frame)

val planes = session.getAllTrackables<Plane>()
planes.forEach { plane ->
    if (plane.trackingState != TrackingState.TRACKING) return@forEach

    when (plane.type) {
        Plane.Type.HORIZONTAL_UPWARD_FACING -> {
            // 可以在上面放桌子、地毯等
            showFurniturePlacementHint(plane)
        }
        Plane.Type.VERTICAL -> {
            // 可以在上面挂画、镜子等
            showWallDecorationHint(plane)
        }
        Plane.Type.INCLINED -> {
            // 楼梯等,可能适合放台阶、滑雪坡道
        }
    }
}

Plane 的边界 Polygon

Plane 对象的核心是它的 polygon 属性——一个多边形列表,描述这个平面的实际物理边界。

val plane: Plane = session.getAllTrackables<Plane>().first()

// polygon 是 List<List<Pose>>
// 外层 List 是多个边界环(外轮廓 + 可能的内部孔洞)
// 内层 List 是一个环上的所有顶点 Pose(首尾相连形成闭合多边形)
val boundaries = plane.polygon

boundaries.forEach { ring ->
    ring.forEach { vertexPose ->
        val worldPos = vertexPose.translation
        // 可以用这些点在 3D 世界中画出平面边界
    }
}

边界的质量受以下因素影响:

extents:粗略的近似尺寸

如果不需要精确的多边形边界,可以用 extents 属性快速获取 Plane 的大致宽高:

val extents: FloatArray = plane.extents  // [width, height](单位:米)

注意:extents 返回的是 AABB(轴对齐包围盒) 的尺寸,不是实际形状的精确尺寸。对于圆角或斜边的桌子,这个值会偏大。

监听新 Plane 的出现

ARCore 不会主动通知开发者有新 Plane 出现——你需要在每一帧检查。一个高效的做法是用 Plane 的标识符(hashCode 或引用)记录已处理过的 Plane:

private val processedPlaneIds = HashSet<Int>()

session.update(frame)

session.getAllTrackables<Plane>().forEach { plane ->
    if (plane.trackingState != TrackingState.TRACKING) return@forEach
    if (!processedPlaneIds.add(plane.hashCode())) return@forEach

    // 这是新检测到的 Plane
    onNewPlaneDetected(plane)
}

平面合并:坑点与处理

ARCore 会在后续帧中合并它判断为同一个物理平面的多个小 Plane。合并后的表现:

开发者需要确保在合并时正确处理旧 Plane 上的 Anchor 和虚拟物体:

session.getAllTrackables<Plane>().forEach { plane ->
    if (plane.trackingState == TrackingState.STOPPED) {
        // 这个 Plane 已停止跟踪(可能被合并)
        // 检查是否有 Anchor 还绑定在它上面
        findAnchorsOnPlane(plane)?.forEach { anchor ->
            anchor.detach()
            // 或者尝试把虚拟物体重新放置到新 Plane 上
        }
    }
}

Plane 合并的时机:ARCore 检测到两个 Plane 的特征点簇有大量重叠时,会在一帧之内完成合并。

影响平面检测的环境条件

条件好坏原因
纹理木纹地板、花纹地毯、海报贴墙纯白色墙面、光滑玻璃桌面、磨砂金属特征点密度
光照明亮均匀的室内光昏暗走廊、强逆光、高频闪烁光源摄像头成像质量
扫描范围缓慢扫过整个房间只盯着桌子一角完整特征点视图
视野深度同时看到近处和远处只看到 20cm 内的物体视差不足
遮挡平面完整暴露桌子上堆满书和杯子特征点被遮挡

给用户的引导文案

根据环境条件自动判断是否需要引导用户:

"请缓慢移动手机扫一下周围环境" "光照条件不太好,建议移到光线充足的地方" "当前没有检测到平面,请对准桌面、地板或墙壁"