使用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){//...// 创建点云 MeshMesh 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_NEWERpointCloudMesh.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””

查看点云

查看点云

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

查看点云

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统IO函数接口

目录 前言 一. man手册 1.1 man手册如何查询 1.2 man手册基础 二.系统IO函数接口 三.open打开文件夹 3.1 例1 open打开文件 3.2 open打开文件代码 3.3 例2 创建文件 四.write写文件 4.1 write写文件 五. read读文件 5.1 read读文件与偏移 5.2 偏移细节 5.3 read读文件代码 六.复…

stream使用

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

【项目新功能开发篇】需求分析和开发设计

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

Mysql数据库getshell方法

今天摸鱼时候&#xff0c;突然有人问我不同的数据库getshell的方式&#xff0c;一时间我想到了mysql还有redis未授权访问到getshell的方式&#xff0c;但是仅仅第一时间只想到了这两种&#xff0c;我有查了查资料&#xff0c;找到了上面两种数据库getshell的补充&#xff0c;以…

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

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

vulhub打靶记录——healthcare

文章目录 主机发现端口扫描FTP—21search ProPFTd EXPFTP 匿名用户登录 web服务—80目录扫描search openemr exp登录openEMR 后台 提权总结 主机发现 使用nmap扫描局域网内存活的主机&#xff0c;命令如下&#xff1a; netdiscover -i eth0 -r 192.168.151.0/24192.168.151.1…

分享:搭建企微知识库简单易学步骤

说起企微知识库&#xff0c;可能有些人还不太清楚&#xff0c;为什么现在很懂企业选择搭建企微知识库&#xff1f;其实&#xff0c;企微知识库就是一个装满了企业的各种知识、经验和资料的载体。目的是为了方便员工随时查找和学习、有助于知识的传承和共享、加强团队协作和沟通…

Java SpringBoot中优雅地判断一个对象是否为空

在Java中&#xff0c;可以使用以下方法优雅地判断一个对象是否为空&#xff1a; 使用Objects.isNull()方法判断对象是否为空&#xff1a; import java.util.Objects;if (Objects.isNull(obj)) {// obj为空的处理逻辑 }使用Optional类优雅地处理可能为空的对象&#xff1a; impo…

文心一言 VS 讯飞星火 VS chatgpt (227)-- 算法导论16.3 9题

九、证明&#xff1a;对于一个由随机生成的8位字符组成的文件&#xff0c;没有任何压缩方法可以望将其压缩、哪怕只是压缩一位。(提示&#xff1a;比较可能的文件数量和可能的编码文件数量。) 文心一言&#xff1a; 确实&#xff0c;对于一个由随机生成的8位字符组成的文件&a…

分布式锁 — Redisson 全面解析!

前言 分布式锁主要是解决集群&#xff0c;分布式下数据一致性的问题。在单机的环境下&#xff0c;应用是在同一进程下的&#xff0c;只需要保证单进程多线程环境中的线程安全性&#xff0c;通过 JAVA 提供的 volatile、ReentrantLock、synchronized 以及 concurrent 并发包下一…

某眼实时票房接口获取

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

每日一题(leetcode169):多数元素-哈希、随机、分治

哈希&#xff1a; class Solution { public:int majorityElement(vector<int>& nums) {int lennums.size();unordered_map<int,int> map;for (int i0;i<len;i){if(map.find(nums[i])map.end()){map[nums[i]]1;}else{map[nums[i]];}}int seqlen/2;int ansnu…