【Unity-学习-014】EasyAR4.0稀疏空间地图 扫描场景功能

2023-11-02

本帖主要描写扫描场景的功能实现,以及一些需要注意的问题。跟上层贴有所关联,想要更多了解请移步链接

场景中有几个重要的预设需要添加。目录如下:

其中:

SparseSpatialMap 用于扫描空间成成点云信息,点云可以将空间数据以点的信息保存下来 SparseSpatialMapController
WorldRoot 点云的空间位置的基本参照 WorldRootController
EasyAR_SparseSpatialMapWorker 点云保存上传等一些操作的入口 ARSession/SparseSpatialMapWorkerFrameFilter

 

场景中,我用Text来debug一些信息,可以显示当前的进度。

操作流程:进入场景开始扫描云点,然后点击保存之后弹出窗口,开始编辑地图名字,编辑完成后,点击上传。

然后创建SceneMaster来对场景进行管理。添加脚本BuildMapController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using easyar;
using System;
using SpatialMap_SparseSpatialMap;
using UnityEngine.SceneManagement;

public class BuildMapController : MonoBehaviour
{
    //稀疏空间地图相关对象
    private ARSession session;
    private SparseSpatialMapWorkerFrameFilter mapWorker;
    private SparseSpatialMapController map;
    /// <summary>
    /// 保存按钮
    /// </summary>
    private Button btnSave; //保存按钮
    private Button btnUpload;  //输入名字后上传
    private GameObject SavePanel;
    /// <summary>
    /// 显示文本
    /// </summary>
    private Text text;

    private InputField inputField;


    private void Awake()
    {
        //稀疏空间地图初始
        session = FindObjectOfType<ARSession>();
        mapWorker = FindObjectOfType<SparseSpatialMapWorkerFrameFilter>();
        map = FindObjectOfType<SparseSpatialMapController>();
        SavePanel = GameObject.Find("MapName");
        inputField = SavePanel.transform.Find("Panel/InputField").GetComponent<InputField>();
        btnUpload = SavePanel.transform.Find("Panel/Btn_Upload").GetComponent<Button>();
        btnSave = GameObject.Find("Canvas/Map/Btn_SaveMap").GetComponent<Button>();
        //显示文本
        text = GameObject.Find("Canvas/Map/Text").GetComponent<Text>();
    }

    void Start()
    {
        map.MapWorker = mapWorker;
        //注册追踪状态变化事件
        session.WorldRootController.TrackingStatusChanged += OnTrackingStatusChanged;

        SavePanel.SetActive(false);
        //初始化保存按钮
        btnSave.onClick.AddListener(OpenUploadPanel);
        btnUpload.onClick.AddListener(Save);
        btnSave.interactable = false;
        if (session.WorldRootController.TrackingStatus == MotionTrackingStatus.Tracking)
        {
            btnSave.interactable = true;
        }
        else
        {
            btnSave.interactable = false;
        }
    }

    /// <summary>
    /// 保存地图方法
    /// </summary>
    private void OpenUploadPanel()
    {
        btnSave.interactable = false;
        SavePanel.SetActive(true);
    }

    string mapName = "";
    private void Save()
    {
        //注册地图保存结果反馈事件
        mapWorker.BuilderMapController.MapHost += SaveMapHostBack;
        //mapWorker.BuilderMapController.MapLoad += LoadMapHostBack;
        //保存地图
        try
        {
            if (!inputField.text.Equals(string.Empty))
            {
                mapName = "Map" + inputField.text;
            }
            else
            {
                mapName = "Map" + DateTime.Now.ToString("YYYY-MM-DD-HH-mm-ss");
            }
            //保存地图
            mapWorker.BuilderMapController.Host(mapName, null);
            text.text = "开始保存地图,请稍等。";
        }
        catch (Exception ex)
        {
            btnSave.interactable = true;
            text.text = "保存出错:" + ex.Message;
        }
    }

