ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HarmonyOS AR Engine常见问题排查实操——从空平面保护到会话重建

HarmonyOS AR Engine常见问题排查实操——从空平面保护到会话重建 这次做了一个可以直接复现问题的 AR 稳定性诊断台。镜头正常扫描木地板时页面显示TRACKING、平面数量和边界顶点遮住摄像头后状态变成PAUSED原因是INSUFFICIENT_FEATURES快速移动手机时又真实触发了EXCESSIVE_MOTION。镜头重新看到原来的环境后页面会记录暂停了多久以及恢复前后的位移和转角差。如果恢复结果不可信还可以直接销毁旧会话并创建一个全新的 WORLD 会话。正常扫描时真机取得了 1 个跟踪平面和 11 个边界顶点。识别到平面之前平面列表连续为空 4 次页面只增加“空保护”计数没有读取不存在的对象完整遮挡镜头后真机暂停了 10162 ms。重新对准木地板时会话恢复为TRACKING恢复前后的位移差为 0.692 m转角差为 50.1°本次结果验证项目真机结果设备HUAWEI Mate 60 Pro系统HarmonyOS 7.0SDKAPI 26CAMERA 权限已授予SLAM 能力支持正常平面1 个初次有效边界11 个顶点空平面保护4 次无异常镜头遮挡原因INSUFFICIENT_FEATURES遮挡恢复10162 ms / 0.692 m / 50.1°快速移动原因EXCESSIVE_MOTION快速移动恢复1003 ms / 0.188 m / 34.3°一键重建旧上下文销毁成功新会话 22 ms 初始化重建后结果重新进入TRACKING取得 1 个平面最终释放暂停、恢复、销毁均成功实验准备本次需要一台支持ARENGINE_FEATURE_TYPE_SLAM的 HarmonyOS 真机CAMERA 权限一块纹理明显、光线正常的地面一面纯色墙或可以完整遮挡摄像头的手掌手机周围留出安全活动空间用于短距离快速移动测试。磁铁、长时间高负载和故意让手机发烫都不在实验范围内。官方提到这些因素可能造成漂移或位姿跳变但没有必要为了文章主动制造不安全条件。诊断台要看哪些数据这次没有把所有 AR 数据都铺在页面上只保留能判断问题的五段链路先读相机的跟踪状态暂停时再看真实原因平面列表为空就安全跳过恢复后计算前后 Pose 差值需要时销毁旧会话并重新初始化。页面顶部显示相机状态、平面数、Polygon 顶点、空列表保护次数和恢复次数。底部保存最近一次暂停时长、恢复位移和恢复转角。进入前检查 CAMERA 和 SLAM诊断页使用 WORLD 会话。入口先检查相机权限和 SLAM 能力任一条件不满足都不创建ARViewContextprivate startARStabilityDiagnostic(): void { if (!this.cameraGranted) { this.latestMessage 未获得相机权限不启动诊断; return; } const supported: boolean arViewController.isARTypeSupported( arEngine.ARFeatureType .ARENGINE_FEATURE_TYPE_SLAM); if (!supported) { this.latestMessage 当前设备不支持 SLAM; return; } this.showARStabilityDiagnostic true; }本次真机门禁通过STABILITY_GUARD cameratrue slamtrue创建只包含必要能力的 WORLD 会话平面诊断需要运动跟踪和水平/垂直平面。语义、深度和 Mesh 在本次实验中关闭避免其他模型干扰结果const scene: Scene await Scene.load(); const context new arViewController.ARViewContext(); context.scene scene; context.callback this.callback; context.config { type: arEngine.ARType.WORLD, planeFindingMode: arEngine.ARPlaneFindingMode .HORIZONTAL_AND_VERTICAL, powerMode: arEngine.ARPowerMode.NORMAL, semanticMode: arEngine.ARSemanticMode.NONE, poseMode: arEngine.ARPoseMode.GRAVITY, depthMode: arEngine.ARDepthMode.DISABLED, meshMode: arEngine.ARMeshMode.DISABLED, focusMode: arEngine.ARFocusMode.AUTO }; await context.init(); this.arContext context;首轮初始化用了 31 msSTABILITY_SESSION_INIT triggerINIT costMs31 restart0先看 state再解释 stateReason每一帧从相机对象读取state和stateReasonframe session.getFrame(); const camera: arEngine.ARCamera frame.getCamera(); const state camera.state; const reason camera.stateReason; if (state arEngine.ARTrackingState.TRACKING) { // 读取真实 Pose 和平面 } else if (state arEngine.ARTrackingState.PAUSED) { // 显示暂停原因等待恢复 }原因只负责解释当前状态private reasonText( reason: arEngine.ARTrackingStateReason ): string { if (reason arEngine.ARTrackingStateReason .EXCESSIVE_MOTION) { return 设备移动过快; } if (reason arEngine.ARTrackingStateReason .INSUFFICIENT_FEATURES) { return 纹理、光线或视觉特征不足; } return 系统正在建立或恢复跟踪; }真机中还出现了一个值得保留的细节状态短暂保持TRACKING时stateReason已经先变成 1约 0.5 秒后状态才正式进入PAUSED。因此本案例只在state PAUSED时记为跟踪中断。不能看到reason1就直接把页面改成暂停否则会把过渡帧当成失败。平面为空时不要继续读取会话刚进入TRACKING时平面列表通常还是空的。本次首轮连续出现 4 次空列表STABILITY_PLANE_GUARD_EMPTY sample19 guardCount1 actionskip_index_and_polygon STABILITY_PLANE_GUARD_EMPTY sample24 guardCount2 actionskip_index_and_polygon处理方式是先检查数组长度。为空就更新页面并返回不读取trackables[0]也不调用 Polygon 接口trackables session.getAllTrackables( arEngine.ARTrackableType.PLANE); if (trackables.length 0) { this.trackedPlanes 0; this.polygonVertexCount 0; this.emptyPlaneGuardCount 1; return; }官方 FAQ 中的plane is nullptr / 401是 C API 场景。ArkTS 侧不需要强行制造相同错误码但根因相同还没有取得有效平面就继续读取具体对象。把 Polygon 的 ArrayBuffer 转成坐标只有类型为PLANE、状态为TRACKING的对象才读取边界const plane: arEngine.ARPlane trackable as arEngine.ARPlane; const polygonBuffer: ArrayBuffer plane.getPolygonXZ(); const values: Float32Array new Float32Array(polygonBuffer);边界按X, Z, X, Z...排列所以两个 Float32 数值组成一个顶点。正式计数前继续检查长度和有限值private isValidPolygon( values: Float32Array ): boolean { if (values.length 6 || values.length % 2 ! 0) { return false; } for (let index: number 0; index values.length; index 1) { if (!Number.isFinite(values[index])) { return false; } } return true; } const vertexCount: number Math.floor(values.length / 2);真机首次取得的有效结果是STABILITY_PLANE_VALID sample39 total1 tracking1 vertices11 invalid0记录暂停前的最后一组 Pose从TRACKING进入非跟踪状态时保存当前时间和最后一组稳定 Poseif (previousState arEngine.ARTrackingState.TRACKING state ! arEngine.ARTrackingState.TRACKING) { this.pauseStartedAt Date.now(); this.pauseReason reason; this.pauseReferencePose this.latestStablePose; }这里同时保存了“最初导致暂停的原因”。实测遮挡期间原因先是INSUFFICIENT_FEATURES随后又变成NONE。如果每帧都覆盖原因恢复时就只剩下NONE真正的触发原因反而丢了。恢复时计算位移和转角差状态重新回到TRACKING后取恢复后的第一组 Pose与暂停前的 Pose 比较const dx current.x - reference.x; const dy current.y - reference.y; const dz current.z - reference.z; const distance Math.sqrt( dx * dx dy * dy dz * dz); const dot Math.abs( currentRotation.x * referenceRotation.x currentRotation.y * referenceRotation.y currentRotation.z * referenceRotation.z currentRotation.w * referenceRotation.w); const normalizedDot Math.min(1, Math.max(0, dot)); const rotation 2 * Math.acos(normalizedDot) * 180 / Math.PI;第一次遮挡实验的完整状态变化如下STABILITY_PAUSE_START sample97 state1 reason2 reasonTextINSUFFICIENT_FEATURES STABILITY_TRACKING sample185 state0 stateTextTRACKING reason0 STABILITY_RECOVERY count1 durationMs10162 reason2 hasReferencetrue distance0.692 rotation50.1这组数字不是手机真实移动距离的计量结果而是 AR 会话在暂停前后返回的坐标差。它的用途是判断恢复后坐标是否发生了明显跳变。快速移动测试保持镜头对准木地板短距离快速左右移动手机随后停住等待恢复。本次真实触发了EXCESSIVE_MOTIONSTABILITY_PAUSE_START sample173 state1 reason1 reasonTextEXCESSIVE_MOTION STABILITY_RECOVERY count1 durationMs1003 reason1 distance0.188 rotation34.3 STABILITY_RECOVERY count2 durationMs1704 reason1 distance0.118 rotation20.3 STABILITY_RECOVERY count3 durationMs8279 reason1 distance0.117 rotation18.2本轮没有把快速移动本身的普通 Pose 变化写成故障。只有状态真正进入PAUSED并且随后再次回到TRACKING才增加一次恢复计数。一键销毁并重建会话如果遮挡恢复后叠加物明显错位或位姿差超出业务可以接受的范围可以重新初始化会话。页面的“重建会话”先销毁旧上下文再清空旧 Pose、平面和暂停状态最后创建新会话private async restartARView(): Promisevoid { if (!this.arContext || this.isRestarting) { return; } this.isRestarting true; this.restartCount 1; const oldContext this.arContext; this.arContext undefined; await oldContext.destroy(); this.resetRuntimeState(); await this.initARView(RESTART); this.isRestarting false; }真机执行结果STABILITY_SESSION_RESTART_BEGIN restart1 sample166 recoveries0 STABILITY_CONTEXT_DESTROY actionRESTART successtrue STABILITY_SESSION_INIT triggerRESTART costMs22 restart1 STABILITY_SESSION_RESTART_END restart1 successtrue重建后新会话重新进入TRACKING。识别平面之前空列表保护再次生效随后取得 1 个平面和 8 个边界顶点实操步骤1. 建立正常基线进入诊断页后把镜头缓慢扫向木地板。页面先显示空保护计数随后出现“平面链路正常”。本次基线为 1 个平面、11 个顶点。2. 遮挡镜头用手完整遮住后置摄像头约 3 秒然后移开手并重新对准木地板。真机先返回INSUFFICIENT_FEATURES恢复时记录 10162 ms、0.692 m 和 50.1°。3. 快速移动在安全范围内短距离快速左右移动手机 35 次随后停住。真机返回EXCESSIVE_MOTION并完成多次暂停—恢复。4. 重建会话点击“重建会话”。旧相机上下文销毁后页面回到建立环境坐标状态继续扫描地面新会话再次进入TRACKING并得到平面。5. 验证生命周期先点击“暂停”等待数秒再点击“恢复”最后退出页面。本轮暂停和恢复时采样数都为 100证明暂停期间没有继续采样STABILITY_SESSION_PAUSE sample100 state0 planes1 STABILITY_SESSION_RESUME sample100 state0 planes1 STABILITY_CONTEXT_DESTROY actionEXIT successtrue STABILITY_SESSION_DESTROY samples100 recoveries1 emptyGuards9 maxPlanes1 restarts1实验中遇到的情况stateReason会先于状态变化快速移动时个别帧仍是TRACKING但原因已经变成EXCESSIVE_MOTION。约半秒后状态才进入PAUSED。最终逻辑以state判断是否暂停以stateReason解释原因。暂停原因会从具体原因变回 NONE遮挡后先返回INSUFFICIENT_FEATURES恢复过程中又返回NONE。页面在第一次离开TRACKING时保存触发原因避免后续帧覆盖。入场阶段没有平面是正常状态新会话进入TRACKING不代表平面已经生成。空数组需要跳过而不是直接读取第一个元素。首轮和重建后的真机日志都验证了这条保护。重建必须先销毁旧上下文只清空页面数值并不能重置 AR 坐标系。真正的重建需要等待旧ARViewContext.destroy()完成再创建新的 Scene、Context 和会话配置。资源释放每轮帧读取结束后释放 Pose、全部 Trackable 和 Frameif (pose) { await pose.release(); } for (const trackable of trackables) { await trackable.release(); } if (frame) { await frame.release(); }页面退出前销毁ARViewContext防止相机继续占用。暂停、恢复、重建和退出都带重复调用保护。最终结果这次实操把 AR Engine FAQ 中几类容易混在一起的问题拆成了可以观察的数据没有平面时空数组保护可以避免继续读取无效对象纯色、遮挡或视觉信息不足时真机返回INSUFFICIENT_FEATURES快速移动时真机返回EXCESSIVE_MOTION遮挡恢复后前后 Pose 差可以直接显示坐标跳变量对恢复结果不放心时可以销毁旧上下文并创建新会话暂停、恢复和退出后采样与相机资源都按预期收口。磁场干扰、设备发热和重复纹理造成的漂移没有在本次实验中主动制造因此不写成已经复现。诊断台验证的是状态读取、防空、恢复差值和会话重建这条完整链路。参考资料华为官方AR Engine常见问题华为官方摄像头遮挡后输出位姿跳变华为官方数据类型转换说明华为官方AR Engine开发指南
返回列表