使用ARCore深度API实现点云采集

一、深度API

本小节内容摘自ARCore官方文档。

ARCore 深度API

Depth API 可助力实现对象遮挡、提升沉浸感和新颖的互动体验,从而增强 AR 体验的真实感。

在下图中,右侧画面是采用深度API进行遮挡后的效果,与左侧图相比更加真实。

ARCore深度示例图

深度值

给定实测几何图形上的点 A 和代表深度图像中同一点的 2D 点 a,Depth API 在 a 处给出的值等于投影到主轴上的 CA 的长度。这也可称为 A 相对于相机原点 C 的 z 坐标。使用 Depth API 时,请务必注意,深度值不是光线 CA 本身的长度,而是光线的投影。

图

应用场景

启用遮挡
遮挡(即准确渲染虚拟物体在现实物体后面)对于沉浸式 AR 体验至关重要。假设有一个虚拟 Andy,用户可能需要放置在包含门边有后备箱的场景中。渲染时没有遮挡,Andy 会不切实际地与树干边缘重叠。如果您使用场景的深度来了解虚拟 Andy 相对于木箱等周围环境的距离,就可以准确地渲染 Andy 的遮挡效果,使其在周围环境中看起来更逼真。

ARCore文档截图

改变场景
您可以渲染虚拟雪花,让其坐在沙发的扶手和枕头上,或者在雾气弥漫的客厅中飘散,让用户进入身临其境的新世界。您可以使用“深度”创建虚拟光线互动、隐藏后方以及重新照亮真实物体的场景。

ARCore文档截图

距离和景深
需要显示距离较远的物体?您可以通过 Depth API 使用距离测量并添加景深效果,例如对场景的背景或前景进行模糊处理。

ARCore文档截图

支持用户与 AR 对象互动
让虚拟内容通过碰撞和物理与现实世界互动,让用户能够通过您的应用“触摸”世界。让虚拟物体绕过现实世界的障碍物,或让虚拟彩弹击中并泼洒到真实的树上。将基于深度的碰撞与游戏物理学相结合,可以打造栩栩如生的体验。

ARCore文档截图

改进点击测试
深度可用于改进点击测试结果。平面点击测试仅适用于具有纹理的平面表面,而深度点击测试则更加详细,甚至适用于非平面和低纹理区域。这是因为深度命中测试使用来自场景的深度信息来确定点的正确深度和方向。

在以下示例中,绿色 Andys 代表标准平面命中测试,红色 Andys 代表深度命中测试。

ARCore文档截图

ARCore文档截图

二、 AR Foundation

本小节记录如何在Unity中开发,通过 AR Foundation 使用“深度”功能。

配置安卓权限

这里需要配置安卓清单,以下方式二选一。

自定义AndroidManifest

在清单中添加<users-feature>其值设为“com.google.ar.core.depth>

配置Unity 项目

导航到 Edit > Project Settings > XR Plug-in Management > ARCore。
Depth 默认设置为 Required。修改为optional

启用深度

为节省资源,ARCore 默认情况下不会启用 Depth API。如需在支持的设备上充分利用深度,您必须通过 Camera 和 ARCameraBackground 组件手动将 AROcclusionManager 组件添加到 AR 相机游戏对象。如需了解详情,请参阅 Unity 文档中的自动遮盖。

在新的AR Session中,检查用户的设备是否支持深度,示例如下:

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

// Check whether the user's device supports the Depth API.
if (occlusionManager.descriptor?.supportsEnvironmentDepthImage)
{
    // If depth mode is available on the user's device, perform
    // the steps you want here.
}

获取深度图像

从AROcclusionManager中获取环境的深度图

// Reference to AROcclusionManager that should be added to the AR Camera
// game object that contains the Camera and ARCameraBackground components.
var occlusionManager = …

