ARKit之路-平面检测

版权声明:Davidwang原创文章,严禁用于任何商业途径,授权后方可转载。

  平面检测是很多AR应用的基础,无论是ARKit、ARCore还是Huawei AREngine、SenseAR等SDK,都提供平面检测功能。在SDK底层中,计算机视觉算法根据摄像头图像输入检测特征点,并依据特征点三维信息构建空间环境,将符合特定规律的特征点划归为平面。

  ARKit检测平面的原理:ARKit对从设备摄像头获取的图像进行分析处理,提取分离图像中的特征点(这些特征点往往是图像中明暗、强弱、颜色变化较大的点,因此,特征点通常都是边角位置点),利用VIO和IMU计算并跟踪这些特征点的三维空间信息,在跟踪过程中,对特征点信息进行处理,并尝试将空间中位置相近或者符合一定规律的特征点构建成平面,如果成功即是检测出了平面。平面有位置、方向和边界信息,ARKit负责检测平面以及管理这些被检测到的平面,但它并不负责渲染平面。

  根据需求,我们可以设置平面检测的方式,如水平平面(Horizontal)、垂直平面(Vertical)、水平平面&垂直平面(Horizontal&Vertical)或者不检测平面(Nothing)。在运行ARSession前,需要在ARConfiguration中指定平面检测方式,如下代码所示,在示例代码中我们设置了既进行水平平面检测,也进行垂直平面检测。需要注意的是,检测平面是一个消耗性能的工作,应当根据应用需求选择合适的检测方式并在适当的时机关闭平面检测以优化应用性能。

let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal,.vertical]

  在进行平面检测时,ARKit每帧都会进行平面检测计算,会添加新检测到的平面、更新现有平面、移除过时平面,因此这是一个动态的过程。随着ARKit对现实世界探索的进行,ARKit对环境的理解会更加准确,检测出来的平面也会更加的稳定。
在对检测到的平面可视化之前,我们先了解一下ARView.debugOptions属性,通过设置debugOptions属性可以以可视化的方式查看ARKit运行时的状态信息,如特征点、平面、场景几何网格、世界坐标原点等等,具体可使用的属性由ARView.DebugOptions枚举定义,ARView.DebugOptions枚举值如下表所示。

功能 描述
none 禁止显示所有调试内容
showPhysics 绘制碰撞器(包围盒)和所有刚体
showStatistics 显示性能统计信息
showAnchorOrigins 显示ARAnchor位置
showAnchorGeometry 显示ARAnchor的几何形状
showWorldOrigin 显示世界坐标系原点位置和坐标轴
showFeaturePoints 显示特征点云

  因此,我们可以通过设置ARView.debugOptions属性查看ARKit运行情况,可视化显示特征点、世界坐标原点、检测到的平面等信息。使用下述代码显示特征点与检测到的平面信息。

func makeUIView(context: Context) -> ARView {
     let arView = ARView(frame: .zero)
     let config = ARWorldTrackingConfiguration()
     config.planeDetection = [.vertical,.horizontal]
     arView.session.run(config, options:[ ])
     arView.debugOptions = [.showAnchorGeometry,.showAnchorOrigins,.showFeaturePoints]
     return arView
}

  效果如下图所示,在图中,我们可以看到ARKit检测到的特征点、ARAnchor的位置、检测到平面形状等信息。
ARKit之路-平面检测_第1张图片

  ARView.debugOptions属性指定的枚举项在生产环境中不会产生效果,在实际应用中,一般情况下也不会将这些调试信息显示出来。但有时我们又需要可视化检测到的平面,给用户提供视觉化的检测进度和可用平面信息反馈,当然,也需要使用更个性化、更美观的界面,而不是采用纯色显示,这时我们可以通过遵循ARSessionDelegate代理协议,使用

  session(_ session: ARSession, didAdd anchors: [ARAnchor])

  session(_ session: ARSession, didUpdate anchors: [ARAnchor])

  方法来跟踪并可视化ARKit检测到的平面,如下代码所示。

struct ARViewContainer: UIViewRepresentable {
    func makeUIView(context: Context) -> ARView {
        let arView = ARView(frame: .zero)
        let config = ARWorldTrackingConfiguration()
        config.planeDetection = .horizontal
        arView.session.run(config, options:[ ])
        arView.session.delegate = arView
        arView.createPlane()
        return arView
    }
    
    func updateUIView(_ uiView: ARView, context: Context) {
    }
}

