131 lines
4.2 KiB
Python
131 lines
4.2 KiB
Python
"""实测 deepseek-flash 的 grounding:能否给出可用的「放大区域」归一化坐标。
|
||||
|
|
|
|||
|
|
流程:截一张真实图框整体图 → 问 VLM「哪些区域需要放大」→ 解析它给的归一化坐标
|
|||
|
|
→ 映射成 CAD 世界坐标(WCS)→ 按坐标放大截图,供目视验证 grounding 是否可用。
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
python verify_grounding.py [--frames 1] [--out reports/_probe]
|
|||
|
|
|
|||
|
|
前置:CAD 在线(127.0.0.1:8080 监听)、DEEPSEEK_API_KEY 可用。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import base64
|
|||
|
|
import json
|
|||
|
|
import time
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
from dwg_review import config
|
|||
|
|
from dwg_review.findings import extract_json_block
|
|||
|
|
from dwg_review.llm_client import LlmClient, downsample_image_b64
|
|||
|
|
from dwg_review.mcp_client import result_images
|
|||
|
|
from dwg_review.phases import planning
|
|||
|
|
from dwg_review.context import ReviewContext
|
|||
|
|
|
|||
|
|
_PROMPT = (
|
|||
|
|
"这是某工程图纸中一个图框的截图。请判断:图中哪些区域**细节不清、需要放大**才能看清?\n"
|
|||
|
|
"用**归一化坐标**给出想放大的区域:x0,y0,x1,y1 取值 0~1,"
|
|||
|
|
"图片左上角为 (0,0)、右下角为 (1,1)。\n"
|
|||
|
|
"最多给 3 个区域,按重要性排序。\n"
|
|||
|
|
'只输出 JSON,不要解释:{"regions":[{"bbox":[x0,y0,x1,y1],"reason":"..."}]}'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _save(b64, path):
|
|||
|
|
path.write_bytes(base64.b64decode(b64))
|
|||
|
|
return path
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _norm_to_wcs(win, x0, y0, x1, y1):
|
|||
|
|
"""归一化坐标(左上原点)→ WCS window。"""
|
|||
|
|
dx = win["max_x"] - win["min_x"]
|
|||
|
|
dy = win["max_y"] - win["min_y"]
|
|||
|
|
return {
|
|||
|
|
"min_x": win["min_x"] + x0 * dx,
|
|||
|
|
"max_x": win["min_x"] + x1 * dx,
|
|||
|
|
"min_y": win["max_y"] - y1 * dy, # 图片 y 向下,WCS y 向上
|
|||
|
|
"max_y": win["max_y"] - y0 * dy,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main(argv=None):
|
|||
|
|
ap = argparse.ArgumentParser()
|
|||
|
|
ap.add_argument("--frames", type=int, default=1)
|
|||
|
|
ap.add_argument("--out", default="reports/_probe")
|
|||
|
|
args = ap.parse_args(argv)
|
|||
|
|
|
|||
|
|
ctx = ReviewContext.create()
|
|||
|
|
plan = planning.plan(ctx)
|
|||
|
|
frames = plan.get("frames", [])[: args.frames]
|
|||
|
|
if not frames:
|
|||
|
|
print("未识别到图框")
|
|||
|
|
return 1
|
|||
|
|
|
|||
|
|
out_dir = Path(config.PROJECT_ROOT) / args.out
|
|||
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|||
|
|
|
|||
|
|
frame = frames[0]
|
|||
|
|
win = {
|
|||
|
|
k: float(v)
|
|||
|
|
for k, v in zip(("min_x", "min_y", "max_x", "max_y"), frame["bbox"])
|
|||
|
|
}
|
|||
|
|
print("图框 window:", win)
|
|||
|
|
|
|||
|
|
ctx.tools.call("zoom_to_window", dict(win, margin=0.02))
|
|||
|
|
time.sleep(config.RENDER_WAIT)
|
|||
|
|
shot = ctx.tools.call("get_viewport_screenshot", {})
|
|||
|
|
imgs = result_images(shot)
|
|||
|
|
if not imgs:
|
|||
|
|
print("无截图")
|
|||
|
|
return 1
|
|||
|
|
b64, mime = imgs[0]
|
|||
|
|
nb, nm = downsample_image_b64(b64, mime)
|
|||
|
|
print("整体图:", _save(b64, out_dir / "overall.png"))
|
|||
|
|
|
|||
|
|
llm = LlmClient()
|
|||
|
|
print("模型:", llm.model)
|
|||
|
|
messages = [
|
|||
|
|
{"role": "system", "content": "你是资深工程图纸审图助手。"},
|
|||
|
|
{
|
|||
|
|
"role": "user",
|
|||
|
|
"content": [
|
|||
|
|
{"type": "text", "text": _PROMPT},
|
|||
|
|
LlmClient.image_content(nb, nm),
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
resp = llm.chat(messages)
|
|||
|
|
text = resp.choices[0].message.content or ""
|
|||
|
|
print("=== VLM 原始响应 ===")
|
|||
|
|
print(text)
|
|||
|
|
|
|||
|
|
block = extract_json_block(text)
|
|||
|
|
if not block:
|
|||
|
|
print("[X] 未解析出 JSON")
|
|||
|
|
return 1
|
|||
|
|
data = json.loads(block)
|
|||
|
|
regions = data.get("regions") or []
|
|||
|
|
print(f"=== 解析到 {len(regions)} 个区域 ===")
|
|||
|
|
|
|||
|
|
for i, r in enumerate(regions, 1):
|
|||
|
|
bbox = r.get("bbox")
|
|||
|
|
if not (isinstance(bbox, list) and len(bbox) == 4):
|
|||
|
|
print(f" 区域 {i}: bbox 非法 {bbox}")
|
|||
|
|
continue
|
|||
|
|
x0, y0, x1, y1 = (float(v) for v in bbox)
|
|||
|
|
sub = _norm_to_wcs(win, x0, y0, x1, y1)
|
|||
|
|
print(f" 区域 {i}: bbox={bbox} reason={r.get('reason')!r}")
|
|||
|
|
print(f" -> WCS {sub}")
|
|||
|
|
ctx.tools.call("zoom_to_window", dict(sub, margin=0.05))
|
|||
|
|
time.sleep(config.RENDER_WAIT)
|
|||
|
|
s2 = ctx.tools.call("get_viewport_screenshot", {})
|
|||
|
|
im2 = result_images(s2)
|
|||
|
|
if im2:
|
|||
|
|
print(" -> 已保存", _save(im2[0][0], out_dir / f"region_{i}.png"))
|
|||
|
|
|
|||
|
|
print("\n截图目录:", out_dir)
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
raise SystemExit(main())
|