ArkUI+ForEach:3DGS采集方位缺口角度分桶与补拍清单【鸿蒙心迹】
如果把一圈采集照片按文件数量做进度条,数字很容易漂亮,覆盖却未必可靠。以环绕展品的 3DGS 采集为例:操作者在同一个方位连拍三张,文件数增加了三,但背面可能仍然一张没有。这不是重建算法能替采集页面回答的问题,应该在照片入队之前把“拍到了多少张”和“覆盖了多少方向”分开。这里讨论一个应用侧预检页面,并不假设 Spatial Recon Kit 提供了某个“覆盖率计算 API”。我用固定的模拟方位样本演示如何形成缺口清单。它可以给人工补拍提供可解释的提示,却不能证明最终模型已经完整,更不能替代相机位姿、质量评分和重建验收。
## 一、采集数量先不要等同于覆盖率Demo 叫 SweepLedger,页面为 entry/src/main/ets/pages/CaptureAuditPage.ets。演示会话 GS-1815 约定环绕目标一圈分成十二个方位桶,每桶宽 30°。示例方位依次为 0、30、60、180、210、240、270、300、330 度;这是为了便于核算而手工构造的数据,不是陀螺仪、定位能力或 3DGS SDK 的真实输出。按照这个定义,已覆盖 9/12 个方位,即 75%。缺口恰好是 90°、120°、150° 三个桶。页面状态写作 GAP_FOUND,主按钮为“查看缺口”。我没有把它叫“采集成功”,因为这个状态只说明方向分桶存在空位,图像清晰度、视差与遮挡情况还没有核对。最先要固定的是角度边界。系统输入可能使用负角度、超过一圈的累计角度,甚至带小数。如果直接 Math.floor(angle / 30),-15° 会得到负桶号,而 375° 超出十二桶范围,列表显示与日志便不能对应。## 二、把环绕角度变成稳定的索引下面这段 ArkTS 仅解决一件事:无论输入转了几圈,都归到 [0, 360),再映射到 [0, 11]。非有限数字返回 -1,让调用处拒绝数据,而不是误把异常值算作完成。tsconst BUCKET_COUNT: number = 12;const BUCKET_DEGREE: number = 360 / BUCKET_COUNT;function bucketIndex(angle: number): number { if (!Number.isFinite(angle)) { return -1; } const normalized: number = ((angle % 360) + 360) % 360; return Math.floor(normalized / BUCKET_DEGREE);}function bucketStart(index: number): number { return index * BUCKET_DEGREE;}这里故意不在计算中引入“模糊归位阈值”。30° 附近的 29.9° 和 30.1° 会落到相邻两个桶;这是明确的离散规则,不是定位误差补偿。真实采集中若方位估计误差较大,应先做姿态置信度过滤,再讨论桶边界扩展。否则用一个看似友好的容差把不同方向强行合并,反而会掩盖缺口。另一个容易忽略的细节是 360° 与 0° 应进入同一桶,但 359° 与 0° 虽然空间相近,在本例仍分属第十一桶和第零桶。这样定义有利于报告可重复;如果业务希望环形邻接平滑,需要在独立的提示层处理,不应暗改基础数据层的桶号。## 三、重复照片不等于重复覆盖第二段负责把一次采集会话里的方位整理成布尔覆盖表,并输出空桶。为什么不用“九张照片 / 十二张目标”直接算?因为张数可以重复,方位不能靠重复提交填满。业务侧也应区分“用户按下快门”和“照片已被确认纳入采集候选”的两个阶段。本例假定输入数组已经过确认;如保存失败、位姿无效,不能加入这张表。tsinterface SweepReport { covered: boolean[]; missingDegrees: number[]; visitedCount: number; state: string;}function buildSweepReport(angles: number[]): SweepReport { const covered: boolean[] = new Array<boolean>(BUCKET_COUNT).fill(false); angles.forEach((angle: number) => { const index: number = bucketIndex(angle); if (index >= 0) covered[index] = true; }); const missingDegrees: number[] = []; covered.forEach((visited: boolean, index: number) => { if (!visited) missingDegrees.push(bucketStart(index)); }); const visitedCount: number = BUCKET_COUNT - missingDegrees.length; return { covered, missingDegrees, visitedCount, state: missingDegrees.length === 0 ? 'COVERED' : 'GAP_FOUND' };}布尔数组的好处是天然幂等。同一桶即使传来两百张样本,已覆盖仍只增加一次;接口重试、重复点击、同一路径的二次上报也不会把 75% 变成 100%。但这不是照片去重算法,不判断两张图是否相似,也不会自动找出局部遮挡。在产品中还应保存照片 ID 到桶的映射,以便操作员复核每个桶内哪些照片真实可用。日志适合写输入摘要、覆盖数和缺口列表,不适合把原始照片路径、用户地理位置直接扔进线上日志。为保持示例精确,本篇的演示日志约定如下,全部是预期输出,不是设备实测:[SweepLedger] GS-1815 covered=9/12;[SweepLedger] GS-1815 gaps=90,120,150;[SweepLedger] GS-1815 state=GAP_FOUND。## 四、页面只做能解释的事情有了纯函数之后,ArkUI 页面不要一边绘制卡片一边改覆盖表。数据计算放在进入页面或样本变动的明确时机,显示层只消费结果。为了让演示图片能与正文一一对应,初始数据固定为上述九个角度,页面展示 GS-1815、9/12、75%、GAP_FOUND 与三个缺口起始角度。```ts@Entry@Componentstruct CaptureAuditPage { @State private report: SweepReport = buildSweepReport( [0, 30, 60, 180, 210, 240, 270, 300, 330]); @State private showGaps: boolean = true; build() { Column({ space: 14 }) { Text(‘SweepLedger · 采集方位核对’).fontSize(22) Text(‘任务 GS-1815’) Text(覆盖 ${this.report.visitedCount}/12 · 75%) Text(this.report.state).fontColor(Color.Orange) Button(‘查看缺口’).onClick(() => { this.showGaps = true; }) if (this.showGaps) { ForEach(this.report.missingDegrees, (deg: number) => { Text(${deg}° 待补拍).fontSize(16) }, (deg: number) => deg.toString()) } }.padding(20).width(‘100%’) }}````@State 负责状态更新后的声明式渲染,ForEach按起始角度生成稳定的子项键。这是页面局部的稳定键;当以后加入“不同扫描圈次”时,键必须包含会话与圈次,不能让两个 90° 缺口共享身份。业务上不建议把75%写死,而应从visitedCount * 100 / BUCKET_COUNT 计算并格式化。这里固定数值是为了说明这一批构造输入的确定结果,实际项目需要使用派生值。调试时优先检查三个边界:-15° 能否落入 330° 桶、375°能否回到 0° 桶、重复上报 30° 是否仍然只增加一次。再检查移除某个有效样本后缺口是否恢复,以及页面退出以后是否仍保留不该存在的会话状态。只有手工样例通过,不代表外部传感器的姿态已校准;这个区别必须写在验收记录里。再看一个更容易把进度误判的例子:十张照片中有四张都来自 0° 桶,两张来自 30° 桶,其他四张分散在不同方位。文件数已经达到十,覆盖却最多只有六个桶。若 UI 同时展示“照片 10 张”和“方向 6/12”,就能避免开发者在界面中将它们拼成一条虚假的完成率。对待人工采集来说,准确地告诉用户缺口,比为每一次拍照提供即时庆祝动画更有帮助。还有一种情况是用户转身拍摄后返回起点:姿态值从 359.8° 跳到 0.2°。按基础分桶规则,这是两个相邻但不同的桶;调试时要查的不是页面有无闪烁,而是原始方位是否发生了坐标系切换。如果前后采集阶段的参考朝向不一致,必须保留校准记录并重建方向基准,不能把两个坐标系下的数字直接合并。## 五、屏幕上应看见缺什么,而不是只看见完成多少最终演示画面用十二个环绕分区表示方位,九个青色分区代表已有样本,三个橙色分区对应 90°、120°、150°。中部大数字是“9/12 · 75%”,下方为GAP_FOUND,而不是绿色“重建完成”。这种反馈比单一进度条更有行动价值:操作者知道应该绕到哪一段重新观察目标。这里使用的是**生成的页面示意图**,不是 DevEco Studio 实际运行截图。按钮“查看缺口”只展开名单,不会调用相机、不上传采集数据,更不会启动 3DGS 任务。图上角度是“建议检查的方向桶起点”,不是精确拍摄机位。比如 90° 桶可以对应 [90°,120°) 这个区间,实际操作还要结合目标朝向、遮挡与图像内容来决定相机移动位置。## 六、真正进入工程时,需要再加两层门槛第一层是采集候选质量。模糊、过曝、失焦或同一位置高重复度的照片,方向可能不同却仍然不足以重建。业务应把“采集过”与“可用”两个布尔含义拆开,可以用独立质量评估更新桶内有效照片计数。本文不把某个具体 3DGS 评分阈值写成系统标准;不同算法和机型对输入质量的约束并不相同。第二层是会话和异步状态。GS-1815只是示例任务号,新会话不得继承旧任务的方位数组。若图像保存完成的回执比用户切换任务更晚到达,更新前必须核验 sessionId;退出页面时取消自己注册的监听并释放持有的图片资源。对ForEach` 展示的短列表而言,主要风险不是性能,而是过期回执让缺口列表闪回旧数据。本篇没有声称已经完成 DevEco 编译、真机旋转验证或 Spatial Recon Kit 重建。可复核的结论仅是:按固定的十二桶规则,给定九个示例方位得到 9/12、75% 和缺口 90°/120°/150°。把这份检查放在真实重建流程前,能帮采集 UI 更早暴露可解释的覆盖风险,但不能据此承诺模型质量。参考资料:华为 3DGS 端侧重建参考入口;华为 ArkUI ForEach 声明式渲染说明。接口调用只使用通用 ArkTS/ArkUI 能力;上面 3DGS 文档入口本轮未获得可完整读取的 API 详情,故没有虚构重建接口或 SDK 版本。
更多推荐





所有评论(0)