feat(tiling): CAD 实测标定 R_text/R_line + 瓦片粒度内容过滤 + 默认参数定稿
Stage B2(CAD 实测标定)与收尾:
- 新增 verify_text_resolution.py:临时加字(字高 200 图形单位 = 1:100 纸面 2mm)+
平行线对,按目标像素换算 zoom 窗口 → 截图 → 问 VLM;测完 delete_entities 清理不保存。
- 标定结论:
* **可读下限与字体强相关**——离线合成(Consolas 抗锯齿填充字体)12px;
真实 CAD 单线体 .shx(描边字体)6px 即可读对(3/3,两轮)。
* 线条「1 条 vs 2 条」:2/3/4px 失败,5/8/12px 成功 → R_line ≈ 5px。
* **必须用 ACI 7 白字**:实测红字(ACI 1)12px 因对比度不足完全读不出。
- config 定稿:TEXT_READABLE_PX=8(折中)、LINE_READABLE_PX=6、
EVIDENCE_MAX_IMAGES=12(原 5 会把规划结果截到只剩 4 张瓦片)。
- 新增 heatmap.occupied_boxes:占用格的**世界 bbox**。
- tiling.plan_regions 新增 keep 谓词;evidence._plan_tiles 按占用格剔除**空瓦片**
(簇 bbox 是包围盒,L 形/斜向分布含大片空白,实测有效)。
- 测试:test_heatmap 补 occupied_boxes;test_tiling 补 keep 过滤;全量 286 项全绿。
- 真实图纸端到端验证:A0 图框出 12 张(1 整体 + 11 瓦片),单框审查 1m38s 通过。
- 记忆库同步:工具语义坑(type_filter 必须传列表 / layer_filter 是通配字符串 /
add_* 返回句柄在 created 键)、标定结论与默认值。
This commit is contained in:
1 parent
cba05b0c65
commit
9c4ab62dc1
9 files changed
+454
-11
No files matched your search
+11
-8
@@ -124,8 +124,9 @@ IMAGE_JPEG_QUALITY = int(os.environ.get("IMAGE_JPEG_QUALITY", "80"))
|
||||
# 取证阶段每图框的局部截图数(0=仅整体;4=整体+2x2 象限)
|
||||
# —— 仅在自适应瓦片规划不可用时作为**回退**路径参数
|
||||
EVIDENCE_DETAIL_TILES = int(os.environ.get("EVIDENCE_DETAIL_TILES", "4"))
|
||||
# 每图框图片硬上限(整体 + 局部总张数)
|
||||
EVIDENCE_MAX_IMAGES = int(os.environ.get("EVIDENCE_MAX_IMAGES", "5"))
|
||||
# 每图框图片硬上限(整体 + 局部总张数)。
|
||||
# 默认 12 才能装下规划器给出的瓦片(实测测试图 A0 需 12 张);调小会截断规划结果。
|
||||
EVIDENCE_MAX_IMAGES = int(os.environ.get("EVIDENCE_MAX_IMAGES", "12"))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 自适应瓦片规划(按实体分布决定截几张、截哪里)
|
||||
@@ -134,12 +135,14 @@ EVIDENCE_MAX_IMAGES = int(os.environ.get("EVIDENCE_MAX_IMAGES", "5"))
|
||||
TILING_ENABLED = _read_bool("TILING_ENABLED", True)
|
||||
# 纸面字高(mm)。国标标注字高 2~3mm,取保守下限
|
||||
TEXT_HEIGHT_MM = float(os.environ.get("TEXT_HEIGHT_MM", "2.0"))
|
||||
# VLM 可读的**最小像素字高**。
|
||||
# 离线标定(verify_vlm_resolution_offline.py,repeat=2):黑底 12px / 白底 10px 全行正确。
|
||||
# CAD 模型空间默认黑底 → 取 12;离线合成图偏干净,故以 B2 CAD 实测为最终依据。
|
||||
TEXT_READABLE_PX = int(os.environ.get("TEXT_READABLE_PX", "12"))
|
||||
# VLM 可分辨的**最小线间距像素**。
|
||||
# 同上标定:4~5px 有噪声、≥6px 稳定(1 条 vs 2 条二选一)。取 6 保守。
|
||||
# VLM 可读的**最小像素字高**。实测与**字体强相关**:
|
||||
# - 离线合成(Consolas 抗锯齿填充字体)12px(白底 10px);
|
||||
# - 真实 CAD 单线体 .shx(描边字体)6px 即可读对(3/3,两轮)。
|
||||
# 取 8 折中:兼顾描边与常见 TrueType。标定脚本见 verify_vlm_resolution_offline.py /
|
||||
# verify_text_resolution.py(后者须用 ACI 7 白字——红字 12px 因对比度不足完全读不出)。
|
||||
TEXT_READABLE_PX = int(os.environ.get("TEXT_READABLE_PX", "8"))
|
||||
# VLM 可分辨的**最小线间距像素**(1 条 vs 2 条二选一)。
|
||||
# 实测 2/3/4px 失败、5/8/12px 成功;取 6 留一点余量。
|
||||
LINE_READABLE_PX = int(os.environ.get("LINE_READABLE_PX", "6"))
|
||||
# 单图框瓦片数硬上界(成本兜底)
|
||||
TILE_MAX_COUNT = int(os.environ.get("TILE_MAX_COUNT", "36"))
|
||||
|
||||
@@ -246,6 +246,15 @@ def _cell_box(grid, r, c):
|
||||
return (x1, y_top - ch, x1 + cw, y_top)
|
||||
|
||||
|
||||
def occupied_boxes(grid, min_count=1):
|
||||
"""占用格的**世界 bbox** 列表。
|
||||
|
||||
供瓦片规划做「内容过滤」:簇 bbox 是**包围盒**,对 L 形/斜向分布会含大片空白,
|
||||
故瓦片切分后还要按这些格子判断该瓦片是否真有内容。
|
||||
"""
|
||||
return [_cell_box(grid, r, c) for r, c in occupied_cells(grid, min_count)]
|
||||
|
||||
|
||||
def grid_to_windows(clusters, margin=0.0):
|
||||
"""把簇 bbox 转成 zoom 窗口列表(保持输入顺序,即实体计数降序)。"""
|
||||
out = []
|
||||
|
||||
@@ -127,10 +127,19 @@ def _plan_tiles(window, frame, boxes, text_boxes, truncated, view_px):
|
||||
return []
|
||||
flags = tiling.cluster_text_flags(clusters, text_boxes)
|
||||
text_idx = {i for i, is_text in enumerate(flags) if is_text}
|
||||
|
||||
# 内容过滤:簇 bbox 是包围盒,L 形/斜向分布会含大片空白 → 按**占用格**剔除空瓦片
|
||||
occ = heatmap.occupied_boxes(grid)
|
||||
|
||||
def _has_content(win):
|
||||
box = (win["min_x"], win["min_y"], win["max_x"], win["max_y"])
|
||||
return any(tiling.boxes_intersect(box, b) for b in occ)
|
||||
|
||||
return tiling.plan_regions(
|
||||
frame, clusters,
|
||||
text_clusters=text_idx, s_text=s_text, s_line=s_line, view_px=eff,
|
||||
max_tiles=config.TILE_MAX_COUNT, overlap=config.TILE_OVERLAP,
|
||||
keep=_has_content,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ def subdivide(box, cols, rows, overlap=0.0, frame_win=None):
|
||||
|
||||
|
||||
def plan_regions(frame, clusters, *, text_clusters=(), s_text=0.0, s_line=0.0,
|
||||
view_px=(0, 0), max_tiles=36, overlap=0.0):
|
||||
view_px=(0, 0), max_tiles=36, overlap=0.0, keep=None):
|
||||
"""把图框内的簇规划成一组 zoom 窗口(自适应瓦片)。
|
||||
|
||||
参数:
|
||||
@@ -246,7 +246,9 @@ def plan_regions(frame, clusters, *, text_clusters=(), s_text=0.0, s_line=0.0,
|
||||
- `s_text`/`s_line`:CAD 图形单位 / 像素(`world_per_px` 的输出);
|
||||
- `view_px` :单张截图的**有效像素** (ew, eh)(`effective_px` 的输出);
|
||||
- `max_tiles` :返回窗口数硬上界(文字簇优先保留);
|
||||
- `overlap` :相邻瓦片重叠比例。
|
||||
- `overlap` :相邻瓦片重叠比例;
|
||||
- `keep` :可选谓词 `f(window) -> bool`,返回 False 的瓦片被丢弃。
|
||||
用于按**占用格**剔除簇 bbox 内的空白瓦片(簇 bbox 对 L 形/斜向分布含大片空白)。
|
||||
|
||||
返回行优先的 window 列表;**无法规划时返回 []**(调用方退回固定策略)。
|
||||
"""
|
||||
@@ -278,6 +280,8 @@ def plan_regions(frame, clusters, *, text_clusters=(), s_text=0.0, s_line=0.0,
|
||||
cols, rows = tiles_for_box(box, cap_w, cap_h, overlap)
|
||||
# 单屏可覆盖 → 1 张(subdivide 仍会按 overlap 外扩并夹到图框)
|
||||
for win in subdivide(box, cols, rows, overlap, frame_win):
|
||||
if keep is not None and not keep(win):
|
||||
continue # 该瓦片落在空白区,不出图
|
||||
regions.append(win)
|
||||
if len(regions) >= max_tiles:
|
||||
break
|
||||
|
||||
Reference in new issue
Block a user