var planeMesh = MeshResource.generatePlane(width: 0.15, depth: 0.15)
var planeMaterial = SimpleMaterial(color:.white,isMetallic: false)
var planeEntity  = ModelEntity(mesh:planeMesh,materials:[planeMaterial])
var planeAnchor = AnchorEntity()

extension ARView :ARSessionDelegate{
    func createPlane(){
        let planeAnchor = AnchorEntity(plane:.horizontal)
        do {
           planeMaterial.baseColor = try .texture(.load(named: "Surface_DIFFUSE.png"))
            planeMaterial.tintColor = UIColor.yellow.withAlphaComponent(0.9999)
           planeAnchor.addChild(planeEntity)
            self.scene.addAnchor(planeAnchor)
        } catch {
            print("找不到文件")
        }
    }

    public func session(_ session: ARSession, didAdd anchors: [ARAnchor]){
       guard let pAnchor = anchors[0] as? ARPlaneAnchor else {
          return
        }
        DispatchQueue.main.async {
        planeEntity.model?.mesh = MeshResource.generatePlane(
          width: pAnchor.extent.x,
          depth: pAnchor.extent.z
        )
        planeEntity.setTransformMatrix(pAnchor.transform, relativeTo: nil)
        }
    }
    public func session(_ session: ARSession, didUpdate anchors: [ARAnchor]){
       guard let pAnchor = anchors[0] as? ARPlaneAnchor else {
          return
        }
        DispatchQueue.main.async {
        planeEntity.model?.mesh = MeshResource.generatePlane(
          width: pAnchor.extent.x,
          depth: pAnchor.extent.z
        )
        planeEntity.setTransformMatrix(pAnchor.transform, relativeTo: nil)
        }
    }
}

  在上述代码中,我们首先加载了个性化的界面图片,然后在didAdd anchors和didUpdate anchors这两个方法中实时的对检测到的平面进行修正,将当前检测到的平面大小、位置等信息以图形化的形式展示出来。效果如下图所示。
ARKit之路-平面检测_第2张图片

  提示
  RealityKit目前不支持自定义网格,也不支持对网格进行修改,所以代码中我们使用了创建平面网格的方法修改平面大小。RealityKit目前只能以四边形的形式渲染检测到的平面网格,无法自定义形状。

  另外,在A12以上处理器的设备上,ARKit还支持平面分类,可以在创建平面AnchorEntity时指定检测的平面类型,还可以指定需要的最小平面尺寸,这对一些AR应用来讲非常有用,如我们希望虚拟桌椅放置在地面上而不能放置在天花板上,并且可以指定需要的最小平面尺寸,如果平面尺寸小于桌椅尺寸则不进行任何放置操作。利用ARKit的平面分类和最小平面尺寸功能可以更加准确、以更加符合客观认知的方式添加虚拟物体到场景中。指定平面分类与最小平面尺寸的代码如下代码所示。

let planeAnchor = AnchorEntity(plane:.horizontal,classification: .floor, minimumBounds: [0.2,0.2])

  上述代码指定了需要检测的平面为水平平面,平面分类为地面,最小尺寸为0.2mx0.2m。当前ARKit支持8种平面分类,平面分类由枚举ARPlaneClassification定义,该枚举包含的值如下表所示。

功能 描述
ARPlaneClassificationNone ARKit暂未明确分类
ARPlaneClassificationWall 检测现实世界中的墙或类似的垂直平面
ARPlaneClassificationFloor 检测现实世界中的地面或类似的水平平面
ARPlaneClassificationCeiling 检测现实世界中的屋顶水平面或者类似的比用户设备高的水平平面
ARPlaneClassificationTable 检测现实世界中的桌面或者类似的水平平面
ARPlaneClassificationSeat 检测现实世界中的椅面或者类似水平页面
ARPlaneClassificationDoor 检测现实世界中的各类门或者类似垂直平面
ARPlaneClassificationWindow 检测现实世界中的各类窗或者类似垂直平面

  ARKit平面分类功能在底层使用了机器学习方法,因此是一个对计算资源要求很高的操作,在不需要时应当及时关闭以提高性能。在不确定用户设备是否支持平面分类时,最好的方式是先进行可用性检查,根据检查结果再进行相应处理,如下代码所示。

if ARPlaneAnchor.isClassificationSupported {
       let planeAnchor = AnchorEntity(plane:.horizontal,classification: .floor, minimumBounds: [0.2,0.2])
 }
 else{
       let planeAnchor = AnchorEntity(plane:.horizontal)
 }

你可能感兴趣的:(ARKit之路,RealityKit,ARKit)