if (occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
{
    using (image)
    {
        // Use the texture.
    }
}

提取深度图的距离

如需将 Depth API 用于遮蔽虚拟对象或直观呈现深度数据之外的用途,请从深度图像中提取信息。

Texture2D _depthTexture;
short[] _depthArray;

void UpdateEnvironmentDepthImage()
{
  if (_occlusionManager &&
        _occlusionManager.TryAcquireEnvironmentDepthCpuImage(out XRCpuImage image))
    {
        using (image)
        {
            UpdateRawImage(ref _depthTexture, image, TextureFormat.R16);
            _depthWidth = image.width;
            _depthHeight = image.height;
        }
    }
  var byteBuffer = _depthTexture.GetRawTextureData();
  Buffer.BlockCopy(byteBuffer, 0, _depthArray, 0, byteBuffer.Length);
}

// Obtain the depth value in meters at a normalized screen point.
public static float GetDepthFromUV(Vector2 uv, short[] depthArray)
{
    int depthX = (int)(uv.x * (DepthWidth - 1));
    int depthY = (int)(uv.y * (DepthHeight - 1));

    return GetDepthFromXY(depthX, depthY, depthArray);
}

// Obtain the depth value in meters at the specified x, y location.
public static float GetDepthFromXY(int x, int y, short[] depthArray)
{
    if (!Initialized)
    {
        return InvalidDepthValue;
    }

    if (x >= DepthWidth || x < 0 || y >= DepthHeight || y < 0)
    {
        return InvalidDepthValue;
    }

    var depthIndex = (y * DepthWidth) + x;
    var depthInShort = depthArray[depthIndex];
    var depthInMeters = depthInShort * MillimeterToMeter;
    return depthInMeters;
}

三、示例Demo

这里记录当前ARFoundation关于深度API使用点云的示例Demo。不同的是,这里新增的“点云采集”和“点云导入”功能。

官方示例:arcore-depth-lab

版本信息

        "com.unity.xr.arfoundation": "4.1.5",
        "com.unity.xr.arcore": "4.1.5",
        "com.unity.xr.arkit": "4.1.5"

相机配置

在“AR Session Origin”->“AR Camera"中添加"AROcclusion Manager组件”

组件

此外在示例demo中,这里挂载了"DepthSource"脚本。(这个脚本一个场景只可有且仅有一个激活)
这个脚本主要作用是处理深度图纹理。

点云采集

示例场景

Demo场景路径:Assests/ARRealismDemos/PointCloud/Scenes/RawPointClouds.unity

点云渲染

场景中找到“RawPointCloudBlender”游戏对象,其挂载的组件如下图:

组件

"PointCloudMaterial"是ARCore示例提供的渲染点云的材质(这个后续会用到)。

“RawPointCloudBlender”是渲染点云的核心脚本,它会在MeshFilter上生成Mesh。

点云保存

在运行时,点云实时渲染的Mesh在“RawPointCloudBlender”->MeshFilter上。

因此这里可直接获取Mesh的顶点数据进行保存。

保存为PTS

   public void SaveMeshToFile(string filePath)
    {
        if (meshFilter == null || meshFilter.sharedMesh == null)
        {
            Debug.LogWarning("MeshFilter or Mesh not assigned.");
            return;
        }

        Mesh mesh = meshFilter.sharedMesh;

        // 获取网格数据
        Vector3[] vertices = mesh.vertices;
        //int[] triangles = mesh.triangles;//点云无需三角顶点索引,
        Color[] colors = mesh.colors;

        int minCount = Math.Min(vertices.Length, colors.Length);

        // 创建保存网格数据的文件
        using (StreamWriter writer = new StreamWriter(filePath))
        {
            // 写入顶点数据
            for (int i = 0; i < minCount; i++)
            {
                //PTS格式前三个是 (x,y,z) 坐标 其中,第四个是“强度”值,最后三个是“颜色值”(R,G,B) 
                Vector3 vertex = vertices[i];
                Color c = colors[i];
                //这里暂用ALPHA表示强度值
                writer.WriteLine(vertex.x + " " + vertex.y + " " + vertex.z
                    + " " + (int)(c.a * 255) + " " + (int)(c.r * 255) + " " + (int)(c.g * 255) + " " + (int)(c.b * 255));
            }

        }
    }

保存为OBJ

将上述循环中替换为如下内容:

        // 创建保存网格数据的文件
        using (StreamWriter writer = new StreamWriter(filePath))
        {
            // 写入顶点数据
            foreach (Vector3 vertex in vertices)
            {
                writer.WriteLine("v " + vertex.x + " " + vertex.y + " " + vertex.z);
            }
            //...
        }

点云加载

扩展编辑器

编写脚本,新增点云数据导入功能

public class PTSLoaderEditor : EditorWindow
{
    private string ptsFilePath;
    private Material material;

    [MenuItem("EQ/点云数据/\"加载 *.pts\"")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(PTSLoaderEditor));
    }

    private void OnGUI()
    {
        //...
        LoadPointCloud(string filePath);
        //...
    }

    private void LoadPointCloud(string filePath)
    {
        //...

        // 创建点云 Mesh
        Mesh pointCloudMesh = new Mesh();


        for (int i = 0; i < count; i++)
        {
            string[] parts = lines[i].Split(' ');
            if (parts.Length >= 7)
            {
                float x = float.Parse(parts[0]);
                float y = float.Parse(parts[1]);
                float z = float.Parse(parts[2]);
                _vertices[i] = new Vector3(x, y, z);

                byte a = byte.Parse(parts[3]);
                byte r = byte.Parse(parts[4]);
                byte g = byte.Parse(parts[5]);
                byte b = byte.Parse(parts[6]);
                _colors[i] = new Color32(r, g, b, a);

                _indices[i] = i;
            }
        }

#if UNITY_2019_3_OR_NEWER
        pointCloudMesh.SetVertices(_vertices, 0, count);
        pointCloudMesh.SetIndices(_indices, 0, count, MeshTopology.Points, 0);
        pointCloudMesh.SetColors(_colors, 0, count);
#else
        // Note that we recommend using Unity 2019.3 or above to compile this scene.
        List<Vector3> vertexList = new List<Vector3>();
        List<Color32> colorList = new List<Color32>();
        List<int> indexList = new List<int>();

        for (int i = 0; i < count; ++i)
        {
            vertexList.Add(_vertices[i]);
            indexList.Add(_indices[i]);
            colorList.Add(_colors[i]);
        }

        pointCloudMesh.SetVertices(vertexList);
        pointCloudMesh.SetIndices(indexList.ToArray(), MeshTopology.Points, 0);
        pointCloudMesh.SetColors(colorList);
#endif // UNITY_2019_3_OR_NEWER

        //...
    }
}