    /// <summary>
    /// 保存地图反馈
    /// </summary>
    /// <param name="mapInfo">地图信息</param>
    /// <param name="isSuccess">成功标识</param>
    /// <param name="error">错误信息</param>
    private void SaveMapHostBack(SparseSpatialMapController.SparseSpatialMapInfo mapInfo, bool isSuccess, string error)
    {
        if (isSuccess)
        {
            SavePanel.SetActive(false);

            PlayerPrefs.SetString("MapID", mapInfo.ID);
            PlayerPrefs.SetString("MapName", mapInfo.Name);
            text.text = "地图保存成功。\r\nMapID:" + mapInfo.ID + "\r\nMapName:" + mapInfo.Name;
            MapMetaManager.Save(new MapMeta(mapInfo, new List<MapMeta.PropInfo>()), MapMetaManager.FileNameType.Name);
            Invoke("BackMain", 3);
        }
        else
        {
            btnSave.interactable = true;
            text.text = "地图保存出错:" + error;
        }
    }

    public void BackMain()
    {
        SceneManager.LoadScene("MainMapScene");
    }




    /// <summary>
    /// 摄像机状态变化
    /// </summary>
    /// <param name="status">状态</param>
    private void OnTrackingStatusChanged(MotionTrackingStatus status)
    {
        if (status == MotionTrackingStatus.Tracking)
        {
            btnSave.interactable = true;
            text.text = "进入跟踪状态。";
        }
        else
        {
            btnSave.interactable = false;
            text.text = "退出跟踪状态。" + status.ToString();
        }
    }
}




代码相关:

在Awake获取到三个主要的脚本,然后在start中完成初始化。

1.将 mapWorker 填充进 mapController 中的MapWorker属性中。这一步也可以在场景中直接拖拽也行。

2.在session.WorldRootController.TrackingStatusChanged 注册事件。

 ///注册追踪状态变化事件
 session.WorldRootController.TrackingStatusChanged += OnTrackingStatusChanged;

这个事件的用处是可以监听当前的追踪状态。拿到追踪状态就可做一些事情。例如我用来提示,也可以用来操作一些游戏对象的状态等,或者弹出UI提醒用户做一些操作等。

 private void OnTrackingStatusChanged(MotionTrackingStatus status)
    {
        if (status == MotionTrackingStatus.Tracking)
        {
            btnSave.interactable = true;
            text.text = "进入跟踪状态。";
        }
        else
        {
            btnSave.interactable = false;
            text.text = "退出跟踪状态。" + status.ToString();
        }
    }

3.扫描场景。扫描的过程中,随着点云数量的增加,手机会变的逐渐卡顿,最大的场景支持100平方米,其实也不大,就10*10的地块。实际使用下来一般50平方左右的空间最舒服。

4.点击保存按钮弹出框框,编辑名字后点击上传。

 string mapName = "";
    private void Save()
    {
        //注册地图保存结果反馈事件
        mapWorker.BuilderMapController.MapHost += SaveMapHostBack;
        //mapWorker.BuilderMapController.MapLoad += LoadMapHostBack;
        //保存地图
        try
        {
            if (!inputField.text.Equals(string.Empty))
            {
                mapName = "Map" + inputField.text;
            }
            else
            {
                mapName = "Map" + DateTime.Now.ToString("YYYY-MM-DD-HH-mm-ss");
            }
            //保存地图
            mapWorker.BuilderMapController.Host(mapName, null);
            text.text = "开始保存地图,请稍等。";
        }
        catch (Exception ex)
        {
            btnSave.interactable = true;
            text.text = "保存出错:" + ex.Message;
        }
    }

首先注册一个保存地图后的回调。该回调在完成上传后调用,不管成功与否。

 mapWorker.BuilderMapController.MapHost += SaveMapHostBack;

然后 调用 host 方法来上传地图。

  //保存地图
  mapWorker.BuilderMapController.Host(mapName, null);

