01 把 SceneForge 的输入协议先做稳了:

1440×1920
→
1080×1440

相机内参同步 ×0.75

48 帧全部 PushFrame

StartSession 成功进入 RECON_RUNNING

真正拿手机围着茶具走一圈以后,第二个问题马上出现:

原始采集的帧,不应该不加判断地全部送进 Session。

这次我把采集数量提高到:

72 帧

肉眼回看,很快就能看到四类问题:

连续两个视角几乎一样;

用户手停住以后,很多帧位移非常小;

转身过快时,有几帧明显模糊;

某一瞬间位姿数据发生了不合理跳变。

Spatial Recon Kit 官方允许 DataFrame 重复输入,系统内部也会做自己的数据处理;但在产品工程里,SceneForge 仍然需要一层项目侧 Frame Gate,目的不是取代系统算法,而是减少明显低价值或异常输入,让采集轨迹更可解释。

这篇最重要的一点也先说清楚:

下文所有阈值都是 SceneForge 当前 Demo 的工程策略,不是 Spatial Recon Kit 官方固定阈值。

本轮统一数据:

taskId:
recon_20261003_02

sessionId:
sf_session_20261003_02

scene:
tabletop_tea_set_02

capturedFrames:
72

normalizedFrames:
72

acceptedFrames:
54

rejectedFrames:
18

duplicateRejected:
6

blurRejected:
4

lowBaselineRejected:
5

poseJumpRejected:
3

frameSpec:
1080 × 1440 RGB

minTranslation:
0.035m

minRotation:
4.0°

blurThreshold:
82.0

maxPoseJump:
0.65m

gateCostAvg:
2.7ms

queueDepthMax:
8

pushSuccess:
54

pushFailed:
0

coverage:
312°

timestampMonotonic:
true

progressAfterStart:
7.6%

status:
RECON_RUNNING

一、Frame Gate 的目标不是“帮系统挑关键帧”,而是先挡掉明显问题

如果把 Frame Gate 写成:

我来决定哪些帧是最优关键帧

这个定位太大了。

SceneForge 只做四件很工程化的事情:

重复视角过滤;

模糊帧过滤;

低基线 / 静止帧过滤;

异常位姿跳变过滤。

也就是说:

它只拒绝明显“不值得进入下游”的帧,不替代 Spatial Recon Kit 内部的重建算法。

这种边界更稳。

二、重复视角先做低成本指纹距离

采集环绕茶具时,用户经常会在同一位置停一下。

相邻两帧肉眼几乎一样。

SceneForge 当前用缩略图快速指纹做第一层去重。

struct FrameGateConfig {
    int duplicateHashDistance;
    double blurThreshold;

    double minTranslationM;
    double minRotationDeg;

    double maxPoseJumpM;
};

bool IsDuplicate(
    const FrameDigest& current,
    const FrameDigest& last,
    int threshold)
{
    int distance =
        HammingDistance(
            current.perceptualHash,
            last.perceptualHash);

    return distance <= threshold;
}

本轮:

duplicateRejected=6

这只是项目快速去重。

不把它称为“3DGS 官方关键帧算法”。

三、模糊检测只负责挡住最差的一批帧

本轮:

blurThreshold=82.0

SceneForge 用灰度缩略图的拉普拉斯方差作为项目级锐度分数。

bool IsBlurFrame(
    const GrayImage& gray,
    double threshold)
{
    double score =
        CalculateLaplacianVariance(
            gray);

    return score < threshold;
}

4 帧被拒绝:

blurRejected=4

这里不能写成:

82 以下就不适合 3DGS

因为阈值会受到:

分辨率
曝光
纹理复杂度
相机噪声
缩略图策略

影响。

它只是 SceneForge 当前测试集的工程基线。

四、低基线过滤解决“人没动,帧一直来”

围绕桌面场景采集时,手机如果几乎没移动,却仍然持续生成帧:

视角信息新增很少;
队列会快速堆积;
输入密度局部失衡。

所以 SceneForge 比较当前帧和上一个 Accepted Frame 的位姿变化。

本轮阈值:

minTranslation=0.035m

minRotation=4.0°

只有:

平移 >= 3.5cm
或
旋转 >= 4°

才认为视角变化达到当前项目要求。

