在你的框架里,portal 不是"凭空造一个概念" ,而是从你已经有的地图/拓扑/语义里自动抽出来的"通往未知的入口点"。最常用、最稳的生成方式有 3 条主线(你可以同时用,互相校验):
1) 从 2D 占据栅格的 Frontier 生成(最推荐、最通用)
这是最标准的:portal ≈ frontier cluster 的代表点。
步骤
-
Frontier 检测
在 2D occupancy grid 上找满足条件的格子:
-
当前格子是 free
-
邻域里存在 unknown
这就是 frontier cell。
-
Frontier 聚类(cluster)
对 frontier cells 做连通域聚类(4/8 邻接都行),得到多个簇
C_i。每个簇基本对应一个"未知方向的入口边界"。 -
为每个 cluster 选一个 portal anchor(代表点)
常见选法:
-
centroid:簇的几何中心(简单) -
best-reachable:簇内距离机器人最近且 A* 可达的点(更稳) -
max-visibility:在簇附近采样几个候选位姿,选"能看到更多未知"的那个(更像 IG)
-
估计 portal 的朝向 normal / heading(关键)
你需要知道"未知大概在 portal 的哪一侧",做法:
-
取代表点
p周围一个小环带,统计 unknown cells 的方向分布 -
unknown 多的方向就是 portal 的法向
n(heading)
-
估计 portal 宽度(可选但很有用)
沿着与
n垂直的方向,在 frontier cluster 上测量长度(格子数量×分辨率),得到width。用途:过滤"太窄不可通行"的 portal;或者区分"门洞 vs 大开口"。
生成到 scene graph 的形式
-
新建节点
Portal_i(type=portal) -
属性:
position(x,y,z),heading(yaw),width,cluster_size,confidence -
连边:
-
Portal_i --attached_to--> Place/Region_j(它属于哪个已探索区域) -
Portal_i --leads_to--> UnknownRegion_i(后面未知)
-
你现在的
target_points_node.cpp已经做了 frontier 检测/可见性/目标点选择,这条链路通常最容易直接复用。
2) 从拓扑 Place Graph / Scene Graph 的"边界节点"生成(与你的 GraphDecoder 很搭)
如果你有 places(可行走节点)和它们的连边:
思路
-
在 place graph 里,找那些靠近未知边界的 place(boundary place)
-
对 boundary places 做聚类/合并
-
每一簇生成一个 portal
怎么判断 place 是否 boundary?
对每个 place 节点 v:
-
在它附近(半径 r)的 occupancy grid 上统计 unknown 比例
-
unknown 比例 > 阈值 →
v是 boundary candidate
优势:portal 天然和你的拓扑图绑定,后续做路径规划/代价估计很顺。
3) 从语义结构生成(Door / Opening)(增强但别单独依赖)
如果你的 scene graph 里已经有 door / opening 之类结构节点:
做法
-
对每个 door 节点:
-
用其 3D bbox/中心作为 portal position
-
朝向用门框平面法向(或用两侧 free/unknown 分布估计)
-
-
然后用 occupancy/frontier 验证:门的一侧是否真的通向未知或未探索区域
- 如果门两侧都已探索,那它不是 portal(只是室内连接)
优势:更"语义正确",尤其在房间级规划时很好用。
坑:语义门检测不稳时会漏/错,所以最好与 frontier-based 互补。
推荐你用的"组合策略"(最稳)
主干用 Frontier → Portal(方案 1),再用 door/拓扑做增强:
-
Frontier 聚类生成
Portal_frontier -
door 节点生成
Portal_door -
空间上做合并(2D/3D 距离 < ε 且朝向相近):
-
如果 door portal 和 frontier portal 重合 → 提升置信度
-
如果 door portal 不对应 frontier → 先降权(可能是假门/已探索门)
-
-
输出 portal 集合给 scene graph
你可以直接照抄的伪代码(偏工程实现)
portals = []
frontiers = detectFrontier(occupancy)
clusters = clusterFrontier(frontiers)
for each cluster Ci:
p = chooseAnchor(Ci, mode="reachable_nearest") // centroid / nearest reachable / best visibility
n = estimateNormalFromUnknown(occupancy, p) // direction to unknown
w = estimateWidth(Ci, n, resolution)
if w < min_width: continue
portal = Portal(
pos = gridToWorld(p),
yaw = atan2(n.y, n.x),
width = w,
confidence = f(cluster_size, w)
)
portals.push(portal)
door_portals = portalsFromDoors(scene_graph_doors)
portals = mergeAndValidate(portals, door_portals, occupancy)
attachPortalsToGraph(portals, place_graph_or_regions)
Scene graph 里"Portal 节点"的推荐字段
(你后面要做 IG、代价、补全,这些字段很关键)
-
id -
type = portal -
pos (x,y,z) -
yaw(通往未知的朝向) -
width -
source = {frontier, door, place_boundary} -
confidence -
connected_region_id(它属于哪个已探索 room/region) -
unknown_region_id(它指向哪个 UnknownRegion)
如果你告诉我你现在的地图表示是哪一种最方便(2D occupancy/frontier 已有 、还是 place graph 已有 、还是 door 节点已检测 ),我可以按你现有代码结构(比如你 target_points_node.cpp 的 frontier 输出格式)给你一份更贴你工程的 C++/ROS 接口设计:订阅哪些 topic、portal message 怎么定义、在哪一层插入 scene graph。