精华内容
下载资源
问答
  • 通过RANSAC检测点云中的平面 本节介绍了随机样本共识(RANSAC)算法在点云中检测平面的应用。 RANSAC被广泛用于将模型拟合到实验数据。 RANSAC使用与经典的参数估计算法相反的方法。 RANSAC不会通过从尽可能多的数据...
  • 检测三维点云中的平面, 根据地平面与世界坐标系的约束关系, 遍历并筛选平面, 直至得到地平面, 利用地平面与相机.坐标系的空间关系, 最终计算出相机的外参数, 即相机坐标系内的点与世界坐标系内的点的转换矩阵. 结果 ...
  • 针对实际广泛存在的具有深度变化的多平面场景,提出一种基于匹配点引导采样的多平面检测算法。根据平面结构中匹配点的相似度改进匹配点的采样规则,通过对匹配点的残差信息进行排序和加权分析,获取场景中各个平面...
  • UnityAR-平面检测

    2021-03-05 21:56:37
    一、新建平面 在Unity中,有一类叫Prefabs的预制件,所谓的预制件其实就是一个模块,这个模块包含了GameObjects及其相关联的组件、属性、动作、模型、纹理等等,这是一个功能块,这种设计使得模块可以非常方便的被...

    一、新建平面
    在Unity中,有一类叫Prefabs的预制件,所谓的预制件其实就是一个模块,这个模块包含了GameObjects及其相关联的组件、属性、动作、模型、纹理等等,这是一个功能块,这种设计使得模块可以非常方便的被复用。在我们引入ARCore的相关Prefab前,我们把场景中的”Main Camera” 和”Directional Light”删掉,因为ARCore中已经有这两个GameObject了,Hierarchy窗口,选中”Main Camera”, “Directional Light”,右键选择”Delete” 删除掉(或者在选择后直接按Delete键删除)。
    在这里插入图片描述

    在Project窗口中,找到”GoogleARCore” -> “Prefabs” ,选择 “ARCore Device” 和 “Environmental Light” prefabs,并将这两个 prefabs拖到Hierarchy窗口中,如下图所示。
    在这里插入图片描述

    前面我们介绍过Prefabs,当检测到真实世界中的平面时,我们需要一种在虚拟空间中表示这一特征的方法,这就是使用可视化的虚拟平面,为了使用代码来创建平面,我们也要制作一个平面的Prefabs,当检测到更多真实世界的平面时,我们还要实例化并附加更多的Prefabs,以使可用的虚拟平面更大。
      在Hierarchy窗口,点击右键,选择 “Create”-> “3D Object” ->”Plane”,新建一个平面,命名为”VisualDetectedPlane”如下图所示:
    在这里插入图片描述

    在这里,我们特别需要关注的是在平面的Inspector窗口中,Position一定要归0,同时还要确保Scale为(1,1,1),如果这里有少许偏移,那么在用代码实例化平面后也同样会出现偏移。
    在这里插入图片描述

    二、应用纹理
      有了平面,我们还要给平面赋一个材质,以便在实例化后用户能看到这个平面。保持当前”VisualDetectedPlane”属于被选中状态,点击Mesh Renderer组件左侧的箭头打开Mesh Renderer组件详细信息,在详细信息栏展开后点击Element0后面的那个小圆圈,这将打开材质选择面板,在材质选择面板中,选择”PlaneGrid”,这就为我们的“VisualDetectedPlane”平面赋上了一个漂亮的可视材质纹理了。如下图所示:
    在这里插入图片描述

    三、添加平面渲染脚本
    有了平面,也有了材质纹理了,但我们还需要一个渲染器来将检测到或者扩展出来的平面渲染出来,如果我们自己去写这个渲染器将不会是一件愉快的工作,好在ARCore已经为我们写好了,我们只需要将这个写好的类附加到我们的平面上即可,点击 “Add Component”按钮,在搜索框中输入”Detected”,可以找到DetectedPlaneVisualizer,将这个脚本附件到我们的平面上。如下图所示:
    在这里插入图片描述

    四、制作平面Prefabs
      与前面所说一样,在Hierarchy窗口中,将VisualDetectedPlane平面拖动到Projects窗口中的Prefabs文件夹中,这样我们就做好了VisualDetectedPlane平面的Prefabs,然后删除Hierarchy窗口VisualDetectedPlane平面。
    在这里插入图片描述

    五、更新App Controller
      好了,有了可视化的平面Prefabs了,我们现在需要更新一下我们的App Controller,以便处理检测到的平面的可视化问题。因为平面中我们添加加的DetectedPlaneVisualizer是由ARCore提供的,我们需要在我们的代码中引用其命名空间GoogleARCore.Examples.Common。
      然后我们再申明一个GameObject,注意这个变量是public型的,等挂载这个脚本后,这个变量会出现在Inspector窗口中,另外,我们还实例化了两个list来存放我们新检测到的平面和已检测到的所有平面,代码如下所示:

    
    ```csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using GoogleARCore;
    using GoogleARCore.Examples.Common;
    
    public class AppController : MonoBehaviour {
    
        private bool mIsQuitting = false;
        public GameObject DetectedPlanePrefab;
        private List<DetectedPlane> mNewPlanes = new List<DetectedPlane>();
        private List<DetectedPlane> mAllPlanes = new List<DetectedPlane>();
        // Use this for initialization
        void Start () {
            OnCheckDevice();
        }
    
        // Update is called once per frame
        void Update () {
            UpdateApplicationLifecycle();
    
            Session.GetTrackables<DetectedPlane>(mNewPlanes, TrackableQueryFilter.New);
            for (int i = 0; i < mNewPlanes.Count; i++)
            {
                GameObject planeObject = Instantiate(DetectedPlanePrefab, Vector3.zero, Quaternion.identity,
                    transform);
                planeObject.GetComponent<DetectedPlaneVisualizer>().Initialize(mNewPlanes[i]);
            }
    
            Session.GetTrackables<DetectedPlane>(mAllPlanes);
    
        }
        /// <summary>
        /// 检查设备
        /// </summary>
        private void OnCheckDevice()
        {
            if(Session.Status == SessionStatus.ErrorSessionConfigurationNotSupported)
            {
                ShowAndroidToastMessage("ARCore在本机上不受支持或配置错误!");
                mIsQuitting = true;
                Invoke("DoQuit", 0.5f);
            }
            else if (Session.Status == SessionStatus.ErrorPermissionNotGranted)
            {
                ShowAndroidToastMessage("AR应用的运行需要使用摄像头,现无法获取到摄像头授权信息,请允许使用摄像头!");
                mIsQuitting = true;
                Invoke("DoQuit", 0.5f);
            }
            else if (Session.Status.IsError())
            {
                ShowAndroidToastMessage("ARCore运行时出现错误,请重新启动本程序!");
                mIsQuitting = true;
                Invoke("DoQuit", 0.5f);
            }
        }
        /// <summary>
        /// 管理应用的生命周期
        /// </summary>
        private void UpdateApplicationLifecycle()
        {
            if (Session.Status != SessionStatus.Tracking)
            {
                const int lostTrackingSleepTimeout = 15;
                Screen.sleepTimeout = lostTrackingSleepTimeout;
            }
            else
            {
                Screen.sleepTimeout = SleepTimeout.NeverSleep;
            }
    
            if (mIsQuitting)
            {
                return;
            }
        }
        /// <summary>
        /// 退出程序
        /// </summary>
        private void DoQuit()
        {
            Application.Quit();
        }
        /// <summary>
        /// 弹出信息提示
        /// </summary>
        /// <param name="message">要弹出的信息</param>
        private void ShowAndroidToastMessage(string message)
        {
            AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
            if (unityActivity != null)
            {
                AndroidJavaClass toastClass = new AndroidJavaClass("android.widget.Toast");
                unityActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
                {
                    AndroidJavaObject toastObject = toastClass.CallStatic<AndroidJavaObject>("makeText", unityActivity,message, 0);
                    toastObject.Call("show");
                }));
            }
        }
    }
    

    这段代码的逻辑如下,首先我们从Session中得到标记为new的DetectedPlane,并将这些检测到的平面赋给mNewPlanes list表,然后我们根据新检测到的mNewPlanes数量,对每一个新检测到的平面实例化一个我们之前制作的VisualDetectedPlane平面,并将新实例化的平面赋给planeObject以便显示和利用。最后我们还保留一份所有检测到的平面的副本。

    六、测试平面检测功能
      至此,我们已经更新了App Controller,也制作了我们需要可视化的平面,现在我们要把这个脚本挂载到场景中,在Hierarchy窗口中,右键选择”Create Empty”,新建一个空对象,并命名为”AppController”。
    在这里插入图片描述

    点击Detected Plane Prefab后的小圆圈打开物体选择对话框,选择”VisualDetectedPlane”,如下图所示:
    在这里插入图片描述

    展开全文
  • ARKit之路-平面检测

    千次阅读 2020-08-18 21:58:54
      平面检测是很多AR应用的基础,无论是ARKit、ARCore还是Huawei AREngine、SenseAR等SDK,都提供平面检测功能。在SDK底层中,计算机视觉算法根据摄像头图像输入检测特征点,并依据特征点三维信息构建空间环境,将...

    版权声明: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的位置、检测到平面形状等信息。
    在这里插入图片描述

      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这两个方法中实时的对检测到的平面进行修正,将当前检测到的平面大小、位置等信息以图形化的形式展示出来。效果如下图所示。
    在这里插入图片描述

      提示
      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定义,该枚举包含的值如下表所示。

    功能描述
    ARPlaneClassificationNoneARKit暂未明确分类
    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)
     }
    
    展开全文
  • 深度估计&平面检测小结

    千次阅读 2018-12-18 19:40:00
    深度估计和平面检测的工作结合起来可以有很多应用,例如3D重建,场景理解,机器人技术以及SLAM (Simutaneous Localization And Mapping)问题。 深度估计 1. 利用MRF(马尔科夫随机场)建模 这里介绍的是参考文献[1...

    https://yq.aliyun.com/ziliao/582885

    最近一段时间已知忙着赶图像分析与理解的项目,在三个星期内强行接触了CNN,MRF,Caffe,openCV在内的很多东西。
    现在项目已经完全结束了,反而有点怀念看论文写代码的日子~希望能用这篇博文将我这段时间的工作作一个整理,
    也方便我之后写报告。 问题描述

    深度估计是从2D图片中得到深度信息,深度估计主要分为两种形式:从单个的单目图像中获得深度信息,从一系列
    不同角度的单目图像中得到深度信息。在这个项目中我用到的方式主要是第一种。

    平面检测的目标是识别2D图像中属于同一个平面的一部分。

    深度估计和平面检测的工作结合起来可以有很多应用,例如3D重建,场景理解,机器人技术以及SLAM
    (Simutaneous Localization And Mapping)问题。 深度估计 1. 利用MRF(马尔科夫随机场)建模

    这里介绍的是参考文献[1]中提到的一种方法,之后同一波人写的论文包括[2],[3]等都是用到了相似的方式。关于这相关的project还有一个网站Make 3D,这个项目在[3]中有较多介绍,这个网站里也有一些关于3D重建所需要的数据集。  a.特征提取

    作者表示,人类之所以可以很清楚的从单个的单目图像中获取深度信息,是因为用到了纹理变化,纹理梯度还有颜色等“单目线索”。所以只要我们从图片中提取了这些特征,并且有监督的让机器去学习它,那么就可以让机器从单幅的单目图像中获得深度信息。

    在这篇文章中,作者把一张图分割成很多patch,然后估计每一个patch的深度。而对于这些patch,有”绝对”和“相对”两对特征。绝对特征用了纹理变化,纹理梯度和雾霾信息。这里用到了9个3*3的Law’s Mask,6个方向的梯度filter,两个颜色通道(YCbCr颜色空间中的Cb和Cr一共十七个模板。而能量

    深度估计&平面检测小结(k=1,2)
    计算了模板和原图像卷积后的L1与L2距离当做是最初的34个特征向量。除此之外,为了更好的捕捉到全局的信息,在这34个特征向量的基础上还加上了三个不同尺度的四邻域mask。三个不同尺度的四领域机上4个列特征所以特征向量一共的维数是19*34=646维。为了表示相对深度,作者为17个特征中的每一个feature vector计算了10个bin的直方图。
    深度估计&平面检测小结  b.概率模型

    为了表示相邻patch之间的深度关系,这里用到了MRF。除此之外,还对不同尺度邻域间的相互影响进行了建模。

    深度估计&平面检测小结

    这里d表示不同尺度的目标像素与四邻域的均值,x表示的是上节获得的特征向量,而方差σ则与则与第i个patch与第j个patch的相对深度成正比,具体说来:
    深度估计&平面检测小结,其中参数u的目的是使σ与d(i)-d(j)的平方更为接近。  c.小结

    这篇paper用到了在图像分析领域用的比较多的MRF模型来更好的表示图像中邻域之间的关系。而且除了一般使用的高斯MRF模型,还提出了拉普拉斯的MRF建模方式。这两种模型都有各适用之处。作者最后提出这种模型在分析相对深度比绝对深度效果好,绝对深度最多能够估计到81m。不得不提这篇在05年出的论文实验的performance已经非常好了,在CNN还没出来之前这种方式应该是业内很流行的。要说缺点,可能就是特征太多,参数也多,我真的是看了好几遍才看明白。= =|||。本来很想把MRF这种方式自己实现一遍都已经在网上下好数据集了,看着这么多要提取的特征又望而却步了(哭泣脸)。 2. 利用CNN(卷积神经网络)

    12年CNN开始流行起来之后,似乎无论是分类还是回归都可以用CNN搞定,这篇用CNN估计深度的论文也出现的正是时候。利用AlexNet得到粗粒度的深度图再自己训练一个三层的神经网络得到细节部分工作量看起来也并不是特别大,当然不出意料的也得到了state-of-art的结果。[4] a.网络结构

    这张图片就很清楚的说明了整个网络的结构,上面是把AlexNet的前五层拿出来,然后后面套了两个全连接层,得到了一个很coarse的深度图,只能看出一些模糊的特征。为了让它更清楚的表示局部特征,把原图再通过一个三层的神经网络训练,得到一些局部特征。最后与上面的coarse深度图结合一下,就可以得到最终的深度图。
    深度估计&平面检测小结 b.Loss function

    为了评判得到的depth map与groud truth之间的差异,这篇paper运用了下面的误差函数:
    深度估计&平面检测小结

    其中y表示的是通过神经网络得到的预测值,而y*表示的是真实值,这个式子化简之后得:
    深度估计&平面检测小结

    其实表示的就是每一个点的局部误差之和减去整张图片的整体误差,可以看做是一个归一化的处理。 c.小结

    根据最后的实验结果,无论是室内的数据集NYU Depth还是室外的数据集KITTI这种方式都全面吊打make 3D。只要涉及到基于特征的分类或者回归,现在CNN似乎都处于王者地位。唯一的缺点可能就是训练的时间长吧。 3. 代码实现

    最开始看的就是CNN那一篇,当时为我们实验室没有GPU而焦虑烦躁了很久很久。之后上github发现有这篇论文的重现代码,于是我开始撸起袖子一点一点装caffe,opencv,从零开始接触这些,居然发现还挺顺利的,都被自己感动了。严格的说起来,这一个部分的代码不算是我实现的,我只是一个搬运工。但是为了能将代码运行成功我也是费了相当大的功夫的。

    我们项目里用到的是github博主已经训练好的caffemodel,这个model是在NYU的训练集上训练的,也就是说我如果直接拿来用几乎是只符合室内的:( …这里的test script就是将输入图片resize成固定的大小,然后用吧input扔进训练好的模型就可以得到output了。

     
     

    1 2 3 4

     
     

    input = loadImage(imagename, 3, WIDTH, HEIGHT) input *= 255 input -= 127 output = testNet(net, input)

    ps:我发现原来这篇NIPS 2014的paper有项目网站,而且其中还有source code~是在theano框架下写的,weights也在里面。我决定安装一波theano再做一次实验。 平面检测

    在做完深度估计之后,思考了蛮久深度估计和平面检测的共同之处。看了[5]之后才发现利用深度信息3D重建之后要探测平面就容易了很多。深度估计和平面检测结合起来在机器人技术,场景理解等方面都有所应用。 1. 利用深度信息与超像素算法

    这篇论文([5])看了很多遍,前面一部分是按照前面提到的MRF模型得到了深度信息,后面一个部分则是用重建后的3D点和超像素算法判断共面。论文中用到的超像素算法是[6],这是一个基于图的贪心聚类算法,实现比较简单,但是年代比较久远。在经过了一番调研之后,我决定利用PAMI 2012中一片论文[7]提到的SLIC方法来做过分割,这个方法的核心思想是利用颜色和位置的距离信息作KNN聚类。 a.超像素算法SLIC

    SLIC 即simple linear iterative clustering。分簇的依据是像素之间的颜色相似性与邻近性。其中颜色相
    似性的度量因子是lab 颜色空间的L2 范数,颜色邻近性的度量因子是图像二维坐标空间xy。因而综合的度量因子
    是[labxy]五维空间。下面所述的距离度量因子由下式计算得到:

    深度估计&平面检测小结

    深度估计&平面检测小结

    深度估计&平面检测小结

    其中Ns与Nc分别是距离与颜色的权重。

    算法思路是对种子坐标为中心的2S*2S范围内所有像素,求这些像素到种子坐标像素的距离度量因子dist,相邻簇之间的重叠区域像素按照距离最小的种子编号(BlockIndex)标记。整幅图像扫描一遍之后,每个像素点都对应一个BlockIndex,相同BlockIndex 的像素属于同一个簇。接下来进入迭代,对上一次划分的每一个簇,求出每一个簇的labxy 均值,作为新的簇心(种子),按照上述规则重新标记,当迭代一定次数之后,分簇结果基本不发生改变即划分完成,迭代结束。(这里摘自SLIC图像超像素分割算法解析) b.平面估计

    在获取了超像素分割的cluster,以及通过获取的深度信息重建了3D坐标之后,就可以来进行平面估计了。主要分三步: 对于过分割的每一个cluster,根据cluster中的3D点拟合出一个平面并求出一个法向量 根据超像素分割的结果建立一个邻接矩阵判断BlockIndex之间是否相邻 根据BFS算法遍历邻接矩阵,通过每个cluster拟合平面法向量的夹角余弦来判断相邻cluster之间是否共面。

    主要流程是这样,其中拟合平面的部分我用到了SVD降维的方法,即求出取样点的协方差矩阵,对角化后最小的特征值对应的特征向量就是平面的法向量。 c.小结

    这一部分算法还是很好理解的,看懂算法我基本上就可以开始撸代码了。这个算法的优点在于简单,直观,但是缺点在于有很多参数影响(SLIC的参数,夹角余弦共面的阈值),而且这种方法是极其依赖于深度图的效果(我们组做presentation的时候老师说就是因为深度图不够好所以结果差强人意)。下面提到的方法比较复杂,但是实现的效果也会好一些。 2. RVM+MRF建模

    这个方法出自[8].这个paper我觉得可读性真的是不太好,读了几遍才总算是把它的方法给看明白了,核心思想和NIPS 2005深度估计那篇paper类似,提取特征,马尔科夫建模得到每个像素点是否处于平面区域以及每个平面的方向,之后又用滑动窗口再去检测那些不同方向之间重叠的区域的具体方向,这样就能得到一个比较准确的结果。 a.平面评估

    这篇paper提到的Depth Estimation的方法主要出自[9],通过这种方式可以粗略估计独立区域的方向。

    首先提取特征,这里主要用Gaussian descriptor提取了纹理和颜色特征,具体说来纹理体征用到12个bin的梯度直方图,而颜色特征则是用到了20个bin的红绿蓝三通道的密度直方图。由于得到的特征维度太大,所以利用bag of words的方法把这些特征KNN聚类减少维度(Bag-of-words model)。为了合并得到的纹理和颜色两个词典,用NOMF(Non-negative matrix factorization)将两个文档矩阵合并起来。最后对于图像的每一个区域根据提取的特征词典创建空域图,而空域图Sa,Sb的相似度深度估计&平面检测小结也是之后作回归和分类的基础。

    在得到了相似度ρ之后,可以用一个基于ρ的核函数

    深度估计&平面检测小结

    来进行RVM(Relevance vector machine)进行回归得到角度值以及进行二值分类得到它是否为一个平面。 b.平面检测

    前面的RVM只能做到平面的评估,在边缘细节方面的方向估计可能还不够精确。所以来要用MRF建模对上一步中得到的结果进一步改进。

    首先用sweep window在整张图像上滑动,对于那些显著特性(over-lapping area)的点,把这些点作为圆心,利用周围的点的特性来判断圆心的点是否为平面以及角度。利用多组半径做实验,取中位数得到的属于平面的概率ri,以及它的方向di。

    之后利用MRF进行建模,对于是否为平面只需做二值分类,而方向判定则转换为属于一张图中有限平面之一,其中ri和di用的是sweep window得到的结果,n表示图像中每一个可能的平面对应的方向。

    深度估计&平面检测小结

    深度估计&平面检测小结 c.小结

    最后这个实验得到的结果是,88%对是否为平面的判断是正确的,而对于角度有18.3°的误差,此外,这种方法对于检测小区域的效果比较差。总体说来,这里的平面检测没有用到重构得到的3D坐标,直接用2D坐标建模,应该比之前的方式实现起来更容易。但是这里用了两个模型一共三次,RVM+MRF+RVM,而且篇paper的整个算法流程真的讲的很难明白。对于用如此复杂的方式得到一个并不算好的结果,我对这种方法是不喜欢的。 3. 代码实现

    这里用到的是前面一篇paper的方式,slic算法+BFS遍历+共面判断,实现起来也不复杂。在验收的时候老师问我在重建了3D点之后为什么不用ransac,当时回答没有想到,但是现在细细想想,我觉得在一个重建的3D图里用ransac应该只能检测出一个最大的平面(就我理解ransac应该是整张图符合一个模型,而使outliner尽可能少),如果需要多个平面应该要提前分割对每一块去拟合平面。这样想想我还是没有办法用一个尽可能简单清晰的方式用ransac得到这种算法的效果。
    在这边列几个函数,首先是多个3D点拟合平面的:

     
     

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

     
     

    def fit_plane(samples): """ To fit a plane according to a series of 3D points Args: samples Returns: norm_vec: the normal vector of the fitted plane """ centroid = np.mean(samples, axis= 0) for i in range(samples.shape[ 0]): samples[i] = samples[i] - centroid cov = np.dot(np.transpose(samples),samples) U,s,V = np.linalg.svd(cov) norm_vec = V[ 2][:] return norm_vec

     

    然后是BFS遍历的部分:

     
     

    1 2 3 4 5 6 7 8 9 10 11 12 13 14

     
     

    for i in range(seg_num): #begin BFS if(visited[i]== False): visited[i] = True qNode.append(i) while(len(qNode)!= 0): current = qNode.pop( 0) for j in range(seg_num): if(adj_matrix[current][j]!= 0 and visited[j]== False): visited[j] = True qNode.append(j) sample1 = get_samples(idx_clusters,current,sample_num,depth) sample2 = get_samples(idx_clusters,j,sample_num,depth) if(is_coplanar(sample1,sample2)): root[j] = root[current] #end BFS

    网站建设

    网站建设其实是项目一开始没有考虑到的。一刚开始我就只是git clone了两个项目SLIC-Superpixels(in C++)和Depth-estimation(in python),在SLIC算法之中增加了[5]这篇paper的实现。也就是说核心算法部分用到了python和C++两种语言。本来觉得能跑出结果就很好了,可是在验收的前一周突然又想到可以做一个demo展示一下,不然还有一个星期的时间很浪费。最后大胆的决定用python将plane-detection的部分完全重构一遍,最后用Django框架写一个前端页面与后台可以连接起来。 1. Django简介

    Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC的框架模式,即模型M,视图V和控制器C。利用Django开发可以省去很多web开发的麻烦,程序员可以专注于写应用而不用去造轮子。并且它是开源且免费的。(Django Overview) 2. view与template

    关于django我了解的不多,也主要是现学现卖了。这里我的项目主要用到了template和view方面的知识,简单的说一下我用到的功能,如需更系统学习django可以访问django Documentation.

    view负责的是对视图的渲染,一个页面中有一个内容是动态的,不是静态写在html中而是要需要一定的操作(譬如从数据库中提取,或是经过一定处理)才能够展现在静态页面里的。访问了一个views.py中的函数就相当于提前把要在页面上显示的内容准备一下,之后再返回一个已经写好的html页面,并把html中相关的动态内容替换成处理后的内容。在本项目中,Views.py中的 get pic 函数调用了 get depth 与 get_ plane 两个函数得到了所需要的深度图和平面检测图,并以一定的名称保存到本地。

    template负责的是展示的页面,我的项目中有一个demo.html页面负责选择图片上传,还有一个show_pic.html的页面负责展示原图,深度图,两种不同形式的平面检测图。具体参见我们的Demo Video这两个页面放在新建的template文件夹中。

    除此之外,还要在url.py里进行配置,这一步主要是将在浏览器地址栏中输入的url与views.py中渲染页面的函数对应起来。

    这一部分详细见 Part3:Views and templates. 3. 代码实现

    处理图片之后显示的代码在views.py中的get pic 函数中,在get _depth 与get plane 两个函数中都把处理过后的图片保存下来了。

     
     

    1 2 3 4 5 6 7 8 9 10 11 12

     
     

    def get_pic(request): if request.method== 'POST': try: image = request.FILES[ 'image'] img = Image.open(image) filepath= 'media/origin.png' img.save(filepath) get_depth(filepath) get_planar( 'media/img.png', 'media/depth.png') return render_to_response( 'show_pic.html',{ 'image': '/media/test.png'}) except Exception,e: return HttpResponse(e) 小结

    前前后后大概花了三个星期来完成这个项目,又花了三天的时间码完这篇1w字的技术博客真是满满的成就感啊~这个项目的源码在这里,我们的项目网站在这里。说起来我对机器学习和深度学习也并没有很了解,关于深度估计和平面检测这块我也是第一次接触,上文中很多提到的方法可能有错误或者不足,也希望大家能指出来。接下来自己想学习部分在python爬虫 / tensorflow写深度学习。立个flag吧,四月份还会写一篇技术博客。加油。 参考文献 Learning Depth from Single Monocular Images, Ashutosh Saxena, Sung H. Chung, Andrew Y. Ng. NIPS 2005. 3-D Depth Reconstruction from a Single Still Image, Ashutosh Saxena, Sung H. Chung, Andrew Y. Ng. In IJCV 2007. Make3D: Learning 3D Scene Structure from a Single Still Image, Ashutosh Saxena, Min Sun, Andrew Y. Ng. IEEE Transactions of Pattern Analysis and Machine Intelligence (PAMI), vol. 30, no. 5, pp 824-840, 2009. Depth Map Prediction from a Single Image using a Multi-Scale Deep Network.(Nips 2014) Accurate 3D ground plane estimation from a single image(ICRA 2009) Efficient Graph-Based Image Segmentation,IJCV 2004,MIT SLIC Superpixels Compared to State-of-the-art Superpixel Methods, IEEE Transactions on Pattern Analysis and Machine Intelligence Detecting planes and estimating their orientation from a single image, In Proc. of BMVC 2011. 赏

    谢谢你请我吃糖果

    以上是深度估计&平面检测小结的全部内容,在云栖社区的博客、问答、公众号、人物、课程等栏目也有深度估计&平面检测小结的相关内容,欢迎继续使用右上角搜索按钮进行搜索opencv学习 ,以便于您获取更多的相关知识。

    展开全文
  • 霍夫变换大多都用在二维平面中的线、圆检测,这里将其扩展到三维空间点的平面检测。其中建立的霍夫变换空间如下: 核心代码: #include <iostream> #include <opencv2/opencv.hpp> #include <...

    霍夫变换大多都用在二维平面中的线、圆检测,这里将其扩展到三维空间点的平面检测。其中建立的霍夫变换空间如下:
    在这里插入图片描述
    在这里插入图片描述
    核心代码:

    #include <iostream>
    #include <opencv2/opencv.hpp>
    #include <string>
    #include <math.h>
    #include <vector>
    #include "depthtoRGB.h"
    #include "My_Img.h"
    
    #define MO_CAMERA_IMAGE_WIDTH	(1280)
    #define MO_CAMERA_IMAGE_HEIGHT	(720)
    
    /* camera output */
    float fBF;
    float fBase;
    
    /* show image */
    cv::Mat RefImg;
    cv::Mat DepImg;
    
    using namespace std;
    using namespace cv;
    
    struct my_point
    {
        double x;
        double y;
        double z;
    };
    
    struct my_hough
    {
        int m;
        int n;
        int r;
        int num;
    };
    
    
    // 排序法则,其中:< 升序    >降序
    bool comparison(my_hough a,my_hough b)
    {
     return a.num > b.num ;
    }
    
    int main()
    {
        Mat RefImg = cv::Mat::zeros(720, 1280, CV_8UC3);        // 定义RGB图像
        Mat DepImg(720,1280,CV_8UC3,Scalar(0,0,0));             // 定义伪深度图像
        Mat dep_iarray = cv::Mat::zeros(720, 1280, CV_32SC1);   // 定义深度图像
        float fBF, fBase;                                       // 定义相机参数
        string path = "../data/";                               // 数据路径
    
    
        My_Img(path, RefImg, dep_iarray, &fBF, &fBase);         // 获取待处理图像
    
        int ***ResFlag;                                         // 定义待检测区域范围坐标点是否存在数据,为了提高检测速度,范围为x:-20到20,y:-20到20,z:0到150,(dm)
        ResFlag = new int**[40];
        for(int x=0; x<40; x++)
        {
            ResFlag[x] = new int*[40];
        }
        for(int x=0; x<40; x++)
        {
            for(int y=0; y<40; y++)
            {
                ResFlag[x][y] = new int[150];
                for(int z=0; z<150; z++)
                {
                    ResFlag[x][y][z] = 0;
                }
            }
        }
    
        for(int i=0; i<720; i++)
        {
            for(int j=0; j<1280; j++)
            {
                if(dep_iarray.at<int>(i,j) > 5)
                {
                    DepImg.at<Vec3b>(i,j) = depthToRGB(dep_iarray.at<int>(i,j));
                    int x = round(32.0*fBase*(double)(j-640)/(double)(dep_iarray.at<int>(i,j))/100.0);
                    int y = round(32.0*fBase*(double)(i-360)/(double)(dep_iarray.at<int>(i,j))/100.0);
                    int z = round(32.0*fBF/(double)(dep_iarray.at<int>(i,j))/100.0);
    
                    if(z > 13 && z < 149 && abs(x) < 19 && abs(y) < 19)     // 判断检测区域没对应坐标点是否存在点,相当于一次滤波
                    {
                        ResFlag[x+20][y+20][z]++;                           // 存在点,对给区域点进行合并,并进行自加
                    }
                }
            }
        }
    
        vector<my_point> points;                // 待检测点容器
        my_point point_temp;                    // 临时点
        for(int x=0; x<40; x++)
        {
            for(int y=0; y<40; y++)
            {
                for(int z=13; z<150; z++)
                {
                    if(ResFlag[x][y][z] > 0)
                    {
                        point_temp.x = x-20;        // 转为实际坐标点
                        point_temp.y = y-20;
                        point_temp.z = z;
                        points.push_back(point_temp);
                    }
                }
            }
        }
    
    
        int ***hist;            // 定义直方图 其大小为180*360*310
        hist = new int**[180];
        for(int m=0; m<180; m++)
        {
            hist[m] = new int*[360];
        }
        for(int m=0; m<180; m++)
        {
            for(int n=0; n<360; n++)
            {
                hist[m][n] = new int[310];
                for(int r=0; r<310; r++)
                {
                    hist[m][n][r] = 0;
                }
            }
        }
    
        // 为了提高效率,先计算各个角度,直接调动
        double msin_array[180] = {0};
        double mcos_array[180] = {0};
        double nsin_array[360] = {0};
        double ncos_array[360] = {0};
        for(int m=0; m<180; m++)
        {
            msin_array[m] = sin(m*3.1415/180);
            mcos_array[m] = cos(m*3.1415/180);
        }
        for(int n=0; n<360; n++)
        {
            nsin_array[n] = sin((n-180)*3.1415/180);
            ncos_array[n] = cos((n-180)*3.1415/180);
        }
    
    
        int step = 2;                           // 设置步长,即检测精度
        for(int k=0; k<points.size(); k++)      // 对每个点进行处理
        {
            for(int m=0; m<180; m+=step)
            {
                for(int n=0; n<360; n+=step)
                {
                    int r = (int)(points.at(k).x*msin_array[m]*ncos_array[n]
                            + points.at(k).y*msin_array[m]*nsin_array[n]
                            + points.at(k).z*mcos_array[m] + 155);
                    if(r<310)
                    {
                        hist[m][n][r]++;
                    }
    
                }
            }
        }
    
        vector<my_hough> houghs;        // 定义直方图容器,对直方图进行排序
        my_hough hough_temp;
        for(int m=0; m<180; m+=step)
        {
            for(int n=0; n<360; n+=step)
            {
                for(int r=0; r<310; r++)
                {
                    if(hist[m][n][r] > 100)
                    {
                        hough_temp.m = m;
                        hough_temp.n = n;
                        hough_temp.r = r;
                        hough_temp.num = hist[m][n][r];
                        houghs.push_back(hough_temp);
                    }
                }
            }
        }
        sort(houghs.begin(), houghs.end(), comparison);     // 排序
    
        for(int k=0; k<1; k++)
        {
            for(int i=0; i<720; i++)
            {
                for(int j=0; j<1280; j++)
                {
                    if(dep_iarray.at<int>(i,j) > 5)
                    {
                        double x = 32.0*fBase*(double)(j-640)/(double)(dep_iarray.at<int>(i,j))/100.0;
                        double y = 32.0*fBase*(double)(i-360)/(double)(dep_iarray.at<int>(i,j))/100.0;
                        double z = 32.0*fBF/(double)(dep_iarray.at<int>(i,j))/100.0;
                        if(abs(houghs.at(k).r - (x*msin_array[houghs.at(k).m]*ncos_array[houghs.at(k).n]
                                         + y*msin_array[houghs.at(k).m]*nsin_array[houghs.at(k).n]
                                         + z*mcos_array[houghs.at(k).m] + 155)) < 2)            // 绘制直方图最大值的点云
                        {
                            RefImg.at<Vec3b>(i,j)[0] =0;
                            RefImg.at<Vec3b>(i,j)[1] =0;
                            RefImg.at<Vec3b>(i,j)[2] =255;
                        }
                    }
                }
            }
        }
    
        cout << "最大点云参数:" << houghs.at(0).m << "," << houghs.at(0).n << "," << houghs.at(0).r << endl;
    
        imshow("RefImg", RefImg);
        imshow("DepImg", DepImg);
        waitKey(0);
        return 0;
    }
    源码即数据下载链接:https://download.csdn.net/download/OEMT_301/12101536
    

    最终效果:
    原图:
    在这里插入图片描述

    深度图:
    在这里插入图片描述
    检测结果:
    在这里插入图片描述
    参考:https://blog.csdn.net/OEMT_301/article/details/103153458

    展开全文
  • labview拟合平面计算,包含工程,各自vi,和解释说明。
  • 开发环境及一些说明 Unity:2021.1.14flc1 导入Package: AR ... (不懂预制件的可以补一下unity的一些基本知识,但这里先不知道也没关系) 导出apk到测试机进行效果测试 淡黄色的范围即为平面检测结果生成的平面
  • ARCore之路-平面检测

    千次阅读 热门讨论 2018-08-24 22:44:17
      在前一节中,我们创建了一个App Controller,构建了我们的AR应用框架来做应用程序的整体流程处理,但是,如果运行我们前面的框架,什么也不会看到,本节中,我们将使用摄像机生成的点云数据来检测和创建平面,...
  • PlaneRCNN: 3D Plane Detection and Reconstruction ...PlaneRCNN:单幅图像的三维平面检测与重建 EN: Figure 1. This paper proposes a deep neural architecture, PlaneRCNN, that detects planar regions and ...
  • 通过对光学平面玻璃样品表面检测结果的分析,对激光数字平面检测系统中,应用泽尼克多项式进行平面分析的系统理论误差以及被测表面形状类型的判别方法进行了深入的研究,并应用误差分析理论,提出了一个在“泽尼克像差...
  • 行业分类-物理装置-一种平面检测方法及装置、平面跟踪方法及装置.zip
  • 针对以往点云多平面检测算法运算时间长、检测结果的准确性易受噪声影响这一问题,提出了一种基于点云几何统计特征的多平面检测算法。该方法首先根据体密度变化率对点云进行粗分割,然后利用多元随机抽样一致性算法...
  • ARFoundation系列讲解-17平面检测

    千次阅读 2020-05-23 17:24:29
    六、更改可视化平面样式(请参考ARFoundation Samples) 1.新建一个Shader命名为FeatheredPlaneShader 代码如下 Shader "Unlit/FeatheredPlaneShader" { Properties { _MainTex ("Texture", 2D) = "white" {}...
  • ARFoundation系列讲解-16平面检测

    千次阅读 2020-05-09 18:36:57
    一、准备工作 1.打开Unity新建一个空场景,将场景中默认的“Main Camera”删除掉 2.Hierarchy->...一些平台需要额外的工作来执行垂直平面检测,因此,如果仅需要水平平面,则应禁用垂直平面检测。..
  • 根据对实际光学平面玻璃表面误差检测结果的分析,深入地研究了激光数字平面检测系统中的理论误差与系统误差,提供了如何根据拟合干涉波面的Zernike多项式的系数对被测表面误差类型的判别方法,并对干涉条纹数量和...
  • 霍夫变换——空间平面检测算法

    千次阅读 2018-01-11 08:26:58
    看了看计算机图形学中关于直线检测的霍夫变换的原理,自己写了一个用于三维空间点中进行平面检测的霍夫变换算法。 先说从最简单的,xy平面图像中,提取直线的霍夫变换开始。 假如xy平面上存在一条直线,用方程y=...
  • AR应平面检测来说并不稀奇,首先我们来说一下锚点与节点,可以这样说对应锚点来说,可以是任意一个形态的事物,并且不局限于虚拟物件,比如说我们现实中的桌子、窗台、床等等都可以理解锚点,当然虚拟物件也可以,对于虚拟...
  • 写在前面:Failure to initialize 在刚开始测试时,我使用的是支持ARCore的华为P20设备,但却出现了Failure to initialize!You hardware does not support this application,sorry 研究完后,发现解决思路是...
  • 《PCL点云库学习&VS2010(X64)》Part 48 基于霍夫变换的点云平面检测法参考文献: Dorit Borrmann, Jan Elseberg, Kai Lingemann, and Andreas Nüchter. The 3D Hough Transform for Plane Detection in Point ...
  • :针对实际广泛存在的具有深度变化的多平面场景,提出一种基于匹配点引导采样的多平面检测算法。根据平面 结构中匹配点的相似度,改进匹配点的采样规则,通过对匹配点的残差信息进行排序和加权分析,获取场景中各个...
  • 使用ARKit,通过摄像机捕捉到真实世界并在其中加入计算机程序创造的虚拟物品,检测平面,并在平面上覆盖上一张网图,最终效果图如下 创建ARKit项目,Xcode会自动在Main.storyboard中为项目创建一个ARSCNView ...
  • 新教材2020-2021学年高中数学第2册课堂作业:8.4.1 平面 检测 含解析.doc
  • #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "direct.h" #include"stdlib.h" #include
  • 在随机抽样一致性 ( RANSAC)的基础上, 提出了一种对多个平面区域同时进行检测的算法。该算法假设 对同一场景的一对未定标图像已经进行了特征点提取和匹配, 首先利用对极几何约束计算出一对极点, 然后随机抽 取多组 3...
  • 使用RANSAC提取平面

    2018-09-21 21:23:07
    使用PCL中RANSAC探测识别平面,并且可将探测出的多个平面进行单独保存。
  • 今天小编就为大家分享一篇opencv3/C++ 平面对象识别&透视变换方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 平面提取检测的方法 https://github.com/muratkendir/PlaneDetectionwPCL 报错一:找不到头文件 include路径更改符号,系统不同注意斜杠方向,解决路径报错问题。 对‘pcl::SACSegmentation<pcl::PointXYZ>::...
  • 用于点云平面拟合程序测试,此为三维不平整表面点云(.obj格式),相关代码请见本人博客,供大家交流学习使用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 50,296
精华内容 20,118
关键字:

平面检测