This commit is contained in:
HuangHai
2026-01-14 10:58:11 +08:00
parent 7549a5f038
commit 11e1091c6a
5 changed files with 163 additions and 77 deletions

View File

@@ -245,12 +245,14 @@ class ReadImageKit:
cv_bboxes = detect_cards_cv(image_path, top_ratio=SAFE_EXCLUDE_RATIO, bottom_ratio=BOTTOM_SAFE_EXCLUDE_RATIO)
if cv_bboxes:
draw_rectangles(image_path, bboxes=cv_bboxes)
img = read_image(image_path)
h, w = img.shape[:2] if img is not None else (2400, 1080)
y_threshold = h * 0.58
# 在最终绘制阶段再生成可视化,避免早期绘制产生误导
prompt = f"""
图片中已经用绿色矩形框标记了 {len(cv_bboxes)} 个可能的充电站卡片。
请按从上到下的顺序,识别每个绿色框内的场站信息。
图片下半部分可能包含多个充电站卡片,请按从上到下识别这些卡片的名称与地址。
输出格式为 JSON 数组,长度必须为 {len(cv_bboxes)}。
输出格式为 JSON 数组。
每个对象包含:
- "name": 场站名称
- "address": 场站地址
@@ -281,23 +283,51 @@ class ReadImageKit:
img = read_image(image_path)
h, w = img.shape[:2] if img is not None else (2400, 1080)
y_threshold = h * 0.58
for i, res in enumerate(vlm_results):
if i < len(cv_bboxes):
bbox = cv_bboxes[i]
# 物理坐标硬过滤:即使 CV 识别到了,y 坐标在 58% 以上的也统统干掉
center_y = (bbox[1] + bbox[3]) // 2
if center_y < y_threshold:
logger.warning(f"CV 误报过滤 (y={center_y} < {y_threshold}): 可能是顶部营销卡片")
continue
if res and (res.get("is_valid") is True or (res.get("name") and res.get("is_valid") is not False)):
final_stations.append({
"name": res.get("name"),
"address": res.get("address"),
"point": [(bbox[0] + bbox[2]) // 2, (bbox[1] + bbox[3]) // 2],
"bbox": bbox
})
bottom_threshold = int(h * (1 - BOTTOM_SAFE_EXCLUDE_RATIO))
use_bboxes = []
for b in cv_bboxes:
cy = (b[1] + b[3]) // 2
if cy >= y_threshold:
use_bboxes.append(b)
for res in vlm_results:
if not res:
continue
p = res.get("point")
actual_p = None
if p and len(p) == 2:
actual_p = p
if p[0] <= 1000 and p[1] <= 1000:
actual_p = [int(p[0] * w / 1000), int(p[1] * h / 1000)]
# 顶部与底部安全过滤
if actual_p and (actual_p[1] < y_threshold or actual_p[1] > bottom_threshold):
logger.warning(f"过滤掉可能的误点击点 (y={actual_p[1]} < {y_threshold}): {res.get('name')}")
continue
matched_bbox = None
if actual_p:
for b in use_bboxes:
if b[0] <= actual_p[0] <= b[2] and b[1] <= actual_p[1] <= b[3]:
matched_bbox = b
break
# 如果没有匹配到 CV 框,则根据点击点生成一个虚拟框
if matched_bbox is None and actual_p is not None:
half_w = int(w * 0.4)
half_h = 90
x1 = max(0, actual_p[0] - half_w)
x2 = min(w, actual_p[0] + half_w)
y1 = max(0, actual_p[1] - half_h)
y2 = min(h, actual_p[1] + half_h)
matched_bbox = [x1, y1, x2, y2]
# 如果既没有 point 也没有匹配框,跳过
if actual_p is None and matched_bbox is None:
continue
if actual_p is None and matched_bbox:
actual_p = [(matched_bbox[0] + matched_bbox[2]) // 2, (matched_bbox[1] + matched_bbox[3]) // 2]
final_stations.append({
"name": res.get("name"),
"address": res.get("address"),
"point": actual_p,
"bbox": matched_bbox
})
elif not cv_bboxes:
vlm_list = vlm_results if isinstance(vlm_results, list) else []
w, h = 0, 0