所有
This commit is contained in:
@@ -22,6 +22,143 @@ def _log(msg):
|
||||
pass
|
||||
|
||||
|
||||
def _read_triangle_direction_cfg():
|
||||
"""读取 config 中三角形方向/中心距校验参数。"""
|
||||
try:
|
||||
import config as cfg
|
||||
return {
|
||||
"enable": bool(getattr(cfg, "TRIANGLE_DIRECTION_VALIDATE_ENABLE", True)),
|
||||
"min_pass": int(getattr(cfg, "TRIANGLE_DIRECTION_MIN_PASS", 3)),
|
||||
"dot_min": float(getattr(cfg, "TRIANGLE_DIRECTION_DOT_MIN", 0.0)),
|
||||
"to_center_dot_min": float(
|
||||
getattr(cfg, "TRIANGLE_DIRECTION_TO_CENTER_DOT_MIN", 0.35)
|
||||
),
|
||||
"center_dist_enable": bool(
|
||||
getattr(cfg, "TRIANGLE_CENTER_DISTANCE_VALIDATE_ENABLE", True)
|
||||
),
|
||||
"center_dist_tol": float(
|
||||
getattr(cfg, "TRIANGLE_CENTER_DISTANCE_RATIO_TOL", 0.45)
|
||||
),
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"enable": True,
|
||||
"min_pass": 3,
|
||||
"dot_min": 0.0,
|
||||
"to_center_dot_min": 0.35,
|
||||
"center_dist_enable": True,
|
||||
"center_dist_tol": 0.45,
|
||||
}
|
||||
|
||||
|
||||
def _quad_combo_orient_penalty(cands_4):
|
||||
"""
|
||||
四点组合评分用的方向惩罚(原 _score_quad 内 orient_pen 逻辑)。
|
||||
TRIANGLE_DIRECTION_VALIDATE_ENABLE=False 时调用方应跳过(不加罚)。
|
||||
"""
|
||||
orient_pen = 0.0
|
||||
orient_vote = []
|
||||
for c in cands_4:
|
||||
cen = np.array(c["center_px"], dtype=np.float32)
|
||||
rpt = np.array(c["right_pt"], dtype=np.float32)
|
||||
vx = float(cen[0] - rpt[0])
|
||||
vy = float(cen[1] - rpt[1])
|
||||
if abs(vx) < 1e-6 or abs(vy) < 1e-6:
|
||||
orient_pen += 1.0
|
||||
orient_vote.append(None)
|
||||
continue
|
||||
if abs(vx) < abs(vy) * 0.15 or abs(vy) < abs(vx) * 0.15:
|
||||
orient_pen += 0.5
|
||||
if vx > 0 and vy > 0:
|
||||
orient_vote.append(0)
|
||||
elif vx < 0 and vy > 0:
|
||||
orient_vote.append(1)
|
||||
elif vx > 0 and vy < 0:
|
||||
orient_vote.append(2)
|
||||
else:
|
||||
orient_vote.append(3)
|
||||
valid_votes = [v for v in orient_vote if v is not None]
|
||||
if valid_votes:
|
||||
from collections import Counter
|
||||
vc = Counter(valid_votes)
|
||||
orient_pen += max(0, max(vc.values()) - 1) * 0.8
|
||||
return orient_pen
|
||||
|
||||
|
||||
def _marker_inward_unit(marker):
|
||||
"""从直角顶点指向三角内部的单位向量;marker['center'] 为直角顶点。"""
|
||||
right = np.array(marker["center"], dtype=np.float64)
|
||||
corners = marker.get("corners")
|
||||
if not corners or len(corners) < 3:
|
||||
return None
|
||||
cen = np.mean(np.array(corners, dtype=np.float64), axis=0)
|
||||
inv = cen - right
|
||||
n = float(np.linalg.norm(inv))
|
||||
if n < 1e-6:
|
||||
return None
|
||||
return inv / n
|
||||
|
||||
|
||||
def _validate_triangle_direction(marker_centers, tri_markers, cfg):
|
||||
"""
|
||||
校验:四角到候选靶心距离近似一致;各真实黑三角朝向靶心。
|
||||
仅统计 tri_markers 中真实检出的角(不含几何补全的虚拟点)。
|
||||
Returns:
|
||||
(ok: bool, reason: str)
|
||||
"""
|
||||
if not cfg.get("enable", True):
|
||||
return True, ""
|
||||
|
||||
pts = np.array(marker_centers, dtype=np.float64).reshape(-1, 2)
|
||||
if len(pts) < 3:
|
||||
return True, ""
|
||||
|
||||
quad_center = np.mean(pts, axis=0)
|
||||
|
||||
if cfg.get("center_dist_enable", True) and len(pts) >= 3:
|
||||
dists = np.linalg.norm(pts - quad_center, axis=1)
|
||||
mean_d = float(np.mean(dists))
|
||||
if mean_d > 1e-6:
|
||||
ratio = (float(np.max(dists)) - float(np.min(dists))) / mean_d
|
||||
tol = float(cfg.get("center_dist_tol", 0.45))
|
||||
if ratio > tol:
|
||||
return False, f"center_dist_ratio={ratio:.2f}>{tol:.2f}"
|
||||
|
||||
dot_need = max(
|
||||
float(cfg.get("dot_min", 0.0)),
|
||||
float(cfg.get("to_center_dot_min", 0.35)),
|
||||
)
|
||||
pass_n = 0
|
||||
check_n = 0
|
||||
for m in tri_markers or []:
|
||||
if m.get("center") is None:
|
||||
continue
|
||||
check_n += 1
|
||||
right = np.array(m["center"], dtype=np.float64)
|
||||
to_center = quad_center - right
|
||||
nc = float(np.linalg.norm(to_center))
|
||||
if nc < 1e-6:
|
||||
continue
|
||||
inward = _marker_inward_unit(m)
|
||||
if inward is None:
|
||||
continue
|
||||
dot_tc = float(np.dot(inward, to_center / nc))
|
||||
if dot_tc >= dot_need:
|
||||
pass_n += 1
|
||||
|
||||
if check_n == 0:
|
||||
return True, ""
|
||||
|
||||
min_pass = int(cfg.get("min_pass", 3))
|
||||
min_pass = max(1, min(min_pass, check_n))
|
||||
if pass_n < min_pass:
|
||||
return False, (
|
||||
f"direction_pass={pass_n}/{check_n} need>={min_pass} "
|
||||
f"(dot>={dot_need:.2f})"
|
||||
)
|
||||
return True, ""
|
||||
|
||||
|
||||
def _gray_suppress_bright_by_v(img_rgb, v_above: int):
|
||||
"""
|
||||
RGB 输入:在 HSV 的 V 上,将亮度 >= v_above 的像素灰度置为 255。
|
||||
@@ -622,6 +759,8 @@ def detect_triangle_markers(
|
||||
bot_pair = sorted(by_y[2:], key=lambda i: pts_4[i][0])
|
||||
return top_pair[0], bot_pair[0], bot_pair[1], top_pair[1]
|
||||
|
||||
_dir_cfg_combo = _read_triangle_direction_cfg()
|
||||
|
||||
def _score_quad(cands_4):
|
||||
pts = [np.array(c["center_px"]) for c in cands_4]
|
||||
legs = [c["avg_leg"] for c in cands_4]
|
||||
@@ -641,38 +780,11 @@ def detect_triangle_markers(
|
||||
med_l = float(np.median(legs))
|
||||
leg_dev = max(abs(l - med_l) / (med_l + 1e-6) for l in legs)
|
||||
|
||||
# 方向一致性:四个标靶角的直角顶点朝向应符合大致的四象限布局。
|
||||
# 对于相邻标靶很近的情况,这个方向信息能显著减少“把同一方向的两个三角混成一组”的误判。
|
||||
orient_pen = 0.0
|
||||
orient_vote = []
|
||||
for c in cands_4:
|
||||
cen = np.array(c["center_px"], dtype=np.float32)
|
||||
rpt = np.array(c["right_pt"], dtype=np.float32)
|
||||
vx = float(cen[0] - rpt[0])
|
||||
vy = float(cen[1] - rpt[1])
|
||||
# 方向太接近轴线时,说明顶点朝向不稳定,增加惩罚
|
||||
if abs(vx) < 1e-6 or abs(vy) < 1e-6:
|
||||
orient_pen += 1.0
|
||||
orient_vote.append(None)
|
||||
continue
|
||||
if abs(vx) < abs(vy) * 0.15 or abs(vy) < abs(vx) * 0.15:
|
||||
orient_pen += 0.5
|
||||
# 以中心相对右角顶点的方向做粗分类:TL=向右下,TR=向左下,BL=向右上,BR=向左上
|
||||
if vx > 0 and vy > 0:
|
||||
orient_vote.append(0)
|
||||
elif vx < 0 and vy > 0:
|
||||
orient_vote.append(1)
|
||||
elif vx > 0 and vy < 0:
|
||||
orient_vote.append(2)
|
||||
else:
|
||||
orient_vote.append(3)
|
||||
|
||||
# 如果 4 个候选的方向落点本身就重复很多,说明可能混入了别的靶标角,直接加罚。
|
||||
valid_votes = [v for v in orient_vote if v is not None]
|
||||
if valid_votes:
|
||||
from collections import Counter
|
||||
vc = Counter(valid_votes)
|
||||
orient_pen += max(0, max(vc.values()) - 1) * 0.8
|
||||
orient_pen = (
|
||||
_quad_combo_orient_penalty(cands_4)
|
||||
if _dir_cfg_combo.get("enable", True)
|
||||
else 0.0
|
||||
)
|
||||
|
||||
score = (diag_ratio - 1.0) * 3.0 + (h_ratio - 1.0) + (v_ratio - 1.0) + leg_dev * 2.0 + orient_pen
|
||||
return score, (tl, bl, br, tr)
|
||||
@@ -965,6 +1077,8 @@ def _assign_marker_ids_from_filtered(filtered, verbose=True):
|
||||
bot_pair = sorted(by_y[2:], key=lambda i: pts_4[i][0])
|
||||
return top_pair[0], bot_pair[0], bot_pair[1], top_pair[1]
|
||||
|
||||
_dir_cfg_combo = _read_triangle_direction_cfg()
|
||||
|
||||
def _score_quad(cands_4):
|
||||
pts = [np.array(c["center_px"]) for c in cands_4]
|
||||
legs = [c["avg_leg"] for c in cands_4]
|
||||
@@ -980,32 +1094,11 @@ def _assign_marker_ids_from_filtered(filtered, verbose=True):
|
||||
v_ratio = max(s_left, s_right) / (min(s_left, s_right) + 1e-6)
|
||||
med_l = float(np.median(legs))
|
||||
leg_dev = max(abs(l - med_l) / (med_l + 1e-6) for l in legs)
|
||||
orient_pen = 0.0
|
||||
orient_vote = []
|
||||
for c in cands_4:
|
||||
cen = np.array(c["center_px"], dtype=np.float32)
|
||||
rpt = np.array(c["right_pt"], dtype=np.float32)
|
||||
vx = float(cen[0] - rpt[0])
|
||||
vy = float(cen[1] - rpt[1])
|
||||
if abs(vx) < 1e-6 or abs(vy) < 1e-6:
|
||||
orient_pen += 1.0
|
||||
orient_vote.append(None)
|
||||
continue
|
||||
if abs(vx) < abs(vy) * 0.15 or abs(vy) < abs(vx) * 0.15:
|
||||
orient_pen += 0.5
|
||||
if vx > 0 and vy > 0:
|
||||
orient_vote.append(0)
|
||||
elif vx < 0 and vy > 0:
|
||||
orient_vote.append(1)
|
||||
elif vx > 0 and vy < 0:
|
||||
orient_vote.append(2)
|
||||
else:
|
||||
orient_vote.append(3)
|
||||
valid_votes = [v for v in orient_vote if v is not None]
|
||||
if valid_votes:
|
||||
from collections import Counter
|
||||
vc = Counter(valid_votes)
|
||||
orient_pen += max(0, max(vc.values()) - 1) * 0.8
|
||||
orient_pen = (
|
||||
_quad_combo_orient_penalty(cands_4)
|
||||
if _dir_cfg_combo.get("enable", True)
|
||||
else 0.0
|
||||
)
|
||||
score = (diag_ratio - 1.0) * 3.0 + (h_ratio - 1.0) + (v_ratio - 1.0) + leg_dev * 2.0 + orient_pen
|
||||
return score, (tl, bl, br, tr)
|
||||
|
||||
@@ -1792,6 +1885,21 @@ def try_triangle_scoring(
|
||||
"is_virtual": bool(_is_virtual),
|
||||
})
|
||||
|
||||
# ---------- 方向 / 中心距校验(config.TRIANGLE_DIRECTION_*) ----------
|
||||
_dir_cfg = _read_triangle_direction_cfg()
|
||||
_dir_ok, _dir_reason = _validate_triangle_direction(
|
||||
marker_centers, tri_markers, _dir_cfg
|
||||
)
|
||||
if not _dir_ok:
|
||||
_log(f"[TRI] 方向校验失败: {_dir_reason}")
|
||||
if _try_timing_log:
|
||||
_log(
|
||||
f"[TRI] timing_ms(try_triangle): {_tri_yolo_part} "
|
||||
f"geometry={(time.perf_counter() - _t_seg) * 1000:.1f} "
|
||||
f"total_try={(time.perf_counter() - _t_try0) * 1000:.1f} (方向校验失败)"
|
||||
)
|
||||
return out
|
||||
|
||||
# ---------- 结果有效性校验(防 nan/inf 与退化角点) ----------
|
||||
try:
|
||||
import config as _cfg
|
||||
|
||||
Reference in New Issue
Block a user