bool HasEnoughBaseline(
    const Pose& current,
    const Pose& lastAccepted,
    const FrameGateConfig& cfg)
{
    double translation =
        TranslationDistanceM(
            current,
            lastAccepted);

    double rotation =
        RotationDistanceDeg(
            current,
            lastAccepted);

    return (
        translation >=
            cfg.minTranslationM ||
        rotation >=
            cfg.minRotationDeg
    );
}

本轮:

lowBaselineRejected=5

五、比较对象必须是“上一个 Accepted Frame”,不是上一帧原始输入

这个细节很容易写错。

假设:

frame 10
Accepted

frame 11
低基线拒绝

frame 12
又只和 11 比

可能导致 frame 12 也错误被拒绝。

正确做法是:

当前候选帧
vs
最后一个已经通过 Gate 的帧

因为 Gate 真正想控制的是:

Accepted Sequence

的视角间距。

不是原始采样序列相邻关系。

六、位姿跳变过滤不是限制“移动太快”,而是防异常输入

SceneForge 还增加:

maxPoseJump=0.65m

如果两帧时间间隔很短,却突然平移 0.65m 以上,当前 Demo 把它当成可疑数据。

本轮:

poseJumpRejected=3

这类异常可能来自:

跟踪瞬时不稳定;
上游位姿错帧;
时间戳配对异常;
采集状态切换。

项目先挡住,保留日志。

不直接把“位姿跳变”解释成用户真的瞬移。

七、四类 Gate 要按顺序执行,避免一帧被重复计数

一张模糊帧可能同时又是重复帧。

统计时如果四个检测都跑完:

duplicate +1
blur +1

总拒绝数就会大于真实帧数。

所以 02 固定优先级:

1 duplicate

2 blur

3 pose jump

4 low baseline

命中第一条就返回。

GateResult CheckFrame(
    const NormalizedFrame& frame,
    const GateContext& context)
{
    if (IsDuplicate(...)) {
        return Reject(DUPLICATE);
    }

    if (IsBlurFrame(...)) {
        return Reject(BLUR);
    }

    if (HasPoseJump(...)) {
        return Reject(POSE_JUMP);
    }

    if (!HasEnoughBaseline(...)) {
        return Reject(LOW_BASELINE);
    }

    return Accept();
}

最终:

6 + 4 + 3 + 5
=
18

和:

72 - 54
=
18

完全对上。

八、54 帧通过以后,才进入 PushFrame 队列

02 没有改变 Spatial Recon 的 API 顺序。

仍然是:

Normalize
→ Frame Gate
→ Accepted Queue
→ CreateSession
→ PushFrame × 54
→ StartSession

Frame Gate 只发生在 PushFrame 之前。

因为 01 已经明确:

StartSession 以后不能再继续 PushFrame

所以 02 的采集、筛选和推帧仍然都属于重建启动前阶段。

九、为什么还要加一个 queueDepthMax=8

采集线程和 Native PushFrame 不一定完全同速。

如果用户快速移动,前端可能短时间产生很多候选帧。

SceneForge 不允许:

Accepted Frame
无限堆进内存。

当前队列:

queueDepthMax=8

如果队列满:

采集侧暂缓接受新候选帧;
优先消化当前队列。

这条策略不是 Spatial Recon Kit 的硬限制。

只是 SceneForge 为了控制:

RGB buffer
DataFrame 生命周期
内存峰值

设置的项目边界。

十、PushFrame 成功以后,Frame 自己的 RGB Buffer 才能释放

HMS_SpatialRecon_DataFrame.imageData 是原始指针。

这意味着 SceneForge 不能在函数调用之前就把背后的 RGB Buffer 释放。

当前 Owner 规则:

FrameQueue 持有 NormalizedFrame

调用 PushFrame

返回后
当前 Buffer 生命周期结束

Queue 释放 Frame

如果后续官方 API 对数据拷贝语义有更细说明,仍以当前文档为准。

项目内部至少要避免“还没 Push 就已经悬空指针”。

十一、Gate 平均只用了 2.7ms,但不能在主线程堆太多图像操作

本轮:

gateCostAvg=2.7ms

包含:

指纹
缩略图锐度
位姿比较
简单统计

这是一组项目实测基线。

如果未来把:

复杂特征匹配
光流
深度估计

全塞进 Gate,采集 UI 自己就会掉帧。

Gate 的设计目标一直是:

低成本挡明显问题。

复杂质量判断留给后面的重建系统和离线分析。

