资讯动态

AR室内导航实战:基于EasyAR稀疏空间地图的定位与路径规划

发布时间:2026/9/16 22:07:30 来源:尧图企业网站定制
1. 为什么选择稀疏空间地图而不是其他AR方案先说结论做AR导航选对空间定位方案项目就成功了一半。我是从2019年开始折腾AR相关开发的Vuforia、ARKit、ARCore、EasyAR全都过了一遍。前几年做AR试戴、AR卡片展示这类应用用图像跟踪就够了没什么技术门槛。但一旦涉及导航这两个字事情就完全不一样了——你要解决的不是识别一张图然后叠个模型而是让虚拟箭头稳稳地钉在真实世界的某个坐标上并且随着人走动实时更新方向。导航的本质是空间定位和姿态估计图像跟踪在这个场景下几乎派不上用场。你总不能在路上贴满识别图让用户走两步就扫一下二维码吧。这个时候就需要真正的空间感知能力也就是SLAM类方案。EasyAR4.0的稀疏空间地图Sparse Spatial Map就是干这个的。它通过摄像头实时采集环境特征点构建一个由三维特征点组成的稀疏点云地图再在这个地图上建立局部坐标系。AR内容锚定在这个坐标系里设备移动时通过持续帧匹配计算出当前相机在地图中的位姿从而实现稳定跟踪。这里有个关键点值得先展开稀疏空间地图和稠密空间地图的区别。稀疏空间地图只保存环境中的显著特征点角点、边缘交点、纹理丰富区域数据量小构建快适合大范围场景。稠密空间地图会重建更完整的表面几何精度高但计算量大实时性差前几年只能在高端手机上勉强跑。AR导航不需要渲染真实环境的完整几何结构只需要知道我在哪、往哪走、方向朝向哪所以稀疏地图是性价比非常高的选择。EasyAR的稀疏空间地图在这条路上做得比较成熟最关键的是它对国产安卓机型的适配比ARKit好太多——这个后面实测部分会专门讲。我这次做的项目是一个室内AR导航Demo场景是公司的一层办公楼目标是让用户打开App相机扫一下周围环境完成定位然后屏幕上的AR箭头指引用户走到指定会议室。技术栈就是Unity3D加EasyAR4.0全程C#开发代码量不大但坑确实不少。下面从环境配置开始一步步把这套东西讲透。你可以直接照着我这份方案搭一个自己的AR导航应用出来中间遇到的所有坑我都会标出来。2. 环境准备与SDK接入最容易翻车的三个环节2.1 版本匹配问题EasyAR4.0对Unity版本是有要求的这一点很多新手没注意就直接踩坑。我当时用的是Unity 2019.4 LTS配合EasyAR 4.0的Unity插件包比较稳。如果你用Unity 2021以上版本需要确认EasyAR版本是否兼容我实测下来4.0的早期版本在Unity 2020.3上就有诡异的Shader报错后来换回2019.4一切正常。下载SDK之前建议先去EasyAR官网注册开发者账号申请一个免费试用Key。这个Key是有有效期的而且绑定包名Bundle Identifier所以你在Unity中设置包名的时候一定要和申请Key时填写的完全一致。我第一次测试时就是包名不一致结果真机上一直黑屏控制台报的错又不直观排查了很久才发现是这个原因。这里有一个非常容易被忽略的环节EasyAR4.0的稀疏空间地图功能是分模块授权的。你拿到的基础SDK可能只包含图像跟踪还需要确认自己的授权是否开通了Spatial Map模块。我当时在官网申请试用时没有仔细看授权范围结果本地构建好工程一运行就提示License has no permission白白折腾了两天。2.2 Gradle和Android构建配置EasyAR的Unity插件底层依赖Android原生库构建APK时对Gradle配置比较敏感。我的建议是直接把项目打包成Android工程然后用Android Studio打开来定制构建不要直接在Unity里一键Build。具体原因后面会讲。Unity导出选项里有几个必选项Scripting Backend选IL2CPP不要选Mono。Mono虽然是纯托管代码运行更快但EasyAR4.0的部分原生模块在Mono模式下会有兼容隐患。Target Architecture勾选ARM64现在的新手机基本都是64位只勾ARMv7的话部分机型直接跑不了。Minimum API Level建议设置到Android 7.0以上EasyAR在低版本系统上的摄像头权限处理不够友好。如果Gradle构建失败最常见的错误是依赖冲突。EasyAR的aar包里自带了部分OpenCV和OpenGL相关的类库如果你的项目里还有其他SDK也带了同一个依赖就会出现Duplicate class的报错。解决办法很简单在Unity导出的工程里找到build.gradle把重复的依赖用exclude去掉。2.3 摄像头权限与运行时申请EasyAR4.0需要相机权限这个权限在Android 6.0以上是动态申请的。我在MainActivity里加了权限申请逻辑但是一开始没有做权限回调的联动处理导致用户点击允许后EasyAR的SenseManager并没有感知到权限变化画面一直是黑屏。解决方案是在权限回调里重新调用SenseManager.onResume()。Unity端只需要在Player Settings里把Camera权限勾上但如果你不想用EasyAR自带的权限弹窗而是自己定制权限流程就要关掉EasyAR的Auto Request Permission选项在代码里自己管理。注意EasyAR4.0的SenseManager在无相机权限时不会给你明确的错误提示很多情况下表现为黑屏或者画面凝固在首帧。排查时优先确认权限状态。3. 稀疏空间地图的构建流程与坐标体系3.1 创建地图的根本逻辑要理解EasyAR稀疏空间地图的用法先要搞清楚一个概念稀疏空间地图不是预先加载好的而是即时创建、设备本地存储的。也就是说你要先用一台设备在目标场景里走一圈让SDK采集特征点并构建地图然后把地图文件JSON格式保存下来。运行时用户设备加载这个地图文件通过摄像头帧与地图中的特征点做匹配来定位自身。这套逻辑和我们在高德地图里提前建好POI然后查表的思路很像。ARKit的World Map本质上也是这个思路只是EasyAR的地图格式是私有的但提供了导入导出的API。构建地图有三种方式用EasyAR官方提供的开发者工具Surveying工具现场采集自动生成地图文件。在Unity编辑器里直接用Scene中的数据构建——这个方法适用于纯虚拟场景实景不行。在手机上跑一个采集App自己编写采集逻辑边移动边把当前位姿和特征点传给SDK最后调用Localizer.startLocalization()保存地图。这三种方式里第一种最省事但官方工具在部分安卓设备上有兼容问题第三种最灵活可以完全自定义采集流程。我这个项目用的是第三种方案在Unity里直接写了一个采集模式脚本运行时按下一个UI按钮就开始采集走了两圈后再按停止地图文件就被保存到手机的持久化目录。核心API调用流程如下// 初始化地图构建器 var builder SparseSpatialMapBuilder.create(); builder.setMapFormat(SparseSpatialMapBuilder.MapFormat.EasyAR); builder.setAnchorScale(1.0f); // 设置回调地图构建完成后会触发 builder.setOnMapUpdateCallback((builder, isSuccessful, mapBuilder, error) { if (isSuccessful) { // 获取地图数据并保存到本地 byte[] mapData mapBuilder.getRawData(); File.WriteAllBytes(Application.persistentDataPath /office_map.json, mapData); } }); // 启动SenseManager并开始构建 senseManager.addSparseSpatialMapBuilder(builder); senseManager.start();采集时有一个非常关键的细节不要走太快也不要原地不动。走太快会导致特征点匹配失败地图出现断层原地不动则会导致采集到的特征点稀疏且重复后续定位时匹配率很低。我的经验是保持正常的步行速度身体微微左右晃动让摄像头从不同角度拍到同一个区域这样特征点的三维位置会估算得更准。3.2 坐标系的换算FeatureMap、Camera和Anchor的关系很多新手在接到相关项目时第一反应是稀疏空间地图直接给我一个GPS坐标然后我基于这个坐标做路径规划——这个理解是错误的。EasyAR的稀疏空间地图有一个自己的局部坐标系原点通常是地图构建起点设备第一次开始采集时相机所在位置。当应用运行时Frame中会提供一个相机位姿Camera Pose这是相对于地图坐标系的。AR内容如果有一个锚点Anchor锚点的位姿也是相对地图坐标系的。我在项目里做导航时就把所有路径点、箭头、指标牌都挂在地图坐标系下。这样做的好处是整个虚拟场景稳定不变设备只是一块在固定坐标系中移动的透视玻璃。不过这里有一个重要前提构建地图时的设备朝向和运行时设备的初始朝向并不总是对齐的。所以你在加载地图后如果发现AR箭头飘在错误的方向不要急着怀疑代码错了先检查一下初始对齐逻辑。EasyAR提供了Localizer的setAligning相关API可以指定一张参考图像来校正设备朝向我在办公楼的走廊里用了墙上的消防示意图作为参照物完成对齐效果不错。3.3 地图的持久化与多设备共享地图构建完后它是保存在构建设备本地路径下的。如果你想把同一个地图分发给多个用户需要把这个JSON文件上传到服务器其他用户下载后放到本地路径再由应用加载。地图文件中包含了特征点的世界坐标、描述子等信息。我看了下生成的文件大小一个大约200平方米的办公楼走廊区域JSON文件在2MB到5MB之间传服务器完全没问题。加载地图的代码如下byte[] mapData File.ReadAllBytes(Application.persistentDataPath /office_map.json); var localizer SparseSpatialMapManager.getLocalizer(); localizer.setMapData(mapData); localizer.startLocalization(new Localizer.LocalizationCallback() { Override public void onSuccess() { // 定位成功 } Override public void onFail(String error) { // 定位失败可以提示用户换个角度扫描 } });定位过程不是瞬时的。在实际场景中用户打开App后需要扫一圈四周环境SDK在积累一定帧数的特征点匹配后才认为定位成功。我实测下来的时间是2到5秒视光线和纹理丰富程度而定。提示如果用户在一个纹理非常少的大白墙房间里打开应用定位大概率会失败或者漂移。做产品的时候一定要考虑这个边缘情况给用户一个请前往有纹理区域的引导提示。4. AR导航功能的完整代码实现4.1 路径规划最短路径还是视线直引拿到空间地图坐标系后下一步就是规划导航路径。对于室内AR导航路径规划算法可以非常轻量因为楼层内部结构相对简单走廊交叉口也不多。我在项目里用了最经典的A*寻路算法把办公楼走廊转成了一张网格图网格分辨率是0.5米一个节点。为什么选0.5米而不是更细因为室内通道宽度一般就1.5到2米0.5米的网格足以表示通行路径网格再细的话节点数暴增在手机上规划路径反而会有卡顿。A*的C#实现在网上有很多成熟方案我这里重点讲一下怎么把路径坐标对齐到EasyAR的地图坐标系。网格图里的节点坐标最初是在CAD图纸坐标系下定义的我需要做一个坐标变换把它们映射到EasyAR的地图坐标系中。最简单的方式是选三个已知点做相似变换。比如我选了大厅前台的花盆、走廊尽头灭火器箱角、会议室门口地毯边缘这三个位置在CAD图纸里查到它们的坐标同时在地图构建完成后用EasyAR在对应位置放置标记物记录坐标然后通过最小二乘法求出旋转、平移和缩放参数。这个方案不要求精确知道比例尺三个点足够估算出一套相似变换参数了。// 一个简化版的相似变换求解 // 输入srcPoints - EasyAR坐标系的三个点 // dstPoints - CAD图纸坐标系的三个点 // 输出一个4x4矩阵把CAD坐标变换到EasyAR坐标 public static Matrix4x4 CalculateSimilarityTransform(Vector3[] src, Vector3[] dst) { // 计算质心 Vector3 srcCentroid (src[0] src[1] src[2]) / 3f; Vector3 dstCentroid (dst[0] dst[1] dst[2]) / 3f; // 去质心化 Vector3[] srcNorm new Vector3[3]; Vector3[] dstNorm new Vector3[3]; for (int i 0; i 3; i) { srcNorm[i] src[i] - srcCentroid; dstNorm[i] dst[i] - dstCentroid; } // 用两个向量估计旋转和缩放 Vector3 srcDir1 srcNorm[1] - srcNorm[0]; Vector3 dstDir1 dstNorm[1] - dstNorm[0]; float scale dstDir1.magnitude / srcDir1.magnitude; Quaternion rotation Quaternion.FromToRotation(srcDir1, dstDir1); // 组合成矩阵注意平移部分需要把质心换算回去 Matrix4x4 result Matrix4x4.TRS(dstCentroid - rotation * srcCentroid * scale, rotation, new Vector3(scale, scale, scale)); return result; }4.2 导航箭头的渲染与动画NavPath规划好之后下一个核心工作是把它可视化。这里有一个比较重要的设计决策在地图上直接显示整条路径线还是只显示一个箭头提示方向我在第一版里把整条路径线都画出来了用LineRenderer从起点连到终点效果确实很直观但问题也很明显用户在行进过程中看到地面上一长条虚拟线视线容易被遮挡而且路径线在转弯处会显得很突兀用户体验不太好。第二版我改成了分段箭头方案——顺着路径每两米放一个箭头模型箭头指向前进方向用户走到一个箭头附近后它自动消失以距离阈值判断这样视觉上更清爽。箭头的朝向计算很简单取当前路径段的方向向量转成四元数赋值给箭头物体的rotation。但要注意的是这里的旋转必须是在EasyAR地图坐标系下的绝对旋转而不是相对于相机的旋转。如果你直接写arrow.transform.right nextPoint - currentPoint在旋转设备时会出问题因为路径点是世界坐标而LineRenderer或箭头模型是在场景中固定位置的不该随相机旋转。正确姿势是先算出路径切向量Vector3 direction nextPoint - currentPoint; arrow.transform.rotation Quaternion.LookRotation(direction, Vector3.up);为了让箭头更生动我加了一个上下浮动动画用正弦函数控制Y轴偏移float baseY arrow.transform.position.y; arrow.transform.position new Vector3( arrow.transform.position.x, baseY Mathf.Sin(Time.time * 2f) * 0.1f, arrow.transform.position.z );4.3 到达判定与下一段路径切换导航过程中每帧要判断用户当前位置距离当前目标点是否足够近。距离阈值我设了1.2米为什么会选这个值太大会导致用户还没走到拐角就提前切换方向太小又容易因为定位抖动导致判定不到。1.2米在室内步速下基本能满足到了十字路口正好转向的体验。判断逻辑写在Update里float distanceToTarget Vector3.Distance(cameraPose.position, currentTarget); if (distanceToTarget 1.2f) { currentTargetIndex; if (currentTargetIndex path.Count) { // 到达终点 OnArrived(); return; } // 更新当前目标并切换箭头 currentTarget path[currentTargetIndex]; UpdateArrowPositionAndRotation(); }这里有个非常值得注意的点cameraPose.position是相机在世界坐标系中的位置但EasyAR在定位成功之前这个位置是不准的。所以在导航逻辑开始前一定要先确保定位成功。我在UI层做了一个定位中的遮罩等Localizer收到定位成功回调之后才隐藏避免用户在未定位时看到乱飘的箭头。4.4 完整工程脚本结构我习惯把项目脚本按功能拆成几个独立的模块而不是堆在一个MonoBehaviour里脚本名职责EasyARInitializer.csSDK初始化、License设置、SenseManager生命周期管理MapBuilder.cs采集模式下的地图构建与保存逻辑MapLocalizer.cs加载本地地图、启动定位、提供相机位姿接口PathPlanner.csA*寻路、网格图加载、坐标变换NavigationVisualizer.cs箭头渲染、动画、到达切换NavigationUIController.cs界面交互、定位状态提示、终点选择面板如果你只是做一个简单Demo可以把这些合并成两个脚本但我建议还是保持这种拆分因为后面调试时会非常省心。尤其是MapLocalizer和NavigationVisualizer之间的解耦可以让你单独测试定位是否稳定再测试路径渲染是否正常不用每次都在完整流程里找问题。5. 真机调试与定位优化的血泪经验5.1 不同手机的定位精度差异这个项目我在公司配的测试机某国产中端机上定位很流畅但在另外一台旗舰机上反而出现了明显的漂移现象。排查了很久才发现问题出在IMU融合策略上。EasyAR的稀疏空间地图定位依赖视觉特征点匹配同时会融合陀螺仪和加速度计数据进行位姿预测。中端手机摄像头帧率低20帧左右视觉匹配间隔大SDK会更依赖IMU插值旗舰机帧率高30到60帧如果IMU数据质量不稳定反而会在视觉匹配的间隙产生跳动。解决方式不是去改EasyAR的参数这些参数不公开而是从应用层做平滑处理。我在相机Pose和导航箭头之间加了一个指数滑动平均过滤器对位姿的平移和旋转分别做平滑public class PoseSmoother { private Vector3 smoothPosition; private Quaternion smoothRotation; private float smoothingFactor 0.3f; public Pose Process(Pose rawPose) { if (smoothPosition Vector3.zero) { smoothPosition rawPose.position; smoothRotation rawPose.rotation; return rawPose; } smoothPosition Vector3.Lerp(smoothPosition, rawPose.position, smoothingFactor); smoothRotation Quaternion.Slerp(smoothRotation, rawPose.rotation, smoothingFactor); return new Pose(smoothPosition, smoothRotation); } }平滑系数不能设太小否则箭头会延迟明显用户转向时指示方向会滞后反而影响体验。0.3这个值在步行场景下刚刚好既消除了高频抖动又不会造成明显的拖影感。5.2 光线变化和反光地面的影响室内导航最常见的失败场景是走过一段玻璃幕墙走廊、或者在抛光大理石地面上特征点数量骤减定位精度断崖式下跌。EasyAR的稀疏空间地图在纹理丰富、无强反光的场景下表现很好但一旦遇到大面积玻璃或者地面反射特征点的三维重建会产生大量噪声点。我在地图构建时专门走避开玻璃走廊的路线构建了另一份地图虽然没有覆盖整层楼但导航到重点区域完全够用。如果应用场景实在避不开玻璃可以考虑在构建地图时绕开此类区域或者将地图构建的采集路径设计成尽可能包含更多稳定特征点的折线走向——比如把大厅中央喷泉、天花板吊灯这类固定目标多个角度都拍到。5.3 后台恢复和生命周期管理真机测试时还有一个非常隐蔽的Bug用户按下Home键让App进入后台再切回来时EasyAR的SenseManager需要重新初始化。如果你没有在OnApplicationPause里做对应处理大概率会看到画面花屏或者直接崩溃。我加上了一层生命周期管理void OnApplicationPause(bool pause) { if (pause) { // 暂停时停止定位和跟踪 senseManager.stop(); } else { // 恢复时重新启动 senseManager.start(); // 需要重新定位因为地图特征点可能已经丢失 localizationStarted false; } }注意恢复后不能直接继续导航因为设备可能已经移动了很远或者相机朝向完全变了。我的做法是恢复后重新执行一遍定位流程定位成功前提示用户请环顾四周。5.4 地图文件体积与加载速度如果你想把导航场景做大比如整个商场五层楼地图文件会有多个。EasyAR的Localizer目前是单地图加载模式切换地图需要先stop再start。我在项目里设计了按楼层加载地图的逻辑切换楼层时先跳转到对应的地图文件然后再次启动定位整个过程大概1到2秒用户感知上是可以接受的。6. 应用场景扩展与后续优化方向这个项目做完之后我最大的感受是EasyAR的稀疏空间地图把AR导航的门槛降低了很多但真正落地到产品里还有很多工程细节需要自己打磨。目前我所在的团队正在把这个导航能力往三个方向扩展室内导览App把办公楼导航改成博物馆、商场场景地图构建范围会覆盖更大区域需要考虑地图分块管理和热切换。与蓝牙信标融合定位视觉定位在开阔空间效果不错但转弯处容易出现短暂丢失如果配合蓝牙信标的粗略位置做先验可以明显提升转弯场景的定位成功率。多人协作AR空间EasyAR其实支持多设备共享同一个稀疏空间地图这样多个用户可以同时在同一空间内看到同一个虚拟物体。我们在办公室试过两台手机同时加载同一份地图虚拟指示牌的位置是一致的这个能力做团队协作类应用会很实用。回到技术本身我也想给正准备入坑AR导航的朋友几个建议不要一上来就追求大范围地图先在一个小场景里把构建-定位-导航闭环跑通再逐步扩大范围。做好设备兼容性测试尽量多借几台不同价位、不同SOC的手机你会发现同一套算法在不同设备上的表现差异非常明显。重视用户引导AR应用最大的门槛是用户不知道怎么用。你要在界面上明确提示请环顾四周请走向有标志的位置这种细节直接决定功能能不能被真正用起来。我在实际运行这个项目时还发现一个特别有意思的规律定期更新的地图比一次性构建的地图稳定得多。因为室内的光照、悬挂物、桌椅位置都可能发生变化导致特征点匹配率下降。第二周重新构建地图后导航精度明显恢复了。所以如果要长期使用记得加上地图重建的机制。最后想说的是AR导航这个方向看起来神奇但底层逻辑依然是工程问题。只要你肯花时间把定位精度、路径规划和用户交互这几个环节打磨好做出来的东西是能真正解决用户问题的。我写的这份代码框架也只是一种解法很多环节都有更优的替代方案比如用ViTPose类视觉模型直接识别关键地标来辅助定位或者引入语义SLAM让导航更智能。如果你也正在做类似项目遇到什么问题可以在评论区留言我看到会回复。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价