然后是对回调信息的处理。处理很简单,就是将这个地图的ID和名字用 PlayerPrefs 存到本地,这个东西自己的觉得方便就用了。大家可以用自己喜欢的方法Json或者其他本地化的方法实现。

    /// <summary>
    /// 保存地图回调
    /// </summary>
    /// <param name="mapInfo">地图信息</param>
    /// <param name="isSuccess">成功标识</param>
    /// <param name="error">错误信息</param>
    private void SaveMapHostBack(SparseSpatialMapController.SparseSpatialMapInfo mapInfo, bool isSuccess, string error)
    {
        if (isSuccess)
        {
            SavePanel.SetActive(false);

            PlayerPrefs.SetString("MapID", mapInfo.ID);
            PlayerPrefs.SetString("MapName", mapInfo.Name);
            text.text = "地图保存成功。\r\nMapID:" + mapInfo.ID + "\r\nMapName:" + mapInfo.Name;
            MapMetaManager.Save(new MapMeta(mapInfo, new List<MapMeta.PropInfo>()), MapMetaManager.FileNameType.Name);
            Invoke("BackMain", 3);
        }
        else
        {
            btnSave.interactable = true;
            text.text = "地图保存出错:" + error;
        }
    }

然后是 MapMetaManager 这个脚本。这个脚本用来处理点云信息。将meta 信息存储到本地先,用来下一步场景编辑时使用。

//================================================================================================================================
//
//  Copyright (c) 2015-2020 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
//  EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
//  and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//================================================================================================================================

using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json; 
using UnityEngine;
namespace SpatialMap_SparseSpatialMap
{
    public class MapMetaManager
    {
        public enum FileNameType
        {
            ID, Name
        }

        private static readonly string root = Application.persistentDataPath + "/SparseSpatialMap";