十二、312° 覆盖不是官方“合格线”

SceneForge 根据 Accepted Frame 的相机位置和朝向,估算本轮环绕覆盖:

312°

这个值用于 UI 给用户提示:

还有一侧没拍够。

它不是 Spatial Recon Kit 官方要求:

必须 >= 300°

如果用户只扫描半个空间、墙面或开放场景,覆盖角度的定义也会不同。

所以这一项只是当前“桌面茶具环绕采集”的项目指标。

十三、54 帧全部 Push 成功以后才 StartSession

最终:

pushSuccess=54
pushFailed=0

时间戳仍然:

timestampMonotonic=true

然后进入:

StartSession

第一次稳定进度:

7.6%

状态:

RECON_RUNNING

从 01 到 02,真正变化的是:

进入 Session 的帧更少了,
但每一帧更有解释。

十四、DevEco 图为什么把阈值标成“项目策略”

开发图:

红色标注直接写:

质量门槛是 SceneForge 项目策略,
不是 Spatial Recon Kit 官方固定阈值。

这个提示非常重要。

因为:

0.035m
4°
82.0
0.65m

都是当前 Demo 调出来的基线。

以后换:

室内房间
大型展品
人像
建筑立面

阈值一定要重新验证。

十五、运行图把“采集质量”做成用户能看懂的反馈

最终运行图:

用户能看到:

72 captured

54 accepted

18 rejected

重复:
6

模糊:
4

低基线:
5

位姿跳变:
3

coverage:
312°

progress:
7.6%

这比一句:

“请继续移动手机”

更容易指导下一步采集。

十六、Frame Gate 也不能越权修改相机位姿

发现:

pose jump

当前策略是:

Reject

而不是:

自己把位姿修一下再送进去。

真正位姿优化可以来自:

AR Engine
Spatial Recon refined frame
更专业的 tracking pipeline

Frame Gate 的职责只是:

识别明显异常;
不篡改上游真实数据。

十七、02 最后固定八组故障注入

第一组,连续重复视角,正确计入 duplicate。

第二组,明显模糊,进入 blur。

第三组,平移小于 3.5cm 且旋转小于 4°,进入 low baseline。

第四组,短间隔出现 >0.65m 跳变,进入 pose jump。

第五组,同一帧同时模糊和重复,只按优先级计一次。

第六组,队列超过 8,不继续无限增长。

第七组,54 accepted 帧时间戳保持单调。

第八组,全部 Push 完以后才允许 StartSession。

全部通过以后,本轮才记录:

RECON_RUNNING

十八、下一篇开始真正进入“重建运行期”

到 02 为止,SceneForge 解决的仍然是:

重建启动之前
该给系统什么数据。

03 会进入 Session 真正运行以后:

GetProgress

PauseSession

ResumeSession

SetRunningMode

前台 / 后台切换

暂停后的阶段状态

也就是说,从下一篇开始,工程主线正式从“采集质量”切到“长任务生命周期”。

十九、Frame Gate 的阈值应该从“误拒绝率”反向校准

如果 Gate 只看:

拒绝了多少帧

很容易走到另一个极端:

为了让输入更干净
把有价值视角也过滤掉。

所以 SceneForge 调阈值时会同时看:

Reject Rate

Coverage

最终重建稳定性

本轮:

18 / 72
=
25%

被拒绝。

但 Accepted Frame 仍然覆盖:

312°

如果把 minRotation 从 4° 提到 8°,拒绝数可能明显上升,覆盖也可能出现空洞。

因此这些参数不是“越严格越好”,而是:

在输入冗余、采集成本和空间覆盖之间找一个当前场景可接受的点。

二十、模糊分数最好在缩略图上算,不要每帧全分辨率做重操作

DataFrame 最终要准备:

1080×1440 RGB

但 Gate 不需要对整张 RGB 做所有分析。

SceneForge 当前先生成轻量灰度缩略图,再算:

感知 hash
Laplacian variance

这样能把:

gateCostAvg

控制在:

2.7ms

附近。

如果后面发现某个质量问题确实需要高分辨率分析,再单独升级该项。

Gate 的第一目标是:

别把采集链自己拖慢。

二十一、位姿比较必须使用统一坐标系

低基线和 pose jump 都依赖:

position
rotation

如果上游一部分位姿来自世界坐标,另一部分来自临时相机坐标,距离计算结果会完全失真。

