HarmonyOS 7 SceneForge 3DGS端侧重建实录 02:SpatialRecon × Frame Gate:模糊帧、低基线、重复视角与位姿跳变过滤【鸿蒙心迹】
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
更多推荐



所有评论(0)