chore: initial commit — import worldmodel workspace (plans/, research/)
This commit is contained in:
@@ -0,0 +1,494 @@
|
||||
# Chapter 02 — 四层空间记忆架构
|
||||
|
||||
> 本章目标:把机器人大脑里的"空间记忆"拆成 **L1–L4 四层**,逐层说清楚:**存什么 / 怎么存 / 谁写 / 谁读 / 何时过期。**
|
||||
|
||||
---
|
||||
|
||||
## 2.1 为什么是四层?而不是 1 层、3 层、7 层?
|
||||
|
||||
### 不能是 1 层的原因
|
||||
单一表示(如纯点云)既不能高频更新(点云重写太慢),也不能直接被 LLM 查询(没有语义)。
|
||||
|
||||
### 不能是 3 层的原因
|
||||
若合并 L3 拓扑与 L4 语义为一层,**路径规划**(需要拓扑边权)与**问答**(需要属性查询)会互相干扰。
|
||||
|
||||
### 不能更多(如 7 层)的原因
|
||||
分层越多,**层间同步成本越高**。四层来自三个现实约束的交集:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph C["认知科学"]
|
||||
direction LR
|
||||
C1["Working"] <--> C2["Short-term"]
|
||||
C2 <--> C3["Long-term"]
|
||||
end
|
||||
subgraph S["SLAM 工程"]
|
||||
direction LR
|
||||
S1["Metric"] <--> S2["Topological"]
|
||||
S2 <--> S3["Semantic"]
|
||||
end
|
||||
subgraph R["机器人控制"]
|
||||
direction LR
|
||||
R1["Reactive (Hz 级)"] <--> R2["Deliberative (秒级)"]
|
||||
end
|
||||
subgraph L["PRISM 四层(三家传统的最小公倍数)"]
|
||||
direction LR
|
||||
L1["L1<br/>感知缓冲"] --- L2["L2<br/>度量"] --- L3["L3<br/>拓扑"] --- L4["L4<br/>语义"]
|
||||
end
|
||||
C -. 分化 .-> S
|
||||
S -. 加感知缓冲 .-> R
|
||||
R -. 取并集 .-> L
|
||||
style L fill:#fff7d6,stroke:#c97a00,stroke-width:2px
|
||||
```
|
||||
|
||||
四层是这三套传统在工程上的**最小公倍数**。
|
||||
|
||||
---
|
||||
|
||||
## 2.2 四层全景图
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph PRISM["PRISM Spatial Memory(左:高频・易变・低抽象 → 右:低频・稳定・高抽象)"]
|
||||
direction LR
|
||||
L1["<b>L1 感知缓冲</b><br/>Perceptual<br/>──────────<br/>位姿环形缓冲<br/>深度环形缓冲<br/>──────────<br/>ZED 2i 主写<br/>更新:30 Hz<br/>寿命:ms 级"]
|
||||
L2["<b>L2 度量</b><br/>Metric<br/>──────────<br/>占据栅格<br/>TSDF<br/>3DGS<br/>──────────<br/>iPhone+ZED 共写<br/>更新:5 Hz<br/>寿命:秒级"]
|
||||
L3["<b>L3 拓扑</b><br/>Topological<br/>──────────<br/>房间节点 + 边<br/>锚点列表<br/>──────────<br/>iPhone 主写<br/>更新:低频<br/>寿命:分钟级"]
|
||||
L4["<b>L4 语义</b><br/>Semantic<br/>──────────<br/>场景图 Neo4j<br/>bbox 属性<br/>──────────<br/>iPhone+ZED 共写<br/>更新:极低频<br/>寿命:小时级"]
|
||||
L1 --> L2 --> L3 --> L4
|
||||
end
|
||||
style L1 fill:#fde2e2,stroke:#a33
|
||||
style L2 fill:#fff1c1,stroke:#a87a00
|
||||
style L3 fill:#d4f0d4,stroke:#2e7d32
|
||||
style L4 fill:#d8e4ff,stroke:#1565c0
|
||||
```
|
||||
|
||||
记住三条直觉:
|
||||
|
||||
1. **越往右越"抽象"**:L1 是像素和位姿,L4 是 "the lamp is on the nightstand"
|
||||
2. **越往右越"慢"**:L1 ms 级覆盖,L4 小时级更新
|
||||
3. **越往右越"小"**:L1 几 GB/小时,L4 几 MB/全场景
|
||||
|
||||
---
|
||||
|
||||
## 2.3 L1 — 感知缓冲 (Perceptual Buffer)
|
||||
|
||||
### 2.3.1 定位
|
||||
"机器人最近几秒看到的所有东西"——纯粹的**工作记忆**,类似人的视觉残留。
|
||||
|
||||
### 2.3.2 存什么
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class PerceptualFrame:
|
||||
timestamp: float
|
||||
pose: Pose # T_robot→map (ZED VIO 输出)
|
||||
rgb_left: np.ndarray # (H,W,3) 可选保存
|
||||
depth: np.ndarray # (H,W)
|
||||
imu_packet: List[IMUSample] # 自上一帧以来的 IMU
|
||||
keypoints: Optional[np.ndarray] # ORB/SuperPoint 关键点
|
||||
|
||||
class L1Buffer:
|
||||
capacity_seconds: float = 10.0 # 环形缓冲容量
|
||||
keyframe_interval: float = 0.5 # 关键帧采样间隔
|
||||
ring: Deque[PerceptualFrame] # 满则覆盖
|
||||
keyframes: Deque[PerceptualFrame] # 关键帧池(保留更久)
|
||||
```
|
||||
|
||||
### 2.3.3 写者 / 读者
|
||||
|
||||
| 角色 | 频率 | 操作 |
|
||||
|------|------|------|
|
||||
| **写**:ZED 2i 驱动 | 30 Hz | `ring.append(frame)` |
|
||||
| **读**:避障 / 局部规划 | 10 Hz | 取最近 1 s 的 frames |
|
||||
| **读**:L2 融合器 | 5 Hz | 取一个关键帧融入 TSDF |
|
||||
| **读**:回环检测 | 1 Hz | 与关键帧池做相似度匹配 |
|
||||
|
||||
### 2.3.4 过期规则
|
||||
- 超过 `capacity_seconds` 的非关键帧 → 直接丢弃
|
||||
- 关键帧:若已成功融入 L2 → 5 分钟后可丢弃
|
||||
- 全部仅在内存,不落盘(除非 debug)
|
||||
|
||||
### 2.3.5 失效与降级
|
||||
- VIO 跟丢 → L1 暂停接收 → 触发重定位(见 [`05_pipeline_B_relocalization.md`](05_pipeline_B_relocalization.md))
|
||||
- IMU 异常 → 仅用视觉位姿,标记 `confidence=low`
|
||||
|
||||
---
|
||||
|
||||
## 2.4 L2 — 度量记忆 (Metric Memory)
|
||||
|
||||
> ⚠️ **v1.5 更新**:本节后续提及"L2 voxel 直接生成 L3/L4 节点内容"的写法已被 [§ 2.7b L2-L3 数据流:路由 vs 内容(v1.5 新原则)](#27b-l2-l3-数据流路由-vs-内容v15-新原则) 取代。L2 几何在 v1.5 起仅用作**路由信号**,不再承担"决定节点内容"的职责。旧描述保留以备历史追溯。
|
||||
|
||||
### 2.4.1 定位
|
||||
"3D 几何长什么样"——机器人能在上面**规划路径、避障、渲染**的稠密表示。
|
||||
|
||||
### 2.4.2 存什么(多重表示)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class L2Memory:
|
||||
# 用途 1:导航——2D/2.5D 占据栅格
|
||||
occupancy_grid: OctoMap # 5 cm 分辨率,0=空 1=占 -1=未知
|
||||
|
||||
# 用途 2:精细几何——TSDF / Mesh
|
||||
tsdf: VoxelBlockGrid # 2 cm 分辨率
|
||||
mesh_uri: str # 离线烘焙的 .glb
|
||||
|
||||
# 用途 3:渲染 / 视觉相似度——3DGS
|
||||
gaussians_uri: Optional[str] # .ply,每房间一份
|
||||
|
||||
# 用途 4:先验掩膜
|
||||
prior_mask: np.ndarray # 0=ZED 可写 1=iPhone 静态保护
|
||||
no_update_zone: np.ndarray # 1=镜面/玻璃,禁止写入
|
||||
```
|
||||
|
||||
为什么要 4 套表示?
|
||||
|
||||
| 表示 | 谁用 | 为什么不能替代 |
|
||||
|------|------|----------------|
|
||||
| OctoMap | 路径规划器 | 路径规划只关心"能不能走",cm 级足够 |
|
||||
| TSDF | 差异检测 / 抓取 | 需要带符号距离才能算 SDF 差 |
|
||||
| Mesh | 可视化 / Unity 仿真 | 渲染流水线友好 |
|
||||
| 3DGS | 视觉重定位 / 新视角生成 | 比 mesh 真实得多 |
|
||||
|
||||
它们**共享同一份原始点云**,只是不同的"派生视图"。
|
||||
|
||||
### 2.4.3 写者 / 读者
|
||||
|
||||
| 区域 | 主写者 | 来源 | 频率 |
|
||||
|------|--------|------|------|
|
||||
| **静态结构**(墙、门、固定家具) | iPhone | RoomPlan mesh | 1 次/场景 |
|
||||
| **可变区域**(家具间空隙、地面) | ZED 2i | TSDF 增量 | 5 Hz 局部 |
|
||||
| **新发现区**(iPhone 没扫到) | ZED 2i | TSDF 新建 voxel | 5 Hz |
|
||||
| **镜面/玻璃** | 都不写 | — | — |
|
||||
|
||||
### 2.4.4 关键规则:先验保护
|
||||
|
||||
```python
|
||||
def fuse_zed_to_l2(zed_tsdf_local, l2: L2Memory):
|
||||
for voxel in zed_tsdf_local:
|
||||
if l2.no_update_zone[voxel.idx]:
|
||||
continue # 镜面区,跳过
|
||||
if l2.prior_mask[voxel.idx]:
|
||||
# 先验区:只允许微调(低权重)
|
||||
l2.tsdf.update(voxel, weight=0.1)
|
||||
else:
|
||||
# 自由区:正常融合
|
||||
l2.tsdf.update(voxel, weight=1.0)
|
||||
```
|
||||
|
||||
→ iPhone 提供的墙不会被一次 ZED 噪声毁掉,但小幅度(< 5 cm)的修正可累积起效。
|
||||
|
||||
### 2.4.5 过期规则
|
||||
- iPhone 写入的体素 → 长期保留,但有 `last_seen` 字段
|
||||
- ZED 写入的体素:30 天内未被再次确认 → 衰减 confidence
|
||||
- 若某区域被打上 `delta`(家具搬走)→ 在 Consolidator 跑后才物理移除
|
||||
|
||||
---
|
||||
|
||||
## 2.5 L3 — 拓扑记忆 (Topological Memory)
|
||||
|
||||
> ⚠️ **v1.5 更新**:本节中"由 L2 几何(TSDF/OctoMap)反推生成 L3 节点内容"的旧写法已被 [§ 2.7b L2-L3 数据流:路由 vs 内容(v1.5 新原则)](#27b-l2-l3-数据流路由-vs-内容v15-新原则) 取代。L3 节点的 `clip_embedding` / `polygon` / 锚点描述等**内容**改为从 L1 高质量 keyframe 直接获取;L2 仅决定"写哪个节点"。旧描述保留以备历史追溯。
|
||||
|
||||
### 2.5.1 定位
|
||||
"房间和走廊怎么连"——机器人**长距离导航**的骨架。
|
||||
|
||||
### 2.5.2 存什么
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class L3Node:
|
||||
uid: str # "room_301"
|
||||
label: str # "Bedroom" / "Hallway" / "Lobby"
|
||||
center: np.ndarray # (3,) 房间几何中心
|
||||
polygon: np.ndarray # (N,2) 房间地面多边形
|
||||
anchors: List[str] # 引用 L4 中的家具 uid(如 'bed_301')
|
||||
clip_embedding: np.ndarray # (512,) 房间整体视觉指纹
|
||||
|
||||
@dataclass
|
||||
class L3Edge:
|
||||
src: str # "room_301"
|
||||
dst: str # "hallway_3F"
|
||||
via: str # 连接介质:'door_301a' / 'open_passage'
|
||||
cost: float # 通行成本(距离 + 难度)
|
||||
bidirectional: bool = True
|
||||
|
||||
class L3Memory:
|
||||
nodes: Dict[str, L3Node]
|
||||
edges: List[L3Edge]
|
||||
anchor_index: Dict[str, str] # furniture_uid -> room_uid 反向索引
|
||||
```
|
||||
|
||||
### 2.5.3 写者 / 读者
|
||||
|
||||
| 角色 | 何时 | 操作 |
|
||||
|------|------|------|
|
||||
| **写**:iPhone 解析器 | 离线一次 | 按 RoomPlan 的 room 切分自动生成节点;门窗作为边 |
|
||||
| **写**:ZED 2i + 巡逻 | 机器人实际穿过门时 | 确认/新增边,更新 `cost` |
|
||||
| **读**:高层规划器 | 任务下发时 | 在 L3 图上跑 A* / Dijkstra 得"房间序列" |
|
||||
| **读**:重定位器 | 上电 / 跟丢时 | 用 `clip_embedding` 做粗匹配 |
|
||||
|
||||
### 2.5.4 过期规则
|
||||
- 房间节点:除非装修,否则永久保留
|
||||
- 边:连续 N 次(默认 3 次)巡逻都走不通 → 标记 `deprecated`,路径规划器跳过
|
||||
- 锚点:被 ZED 检测到原家具消失 → 从 `anchors` 中移除,但节点不删
|
||||
|
||||
### 2.5.5 与 L4 的区别(容易混淆!)
|
||||
- **L3 是"地理"**:节点是**地方**(房间、走廊),边是**通行关系**
|
||||
- **L4 是"物品"**:节点是**东西**(床、灯、遥控器),边是**支撑/包含/邻接关系**
|
||||
- L3 节点 `room_301` 通过 `anchors` 字段指向 L4 节点 `bed_301`, `tv_301`...
|
||||
|
||||
---
|
||||
|
||||
## 2.6 L4 — 语义记忆 (Semantic Memory)
|
||||
|
||||
### 2.6.1 定位
|
||||
"哪个东西在哪儿、长啥样、跟谁挨着"——机器人**理解任务**和**与人对话**的底座。
|
||||
|
||||
### 2.6.2 存什么
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class L4Node:
|
||||
uid: str # 'bed_301', 'lamp_301_a'
|
||||
label: str # 'bed' / 'lamp' / 'remote_control'
|
||||
category: str # 'furniture' / 'appliance' / 'small_item'
|
||||
pose: Pose
|
||||
bbox_3d: np.ndarray # (8,3) OBB
|
||||
mesh_uri: Optional[str]
|
||||
clip_embedding: np.ndarray # (512,)
|
||||
attributes: Dict # {color, material, state(on/off/open/closed),
|
||||
# mobile: bool, fragile: bool, ...}
|
||||
parent_room: str # L3 房间 uid
|
||||
source: Literal['iphone','zed2i','vlm','fused']
|
||||
first_seen: float
|
||||
last_seen: float
|
||||
observation_count: int
|
||||
confidence: float
|
||||
|
||||
@dataclass
|
||||
class L4Edge:
|
||||
src_uid: str
|
||||
dst_uid: str
|
||||
relation: Literal['on','under','in','next_to',
|
||||
'inside_drawer','plugged_into',...]
|
||||
confidence: float
|
||||
|
||||
class L4Memory:
|
||||
nodes: Dict[str, L4Node]
|
||||
edges: List[L4Edge]
|
||||
spatial_index: Optional[KDTree] # 加速"附近的东西"查询
|
||||
semantic_index: Optional[Faiss] # CLIP 向量库,加速文本查物体
|
||||
```
|
||||
|
||||
### 2.6.3 写者 / 读者
|
||||
|
||||
| 角色 | 频率 | 操作 |
|
||||
|------|------|------|
|
||||
| **写**:iPhone 解析器 | 离线一次 | 16 类家具直接落库(高 confidence) |
|
||||
| **写**:ZED + VLM | 2 Hz | 检出物品,与现有节点匹配或新建 |
|
||||
| **写**:Consolidator | 充电时 | 确认 `delta` 并固化 |
|
||||
| **读**:LLM Agent | 按需 | `find("遥控器在哪")` → CLIP 检索 + 关系遍历 |
|
||||
| **读**:抓取规划器 | 任务时 | 取目标 `bbox_3d` + `attributes.fragile` |
|
||||
| **读**:渲染器 | 可视化 | 取所有 `mesh_uri` |
|
||||
|
||||
### 2.6.4 三类节点的不同生命周期
|
||||
|
||||
| 类型 | 例子 | mobile | 谁写 | 多久过期 |
|
||||
|------|------|--------|------|----------|
|
||||
| **固定结构** | 墙、门、内嵌衣柜 | false | iPhone | 永不 |
|
||||
| **大件家具** | 床、沙发、书桌 | half | iPhone 主,ZED 校准 | 季度级 |
|
||||
| **小物品** | 遥控器、水杯、毛巾 | true | ZED + VLM 主 | 小时级 |
|
||||
|
||||
`mobile` 标签直接影响重定位是否能用它当 anchor(见 [`05_pipeline_B_relocalization.md`](05_pipeline_B_relocalization.md))。
|
||||
|
||||
### 2.6.5 关系(Edge)的两种来源
|
||||
|
||||
```python
|
||||
# 1. 几何派生:通过 bbox 相对位置自动得出
|
||||
def derive_edge_from_geometry(a: L4Node, b: L4Node) -> Optional[L4Edge]:
|
||||
if a.bbox_3d.contains(b.bbox_3d.center):
|
||||
return L4Edge(b.uid, a.uid, 'in', confidence=0.9)
|
||||
if abs(b.bbox_min_z - a.bbox_max_z) < 0.05:
|
||||
return L4Edge(b.uid, a.uid, 'on', confidence=0.85)
|
||||
if a.distance_to(b) < 0.5:
|
||||
return L4Edge(a.uid, b.uid, 'next_to', confidence=0.7)
|
||||
return None
|
||||
|
||||
# 2. VLM 派生:让 GPT-4V / Qwen-VL 看图给关系
|
||||
def derive_edge_from_vlm(rgb_image, detections) -> List[L4Edge]:
|
||||
prompt = f"Given these detected objects {detections}, " \
|
||||
f"list spatial relations as (a, relation, b)."
|
||||
return vlm.parse_relations(rgb_image, prompt)
|
||||
```
|
||||
|
||||
两路边都允许存在,按 `confidence` 加权融合。
|
||||
|
||||
---
|
||||
|
||||
## 2.7 层间交互规则
|
||||
|
||||
### 2.7.1 写入顺序(数据如何流入大脑)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
ZED["ZED 2i<br/>原始数据"]
|
||||
VLM["VLM<br/>(在线感知)"]
|
||||
L1["L1 感知缓冲"]
|
||||
L2["L2 度量"]
|
||||
DELTA[("delta/<br/>差异目录")]
|
||||
L4["L4 语义"]
|
||||
L3["L3 拓扑"]
|
||||
CON["Consolidator<br/>(充电时跑)"]
|
||||
IPHONE["iPhone<br/>(离线一次)"]
|
||||
LTM[("L2 + L3 + L4<br/>永久保留")]
|
||||
|
||||
ZED -- "30 Hz 写入" --> L1
|
||||
ZED -- "5 Hz 关键帧" --> L2
|
||||
ZED -- "2 Hz" --> VLM
|
||||
L2 -- "差异检测" --> DELTA
|
||||
VLM -- "关联" --> L4
|
||||
DELTA -- "充电时确认" --> CON
|
||||
CON --> L4
|
||||
L4 -- "更新 anchors" --> L3
|
||||
IPHONE -- "离线一次" --> LTM
|
||||
|
||||
style L1 fill:#fde2e2,stroke:#a33
|
||||
style L2 fill:#fff1c1,stroke:#a87a00
|
||||
style L3 fill:#d4f0d4,stroke:#2e7d32
|
||||
style L4 fill:#d8e4ff,stroke:#1565c0
|
||||
style CON fill:#ffe9b3,stroke:#c97a00
|
||||
style DELTA fill:#f5e1ff,stroke:#7b1fa2
|
||||
```
|
||||
|
||||
### 2.7.2 查询顺序(Agent 怎么读大脑)
|
||||
|
||||
最常见的查询是 **"找东西 + 怎么去"**,标准流程:
|
||||
|
||||
```python
|
||||
def query_find_and_navigate(text: str, memory):
|
||||
# 1. L4 语义搜索:文本 → 物品 uid
|
||||
obj_uid = memory.l4.semantic_search(text) # CLIP
|
||||
obj = memory.l4.nodes[obj_uid]
|
||||
|
||||
# 2. L3 拓扑规划:当前房间 → 物品所在房间
|
||||
cur_room = memory.l3.locate(memory.l1.current_pose)
|
||||
path_rooms = memory.l3.astar(cur_room, obj.parent_room)
|
||||
|
||||
# 3. L2 度量规划:物品所在房间内的精确路径
|
||||
metric_path = memory.l2.plan_path(
|
||||
start=memory.l1.current_pose,
|
||||
goal=obj.pose,
|
||||
room_sequence=path_rooms)
|
||||
|
||||
# 4. L1 实时跟随 + 避障
|
||||
follow_path(metric_path)
|
||||
|
||||
return obj
|
||||
```
|
||||
|
||||
→ **从右向左下钻**:抽象 → 具体,是 PRISM 查询的标准范式。
|
||||
|
||||
### 2.7.3 写入冲突的仲裁
|
||||
|
||||
当 iPhone 和 ZED 对同一区域有不同观测:
|
||||
|
||||
| 情况 | 仲裁规则 |
|
||||
|------|----------|
|
||||
| iPhone 说有墙,ZED 说没墙 | 单次→忽略 ZED;连续 N 帧→写 delta;Consolidator 确认才删墙 |
|
||||
| iPhone 没标家具,ZED 检到家具 | 立即写 L4 新节点,但 `confidence` 起始 0.3,多次确认才上升 |
|
||||
| iPhone 标了 lamp,ZED 检到 lamp 但位置偏 30 cm | ZED 微调 `pose`,confidence 加权平均 |
|
||||
| iPhone 标了 chair(mobile=true),ZED 没看到 | 不立即删,标记 `state='moved'`,consolidate 决定 |
|
||||
|
||||
总原则:**iPhone 写入的内容"假设正确直到证据充分相反"**。
|
||||
|
||||
---
|
||||
|
||||
## 2.7b L2-L3 数据流:路由 vs 内容(v1.5 新原则)
|
||||
|
||||
> v1.5 起 PRISM 借鉴 Lyra 2.0 的核心思想——**"几何只做路由,不做合成"**——重新厘清 L2 与 L3 之间的职责边界。详细动机与 Lyra 2.0 的对照参见 [`18_lyra_inspirations.md`](18_lyra_inspirations.md)(尤其是 §18.2)。在此之前,PRISM 的 L2 既负责几何稠密表示,又承担"把 voxel 内容写入 L3 节点"的合成职责,导致 L3 节点的视觉指纹(CLIP 嵌入)、bounding box、文字描述等都受 L2 voxel 量化精度限制;新原则把这两件事彻底解耦。
|
||||
|
||||
### 2.7b.1 路由 vs 内容:两条数据流分工
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
L1KF["<b>L1 keyframe</b><br/>full-res RGB-D<br/>+ CLIP embedding<br/>(高保真原始观测)"]
|
||||
L2GEO["<b>L2 几何</b><br/>TSDF / OctoMap<br/>(低精度,可量化)"]
|
||||
L3NODE["<b>L3 节点写入</b><br/>clip_embedding<br/>polygon / anchors<br/>(高保真内容)"]
|
||||
L1KF -- "内容来源<br/>(高保真)" --> L3NODE
|
||||
L2GEO -. "仅作路由信号<br/>(决定写哪个节点)" .-> L3NODE
|
||||
style L1KF fill:#fde2e2,stroke:#a33
|
||||
style L2GEO fill:#fff1c1,stroke:#a87a00
|
||||
style L3NODE fill:#d4f0d4,stroke:#2e7d32
|
||||
```
|
||||
|
||||
- **L2 几何 = 路由信号**:仅用来判断"当前观测的 3D 位置应该归属到 L3 图里的哪个节点"。即使 L2 的 voxel 是 2 cm 量化、含噪声、甚至局部缺失,只要它能**指向正确的 L3 节点 uid**就够了。
|
||||
- **L1 keyframe = 内容源**:节点的 `clip_embedding`、视觉证据、bounding box、属性描述等**实际内容**必须来自 L1 缓存里那一张未经量化的高分辨率 RGB-D keyframe(或它的特征),而非从 L2 voxel 反投影回来。
|
||||
|
||||
一句话总结:**几何精度只需要够"指向哪个节点",不需要"决定节点内容"**。
|
||||
|
||||
### 2.7b.2 路由式写入伪代码
|
||||
|
||||
```python
|
||||
def write_to_l3(observation, l2_geometry, l3_graph):
|
||||
# ① L2 只参与"路由":根据观测的 3D 位置定位 L3 节点 uid
|
||||
target_node = route_via_l2(observation.position, l2_geometry)
|
||||
# ② 内容来源是 L1 高保真 keyframe,而非 L2 voxel
|
||||
keyframe_evidence = get_best_keyframe(observation, l3_graph[target_node])
|
||||
# ③ 用 keyframe 的原始 CLIP / bbox / patch 更新节点内容
|
||||
l3_graph[target_node].update(keyframe_evidence)
|
||||
```
|
||||
|
||||
四行核心逻辑里,L2 只出现在第一步(`route_via_l2`)且只读;真正塑造 L3 节点内容的是第二步从 L1 取回的 `keyframe_evidence`。这一拆分让 L2 即使在 OctoMap 5 cm + TSDF 噪声的精度下仍然足以胜任,而 L3 的语义保真度由 L1 决定。
|
||||
|
||||
### 2.7b.3 对原有章节的影响一览
|
||||
|
||||
| 原章节 | 旧写法 | v1.5 新原则下的对应 |
|
||||
|--------|--------|----------------------|
|
||||
| §2.4 L2 度量 | L2 既存几何又"派生"L3 内容 | L2 仅做几何 + 路由;不再向 L3 派生内容 |
|
||||
| §2.5 L3 拓扑 | `clip_embedding` 由 voxel 颜色聚合 | 由 L1 keyframe 直接编码 |
|
||||
| §2.7.1 写入顺序图 | `L2 --差异检测--> DELTA` 仍然成立 | 但 L3 节点**内容更新**的箭头改为来自 L1,L2 只贡献节点 uid 路由 |
|
||||
| `06_pipeline_C` §6.6 | TSDF 体素直接重投影生成 patch | 见 [§ 6.6b keyframe-based 内容更新](06_pipeline_C_online_perception.md#66b-v15-新写法keyframe-based-内容更新代替-l2-几何反推) |
|
||||
|
||||
---
|
||||
|
||||
## 2.8 整体存储预算
|
||||
|
||||
以一个 10 房间酒店楼层为例:
|
||||
|
||||
| 层 | 数据 | 大小 |
|
||||
|----|------|------|
|
||||
| L1 | 10 s 环形缓冲(深度+RGB) | ~ 2 GB 内存 |
|
||||
| L2 | 10 房间 × (OctoMap 50 MB + TSDF 100 MB + 3DGS 200 MB) | ~ 3.5 GB 磁盘 |
|
||||
| L3 | 10 节点 + 边 + CLIP 向量 | ~ 100 KB |
|
||||
| L4 | ~ 500 nodes (10 房 × 50 件) + 关系 | ~ 50 MB |
|
||||
| **总** | | **~ 4 GB 磁盘 + 2 GB 内存** |
|
||||
|
||||
→ 完全可以在 Jetson Orin (32–64 GB) 上跑。
|
||||
|
||||
---
|
||||
|
||||
## 2.9 本章小结
|
||||
|
||||
| 层 | 一句话 | 主写者 | 平均寿命 |
|
||||
|----|--------|--------|----------|
|
||||
| **L1** 感知缓冲 | 最近几秒的位姿+深度 | ZED 2i | 10 秒 |
|
||||
| **L2** 度量 | 3D 几何(导航/渲染用) | iPhone 静态 + ZED 增量 | 月级 |
|
||||
| **L3** 拓扑 | 房间-走廊连通图 | iPhone | 永久 |
|
||||
| **L4** 语义 | 物品-关系场景图 | iPhone 大件 + ZED+VLM 小件 | 小时~季度 |
|
||||
|
||||
四层不是平行的,而是**沿"频率×抽象"轴展开的连续谱**。
|
||||
|
||||
读完本章你应能:
|
||||
- ✅ 解释每一层"是什么"
|
||||
- ✅ 解释每一层"由谁主写、谁主读"
|
||||
- ✅ 理解层间写入冲突的仲裁规则
|
||||
- ✅ 估算一个 10 房间场景的存储预算
|
||||
|
||||
下一章 [`03_data_schema.md`](03_data_schema.md) 给出可直接复制运行的 Python `dataclass` schema + 序列化格式。
|
||||
|
||||
---
|
||||
|
||||
**章节版本**:v1.0
|
||||
**估计阅读时间**:18 分钟
|
||||
**关键收获**:四层结构的"存什么/谁写谁读/何时过期"完整规则
|
||||
Reference in New Issue
Block a user