所以进入 Gate 前,SceneForge 先把 Pose 转成统一任务坐标系:

Task Local Space

Frame Gate 不负责坐标转换。

它只消费已经归一化的 Pose。

这条边界和 01 的“内参归一化先做完再 BuildFrame”非常像:

Gate 不修数据
Gate 只判断数据。

二十二、覆盖角度不能只看“最后一帧减第一帧”

环绕采集可能:

0°
→ 180°
→ 回到 90°
→ 再到 312°

如果简单用:

maxAngle - minAngle

容易把中间空洞忽略掉。

SceneForge 当前把 Accepted Frame 的方位离散到 12° 桶:

0-12
12-24
...

再统计连续覆盖区域。

最终 UI 显示:

312°

仍然是项目估算,不是系统官方覆盖率。

但比只看首尾姿态更能指导用户“哪一侧还没拍到”。

二十三、被拒绝的帧也要保留轻量日志,不能直接消失

如果 Gate 只输出:

accepted=54

后面很难调参数。

所以每个 Reject 只保留轻量信息:

frameIndex
timestamp
reason
blurScore
translation
rotation
poseJump

不会保存一份完整 RGB Buffer。

这样既能复盘:

第 37 帧为什么被拒绝

又不会因为“为了日志”把 18 张大图一直留在内存。

二十四、duplicate 和 low baseline 看起来相似,实际解决的问题不同

重复视角:

画面内容几乎一样

低基线:

相机变化太小

两者常常同时出现,但并不等价。

例如用户在原地旋转:

translation 很小
rotation 足够大

低基线可以通过,但画面如果对称度很高,仍可能被 duplicate 拒绝。

反过来,在纹理重复的场景里:

画面 hash 很像

但相机确实移动了。

所以两个 Gate 必须保留独立原因。

这也是为什么 02 不把 18 个拒绝帧统一记成:

LOW_QUALITY

二十五、Queue Depth=8 还要配合 Buffer 释放

队列满了以后,如果只是:

不再入队

但上游仍然把新帧 RGB Buffer 缓存在另一个列表里,内存还是会涨。

SceneForge 的 Owner 规则是:

未入 Accepted Queue
→ 立即释放当前 normalized RGB Buffer

进入 Queue
→ PushFrame 返回后释放

这样 queueDepthMax=8 才真正具有内存意义。

单纯限制容器长度但不释放背后大对象,只是数字好看。

二十六、Frame Gate 不能在 StartSession 后继续“动态补帧”

有些实时建模系统允许:

边采边建

但当前 Spatial Recon 这条 API 流程明确:

先 PushFrame
再 StartSession

StartSession 之后不再继续 PushFrame。

所以 SceneForge 的 Frame Gate 只属于:

Capture / Feed 阶段。

一旦进入:

RECON_RUNNING

“继续采集”按钮不会把新帧塞进当前 Session。

如果产品未来需要补采,只能设计成:

结束当前任务
→ 新建 Session

或等待官方提供不同能力路径。

二十七、54 帧不是“最优帧数”

这一篇的数据很容易被误读成:

茶具重建应该用 54 帧。

不是。

54 只是:

当前场景
当前移动速度
当前 Gate 参数
当前 72 帧采集

筛出来的结果。

换房间、商品、文旅场景,最佳输入数量会完全不同。

SceneForge 真正想固定的是:

筛选机制可解释
而不是帧数本身固定。

二十八、02 最终形成的是采集前端和重建后端之间的“质量闸门”

到这里,链路已经变成:

Camera / AR Tracking

→ Normalize

→ Frame Gate

→ Accepted Queue

→ Spatial Recon Session

这层 Gate 既没有越权修改系统算法,也没有把所有原始帧无脑下发。

下一篇真正进入 BUILDING 状态以后,Frame Gate 就退出舞台,工程重点会切换到:

Pause / Resume
Foreground / Background
Progress / Stage

这也是同一个 SceneForge 项目自然推进到下一阶段的地方。

参考资料

  • Spatial Recon Kit HMS_SpatialRecon_DataFrame:
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-spatialrecon-hms-spatialrecon-dataframe
  • spatial_recon_interface.h:
    https://developer.huawei.com/consumer/cn/doc/harmonyos-references/capi-spatial-recon-interface-h
  • Spatial Recon Kit 术语:
    https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/spatial-recon-glossary
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