        public static List<MapMeta> LoadAll()
        {
            //Debug.Log(root);
            var metas = new List<MapMeta>();
            var dirRoot = GetRootPath();
            try
            {
                foreach (var path in Directory.GetFiles(dirRoot, "*.meta"))
                {
                    try
                    {
                        metas.Add(JsonUtility.FromJson<MapMeta>(File.ReadAllText(path)));
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogError(e.Message);
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
            }
            return metas;
        }


        public static bool Save(MapMeta meta, FileNameType fileNameType = FileNameType.ID)
        {
            try
            {
                switch (fileNameType)
                {
                    case FileNameType.ID:
                        File.WriteAllText(GetPath(meta.Map.ID), JsonUtility.ToJson(meta, true));
                        break;
                    case FileNameType.Name:
                        File.WriteAllText(GetPath(meta.Map.Name), JsonUtility.ToJson(meta, true));
                        break;
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
                return false;
            }
            return true;
        }



        public static bool Save(PointData data)
        {
            try
            {
                File.WriteAllText(GetPathTxt(data.mapName + "_PointCloud"), JsonConvert.SerializeObject(data));
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
                return false;
            }
            return true;
        }

        public static List<PointData> Load_PointCloud<PointData>()
        {
            var dirRoot = GetRootPath();
            var datas = new List<PointData>();
            try
            {
                foreach (var path in Directory.GetFiles(dirRoot, "*.txt"))
                {
                    try
                    {
                        //Debug.Log(path); 
                        datas.Add(JsonConvert.DeserializeObject<PointData>(File.ReadAllText(path)));
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogError(e.Message);
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
            }
            return datas;
        }




        public static bool Delete(MapMeta meta)
        {
            if (!File.Exists(GetPath(meta.Map.ID)))
            {
                return false;
            }
            try
            {
                File.Delete(GetPath(meta.Map.ID));
            }
            catch (System.Exception e)
            {
                Debug.LogError(e.Message);
                return false;
            }
            return true;
        }

        private static string GetRootPath()
        {
            var path = root;
            if (!File.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return path;
        }

        private static string GetPath(string id)
        {
            return GetRootPath() + "/" + id + ".meta";
        }

        private static string GetPathTxt(string id)
        {
            return GetRootPath() + "/" + id + ".txt";
        }

    }
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Unity-学习-014】EasyAR4.0稀疏空间地图 扫描场景功能 的相关文章

  • protobuf-jetbrains-plugin插件

    ProtoBuf简介 ProtoBuf 是结构数据序列化方法 可简单类比于 XML 其具有以下特点 语言无关 平台无关 即 ProtoBuf 支持 Java C Python 等多种语言 支持多个平台 高效 即比 XML 更小 3 10倍
  • linux 命令:tr 详解

    tr 命令的功能是转换或删除字符 用法 tr OPTION SET1 SET2 选项 c C complement 用 SET2 替换 SET1 中没有包括的字符 d delete 删除 SET1 中的字符 s squeeze repeat
  • t420i升级固态硬盘提升_升级旧机子,为何首选升级固态硬盘?

    哈喽 带噶好 今天我们来聊聊升级老旧机器 为啥先升级固态硬盘 而不是升级内存 CPU 显卡之类的硬件 固态硬盘 是最近这五六年才开始普及开来的物件 所以很多人家里使用的主机 如果是五六年前买的话 机箱里大概率装的是机械硬盘 机械硬盘开机什么
  • 不懂就背--Java中的IO和NIO相关知识点总结

    一 IO和NIO的概念 NIO即New IO 这个库是在JDK1 4中才引入的 NIO和IO有相同的作用和目的 但实现方式不同 NIO主要用到的是块 所以NIO的效率要比IO高很多 在Java API中提供了两套NIO 一套是针对标准输入输
  • 如何正确的理解PSRR

    很多地方将PSR和PSRR混为一谈 PSR为电源抑制 Power Supply Rejection 即电源到输出增益的抑制 而PSRR为电源抑制比 Power Supply Rejection Ratio 即输入到输出的增益除以从电源到输出
  • 问题: Your project contains C++ files but it is not using a supported native build system.解决方法

    问题出现 将eclipse项目导入android studio后由于项目中有用到jni层中的东西 在将一系列可见错误解决后运行APP出现了标题中所述问题 1 网上最常见的解决方案 参考链接 一 在项目的gradle properties添加
  • Pytorch 入门 ----学习笔记

    本文是在参加DataWhale开源组队学习 深入浅出Pytorch 过程中 整理的学习笔记 Pytorch 基础知识 张量 张量的创建 张量 也叫做多维数组 常常我们对于一维张量也叫做标量 二位张量叫做矩阵 大部分时候 张量是三维及三维以上
  • LeetCode 1302. 层数最深叶子节点的和

    给你一棵二叉树的根节点 root 请你返回 层数最深的叶子节点的和 示例 1 输入 root 1 2 3 4 5 null 6 7 null null null null 8 输出 15 解法一 递归法 每次递归返回当前节点所处层数和以该节
  • AutoSet 根据配置表信息解析到新框架里面

    using System using System Collections Generic using System ComponentModel using System IO using System Reflection using
  • 深度学习理论篇

    目录 传统神经网络nn整体 nn总结 前向传播 像素点参数预处理 input 权重参数初始化 得分函数 W x 激活函数 f x 分类问题 反向传播 更新W 损失函数 output和target比较 卷积神经网络CNN CNN总结 卷积层
  • Mat类下的data指针的深刻理解

    摘要 本文主要介绍了Mat类中data指针访问每一个像素的方法 在访问和修改图像矩阵像素值的时候 我们经常会用到at ptr 以及迭代器MatIterator等 对于用Mat存储的图像的像素值的访问方法 文章http blog csdn n
  • html标记符之间不可以,HTML期末复习试题及参考答案

    HTML期末复习题 含答案 第1题判断正误 1 HTML标记符的属性一般不区分大小写 对 2 网站就是一个链接的页面集合 对 3 将网页上传到Internet时通常采用FTP方式 对 4 所有的HTML标记符都包括开始标记符和结束标记符 错
  • python数据评估

    未清理的数据 脏数据与杂乱数据 未清理数据分为两种 脏数据 也称为低质量数据 低质量数据存在内容问题 杂乱数据 也称为不整洁数据 不整洁数据存在结构问题 将数据可视化 例如 绘制图形 是编程评估的一部分 而非我们在这里说的目测评估 即通过目
  • NodeJs服务器启动后在浏览器访问时中文显示乱码处理方法

    创建一个叫 server js 的文件 并写入以下代码 使用 require 指令来载入 http 模块 并将实例化的 HTTP 赋值给变量 http var http require http 使用 http createServer 方
  • Dice相似系数(Dice Similarity Coefficient, DSC)

    Dice相似系数 Dice Similarity Coefficient DSC 分母可以解析为 FP TP 所有分类为阳性的样本 TP FN 真阳 假阴 所有真的是阳性的样本
  • LitJSON之JSON读取和写入

    JSON读取和写入 使用JsonReader例子 使用JsonWriter 目录 JSON读取和写入 一些开发者可能熟悉JSON数据的另一种处理方法 即通过利用类似流的方式来读取和写入数据 实现这种方法的是JsonReader类和 Json
  • jenkins+newman+postman持续集成环境搭建

    目录 一 Newman简介 二 Newman应用 三 安装newman 四 Html报告插件安装 五 安装nodejs 六 Jenkins集成步骤 一 Newman简介 Newman是一款基于Node js开发的 可以运用postman工具
  • jQuery的scroll

    scrollTop垂直滚动 scrollLeft水平滚动 scrollTop 读取或设置滚动条的y坐标 代码示例如下
  • echarts修改柱状图的宽度

    echarts修改柱状图的宽度 series bar barWidth 自适应 numberstring 柱条的宽度 不设时自适应 可以是绝对值例如 40 或者百分数例如 60 百分数基于自动计算出的每一类目的宽度 在同一坐标系上 此属性会
  • Hx711称重模块+STM32+CubeMX

    文章目录 一 模块和接线 二 CubeMX配置 1 时钟及sys 2 IO口 1 数据线DT设置为Input 2 时钟线SCK设置为Output 3 串口 4 后续配置 三 程序 1 main c 2 hx711 c 3 hx711 h 4

随机推荐

  • R(N)

    http acm hdu edu cn showproblem php pid 3835 Problem Description We know that some positive integer x can be expressed a
  • vue 动态修改margin-top_详解 vue 组件三大核心概念

    给前端大全加星标 提升前端技能 作者 前端工匠 公号 浪里行舟 本文来自作者投稿 前言 本文主要介绍属性 事件和插槽这三个vue基础概念 使用方法及其容易被忽略的一些重要细节 如果你阅读别人写的组件 可以从这三个部分展开 它们可以帮助你快速
  • 区块链学习(1) sha256算法 c语言实现

    sha256算法 网上有很多的介绍 摘抄一段如下 SHA 256 算法输入报文的最大长度不超过2 64 bit 输入按512 bit 分组进行处理 产生的输出是一个256 bit 的报文摘要 该算法处理包括以下几步 STEP1 附加填充比特
  • Python学习笔记——多线程

    mtsleepA import thread from time import sleep ctime loops 4 2 def loop nloop nsec lock print start loop nloop at ctime s
  • Node.js mm131图片批量下载爬虫1.00 iconv协助转码

    mm131图片批量下载爬虫1 00 2017年11月15日 内置http模块 var http require http 内置文件处理模块 用于创建目录和图片文件 var fs require fs 用于转码 非Utf8的网页如gb2132
  • java反射详解

    本篇文章依旧采用小例子来说明 因为我始终觉的 案例驱动是最好的 要不然只看理论的话 看了也不懂 不过建议大家在看完文章之后 在回过头去看看理论 会有更好的理解 下面开始正文 案例1 通过一个对象获得完整的包名和类名 1 2 3 4 5
  • STM32学习——FATFS文件系统

    目录 什么是文件系统 常用的文件系统 FATFS的特点 FATFS层次结构 移植步骤 相关配置宏 FATFS文件系统移植实验 FATFS程序结构图 FATFS底层设备驱动函数 宏定义 设备状态获取 设备初始化 读取扇区 扇区写入 什么是文件
  • 代码质量检测工具 QAPLug

    代码质量检测工具 情景 写完代码一定要别人review才发现bug或不好的语法或多余的变量是一件多么尴尬的事情 如果想在写代码时或者写代码后自己能发现问题 那么代码QA工具无疑是你必备的工具 工具 QAPlug就是一款实用十分方便的代码质量
  • [游戏] chrome 的小彩蛋

    在电脑上不了网时 chrome 显示无法显示此网页的同时 还会有一个小游戏可以玩 用户可以操作空格键来控制一只小恐龙让它跳过灌木丛
  • Python 实现逐步回归

    常用评价指标简介 当前统计学以计算机科学作为支撑 机器于人工的优势是计算速度 但机器无法自行判断运算何时退出 因此需要定量指标作为运算退出的标志 对于预测类的统计模型来说 常见的指标有赤池信息准则 AIC 贝叶斯信息准则 BIC R方 RO
  • 冒泡排序、选择排序、插入排序 原理及Java代码实现

    1 冒泡排序 冒泡排序 Bubble Sort 是一种计算机科学领域的较简单的排序算法 冒泡排序算法的原理如下 1 比较相邻的元素 如果第一个比第二个大 就交换他们两个 2 对每一对相邻元素做同样的工作 从开始第一对到结尾的最后一对 在这一
  • Cpp学习——动态内存管理

    目录 一 new 1 malloc realloc calloc的使用不便之处 2 new的好处 3 opreator new 二 delete 1 为什么要有delete 2 为什么要匹配使用 一 new 1 malloc realloc
  • 【论文精读】NeRF详解

    最近阅读了开启三维重建新纪元的经典文章 NeRF Representing Scenes as Neural Radiance Fields for View Synthesis 接下来会 更新NeRF系列的论文精读 代码详解 力求做到全网
  • SpringBoot2.x 集成 Swagger3(springdoc-openapi)

    Swagger是一款RESTFUL接口的文档在线自动生成加功能测试的软件 提供描述 生产 消费和可视化RESTful Web Service Swagger也是一个api文档维护组织 后来成为了OpenAPI 一个业界的api文档标准 标准
  • 赛联区块链培训课程介绍

    基础版
  • 谷歌V8引擎运行机制概览

    最近学习了极客时间上李兵大佬的谷歌V8引擎课程 总结了一下 在公司内部小组分享了一波 在此也分享一下 原理图直接用的专栏的图 由于时间有限 总结略显粗糙 注 解释执行 编译执行各有优缺点 解释执行 不需要做过多的编译 所以启动快 执行时不时
  • 如何让footer始终在页面底部固定

    footer height 50px position fixed bottom 0px left 0px right 0px 参考 https www cnblogs com lk kk p 4654832 html
  • 已 树莓派4b ros 系统 网盘_树莓派4B初次使用--系统安装

    准备 硬件 树莓派本体 读卡器 TF卡 电源线 HDMI连接线 可选 显示器 可选 软件 SDFormatter格式化工具 Win32DiskImager烧录工具 Finalshell Cellular Z 技术规格 首先 来看看树莓派4的
  • 四川对口高职计算机录取分数,四川录取分数线 四川对口高职录取分数线出炉!!!...

    四川对口高职录取分数线出炉 你在线吗 快来看看 成都纺织学院5141 四川中医学院5146 成都航空空职业技术学院5151 四川交通职业技术学院5152 达州职业技术学院5153 四川工程技术学院5155 绵阳职业技术学院5157 四川建筑
  • 【Unity-学习-014】EasyAR4.0稀疏空间地图 扫描场景功能

    本帖主要描写扫描场景的功能实现 以及一些需要注意的问题 跟上层贴有所关联 想要更多了解请移步链接 场景中有几个重要的预设需要添加 目录如下 其中 SparseSpatialMap 用于扫描空间成成点云信息 点云可以将空间数据以点的信息保存下