查看点云

导入点云

在编辑器中操作如下:

“EQ/点云数据/“加载 *.pts””

查看点云

查看点云

注:这里使用点云材质,可以给点赋予之前传入的颜色值

查看点云

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/512286.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue项目引入字体文件无效

这是原来的&#xff0c;没有生效 font-face {font-family: BebasNeue;src: url(./font/BebasNeue.otf);font-weight: normal;font-style: normal; }这是修改后的&#xff08;多了个空格&#xff09; font-face {font-family: Bebas Neue;src: url(./font/BebasNeue.otf);font-…

stream使用

stream流式计算 在Java1.8之前还没有stream流式算法的时候&#xff0c;我们要是在一个放有多个User对象的list集合中&#xff0c;将每个User对象的主键ID取出&#xff0c;组合成一个新的集合&#xff0c;首先想到的肯定是遍历&#xff0c;如下&#xff1a; List<Long> u…

C++ Primer 总结索引 | 第十二章:动态内存

1、到目前为止&#xff0c;我们编写的程序中 所使用的对象 都有着严格定义的生存期。全局对象 在程序启动时分配&#xff0c;在程序结束时 销毁。对于 局部自动对象&#xff0c;当我们进入 其定义所在的程序块时被创建&#xff0c;在 离开块时销毁。局部static对象 在第一次使用…

前端JS商品规格组合

给定一个数组 let data [{name: "颜色",specs: ["白色", "黑色"],},{name: "尺寸",specs: ["14寸","15寸", "16寸"],},{name: "处理器",specs: ["i5", "i7", "i9&…

AI音乐GPT时刻来临:Suno 快速入门手册!

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

KnowLog:基于知识增强的日志预训练语言模型|顶会ICSE 2024论文

徐波 东华大学副教授 东华大学计算机学院信息技术系副系主任&#xff0c;复旦大学知识工场实验室副主任&#xff0c;智能运维方向负责人。入选“上海市青年科技英才扬帆计划”。研究成果发表在IJCAI、ICDE、ICSE、ISSRE、ICWS、CIKM、COLING等国际会议上&#xff0c;曾获中国数…

【威胁情报综述阅读3】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense

【威胁情报综述阅读1】Cyber Threat Intelligence Mining for Proactive Cybersecurity Defense: A Survey and New Perspectives 写在最前面一、介绍二、网络威胁情报挖掘方法和分类A. 研究方法1&#xff09; 第 1 步 - 网络场景分析&#xff1a;2&#xff09; 第 2 步 - 数据…

【Spring实战项目】SpringBoot3整合WebSocket+拦截器实现登录验证!从原理到实战

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

【快捷部署】013_Podman(3.4.4)

&#x1f4e3;【快捷部署系列】013期信息 编号选型版本操作系统部署形式部署模式复检时间013podman3.4.4Ubuntu 22.04apt-2024-04-03 一、快捷部署 注意! 必须满足&#xff1a;Ubuntu 20.10 and newer #由于本期安装脚本较为简单&#xff0c;所以不制作一键安装脚本&#xf…

JDK下载安装配置

一.JDK安装配置。 1.安装注意路径,其他直接下一步。 2.配置。 下接第4步. 代码复制: JAVA_HOME D:\Program Files\Java\jdk1.8.0_91 D:\Program Files\Java\jdk1.8.0_91\bin 3.验证(CMD)。 java javac java -version 二.下载 1.下载JDK1.5-1.9(所有版本)下载: https://www.…

YOLOV5 改进:更换主干网络为Resnet

1、前言 之前实现了yolov5更换主干网络为MobileNet和vgg网络 本章将继续将yolov5代码进行更改,通过引用官方实现的resnet网络,替换原有的yolov5主干网络 替换的效果如下: 2、resnet 网络结构 测试的代码为官方的resnet34 通过summary 打印的resnet网络结构如下 =======…

Flutter应用发布流程详解:从开发到上架一站式指南

引言 Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;其强大的性能和流畅的用户体验使其备受开发者青睐。然而&#xff0c;开发一款应用只是第一步&#xff0c;将其成功上架到苹果商店才是实现商业目标的关键一步。本文将详细介绍如何使用Flutter将应用程序上…

IP地址如何修改?分享操作技巧

在互联网世界中&#xff0c;IP地址是每台计算机或网络设备的唯一标识&#xff0c;它决定了设备在网络中的位置以及与其他设备的通信方式。然而&#xff0c;有时出于特定需求&#xff0c;我们可能需要修改设备的IP地址。虎观代理将详细阐述如何修改IP地址&#xff0c;并探讨在修…

一文读懂匈奴历史

匈奴&#xff0c;一个曾经叱咤风云的游牧民族&#xff0c;在中国历史上留下了浓墨重彩的一笔。他们的崛起和衰落&#xff0c;不仅影响了中原王朝的兴衰更迭&#xff0c;也深刻地改变了中国北方的民族构成和文化面貌。 1、匈奴的起源 根据司马迁的《史记》记载&#xff0c;匈奴…

某眼实时票房接口获取

某眼实时票房接口获取 前言解决方案1.找到veri.js2.找到signKey所在位置3.分析它所处的这个函数的内容4.index参数的获取5.signKey参数的获取运行结果关键代码另一种思路票房接口:https://piaofang.maoyan.com/dashboard-ajax https://piaofang.maoyan.com/dashboard 实时票房…

mongodb的简单操作

文章目录 前言数据库的创建和删除集合的创建和删除文档的插入和查询异常处理更新数据局部修改符合条件的批量更新加操作 删除文档删除全部数据删除符合条件的数据 统计count统计有多少条数据统计特定条件有多少条数据 分页查询排序查询正则查询比较查询包含查询条件连接查询索引…

Git 如何合并多个连续的提交

我平常的编程喜欢是写一段代码就提交一次&#xff0c;本地一般不攒代码&#xff0c;生怕本地有什么闪失导致白干。但这样就又导致一个问题&#xff1a;查看历史日志时十分不方便&#xff0c;随便找一段提交可以看到&#xff1a; > git log --oneline 8f06be5 add 12/qemu-h…

SpringBoot+thymeleaf完成视频记忆播放功能

一、背景 1)客户要做一个视频播放功能,要求是系统能够记录观看人员在看视频时能够记录看到了哪个位置,在下次观看视频的时候能够从该位置进行播放。 2)同时,也要能够记录是谁看了视频,看了百分之多少。 说明:由于时间关系和篇幅原因,我们这里只先讨论第一个要求,第…

supersqli-攻防世界

题目 加个报错 1 and 11 #没报错判断为单引号字符注入 爆显位 1 order by 2#回显正常 1 order by 3#报错 说明列数是2 尝试联合查询 -1 union select 1,2# 被过滤了 return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); select|update|d…

R语言颜色细分

1.如何对R语言中两种颜色之间进行细分 2.代码&#xff1a; x <- colorRampPalette(c("#FC8D62","#FDEAE6"))(12) #打印向量值 # 按字典顺序排序颜色值 x_sorted <- sort(x,decreasing TRUE)# 打印排序后的颜色值 print(x_sorted)#展示颜色 scales:…