refine the triangle algo
This commit is contained in:
@@ -131,9 +131,9 @@ def detect_triangle_markers(
|
||||
gray_image,
|
||||
orig_gray=None,
|
||||
size_range=(8, 500),
|
||||
max_interior_gray=90,
|
||||
dark_pixel_gray=80,
|
||||
min_dark_ratio=0.70,
|
||||
max_interior_gray=None,
|
||||
dark_pixel_gray=None,
|
||||
min_dark_ratio=None,
|
||||
verbose=True,
|
||||
):
|
||||
# 读取可调参数(缺省值与 config.py 保持一致)
|
||||
@@ -142,10 +142,24 @@ def detect_triangle_markers(
|
||||
early_exit = int(getattr(_cfg, "TRIANGLE_EARLY_EXIT_CANDIDATES", 4))
|
||||
block_sizes = tuple(getattr(_cfg, "TRIANGLE_ADAPTIVE_BLOCK_SIZES", (11, 21, 35)))
|
||||
max_combo_n = int(getattr(_cfg, "TRIANGLE_MAX_FILTERED_FOR_COMBO", 10))
|
||||
if max_interior_gray is None:
|
||||
max_interior_gray = int(getattr(_cfg, "TRIANGLE_MAX_INTERIOR_GRAY", 130))
|
||||
if dark_pixel_gray is None:
|
||||
dark_pixel_gray = int(getattr(_cfg, "TRIANGLE_DARK_PIXEL_GRAY", 130))
|
||||
if min_dark_ratio is None:
|
||||
min_dark_ratio = float(getattr(_cfg, "TRIANGLE_MIN_DARK_RATIO", 0.30))
|
||||
min_contrast_diff = int(getattr(_cfg, "TRIANGLE_MIN_CONTRAST_DIFF", 15))
|
||||
except Exception:
|
||||
early_exit = 4
|
||||
block_sizes = (11, 21, 35)
|
||||
max_combo_n = 10
|
||||
if max_interior_gray is None:
|
||||
max_interior_gray = 130
|
||||
if dark_pixel_gray is None:
|
||||
dark_pixel_gray = 130
|
||||
if min_dark_ratio is None:
|
||||
min_dark_ratio = 0.30
|
||||
min_contrast_diff = 15
|
||||
|
||||
min_leg, max_leg = size_range
|
||||
min_area = 0.5 * (min_leg ** 2) * 0.1
|
||||
@@ -202,23 +216,72 @@ def detect_triangle_markers(
|
||||
return False
|
||||
interior = orig_gray[ys, xs]
|
||||
dark_ratio = float(np.mean(interior <= dark_pixel_gray))
|
||||
return (mean_val <= max_interior_gray) and (dark_ratio >= min_dark_ratio)
|
||||
|
||||
# 条件1:绝对阈值(三角形内部足够暗)
|
||||
abs_ok = (mean_val <= max_interior_gray) and (dark_ratio >= min_dark_ratio)
|
||||
|
||||
# 条件2:相对对比度 — 三角形内部比周围背景明显更暗
|
||||
contrast_ok = False
|
||||
if min_contrast_diff > 0:
|
||||
try:
|
||||
dilate_k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * erode_k + 3, 2 * erode_k + 3))
|
||||
mask_dilated = cv2.dilate(mask, dilate_k, iterations=2)
|
||||
mask_border = cv2.subtract(mask_dilated, mask)
|
||||
border_nz = cv2.countNonZero(mask_border)
|
||||
if border_nz > 20:
|
||||
mean_surround = cv2.mean(orig_gray, mask=mask_border)[0]
|
||||
contrast_ok = (mean_surround - mean_val) >= min_contrast_diff
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return abs_ok or contrast_ok
|
||||
|
||||
def _extract_candidates(binary_img):
|
||||
contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
found = []
|
||||
# ---- 诊断计数 ----
|
||||
_n_area_skip = 0
|
||||
_n_3vert = 0
|
||||
_n_shape_ok = 0
|
||||
_n_color_ok = 0
|
||||
_dbg_fail_shape = [] # 记录前几个失败原因
|
||||
_dbg_fail_color = [] # 记录前几个颜色失败详情
|
||||
for cnt in contours:
|
||||
if cv2.contourArea(cnt) < min_area:
|
||||
area = cv2.contourArea(cnt)
|
||||
if area < min_area:
|
||||
_n_area_skip += 1
|
||||
continue
|
||||
peri = cv2.arcLength(cnt, True)
|
||||
approx = cv2.approxPolyDP(cnt, 0.05 * peri, True)
|
||||
eps = 0.05 * peri if peri > 60 else 0.03 * peri
|
||||
approx = cv2.approxPolyDP(cnt, eps, True)
|
||||
if len(approx) != 3:
|
||||
continue
|
||||
_n_3vert += 1
|
||||
shape = _check_shape(approx)
|
||||
if shape is None:
|
||||
if len(_dbg_fail_shape) < 3:
|
||||
pts3 = approx.reshape(3, 2).astype(np.float32)
|
||||
sides = sorted([np.linalg.norm(pts3[1]-pts3[0]),
|
||||
np.linalg.norm(pts3[2]-pts3[1]),
|
||||
np.linalg.norm(pts3[0]-pts3[2])])
|
||||
avg_l = (sides[0]+sides[1])/2
|
||||
reason = f"avg_leg={avg_l:.1f} range=[{min_leg},{max_leg}] legs={sides[0]:.1f},{sides[1]:.1f} hyp={sides[2]:.1f} exp_hyp={avg_l*1.414:.1f}"
|
||||
_dbg_fail_shape.append(reason)
|
||||
continue
|
||||
_n_shape_ok += 1
|
||||
if not _color_ok(approx):
|
||||
if len(_dbg_fail_color) < 3 and orig_gray is not None:
|
||||
mask = np.zeros(orig_gray.shape[:2], dtype=np.uint8)
|
||||
cv2.fillPoly(mask, [approx], 255)
|
||||
mean_v = cv2.mean(orig_gray, mask=mask)[0]
|
||||
ys, xs = np.where(mask > 0)
|
||||
if len(xs) > 0:
|
||||
dr = float(np.mean(orig_gray[ys, xs] <= dark_pixel_gray))
|
||||
else:
|
||||
dr = 0
|
||||
_dbg_fail_color.append(f"mean={mean_v:.1f}(<={max_interior_gray}?) dark_r={dr:.2f}(>={min_dark_ratio}?)")
|
||||
continue
|
||||
_n_color_ok += 1
|
||||
right_pt, avg_leg, pts = shape
|
||||
center_px = np.mean(pts, axis=0).tolist()
|
||||
dedup_key = f"{int(center_px[0] // 10)},{int(center_px[1] // 10)}"
|
||||
@@ -229,6 +292,13 @@ def detect_triangle_markers(
|
||||
"avg_leg": avg_leg,
|
||||
"dedup_key": dedup_key,
|
||||
})
|
||||
if verbose:
|
||||
_log(f"[TRI] _extract: total={len(contours)} area_skip={_n_area_skip} "
|
||||
f"3vert={_n_3vert} shape_ok={_n_shape_ok} color_ok={_n_color_ok}")
|
||||
if _dbg_fail_shape:
|
||||
_log(f"[TRI] shape失败原因(前3): {'; '.join(_dbg_fail_shape)}")
|
||||
if _dbg_fail_color:
|
||||
_log(f"[TRI] color失败原因(前3): {'; '.join(_dbg_fail_color)}")
|
||||
return found
|
||||
|
||||
all_candidates = []
|
||||
@@ -264,6 +334,15 @@ def detect_triangle_markers(
|
||||
|
||||
# 1. 最快:全局 Otsu(无需逐像素邻域计算,~10ms)
|
||||
_, b_otsu = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
||||
# ---- 临时调试:保存 Otsu 二值图供人工检查 ----
|
||||
try:
|
||||
import config as _dbg_cfg
|
||||
if getattr(_dbg_cfg, 'TRIANGLE_SAVE_DEBUG_IMAGE', False):
|
||||
_dbg_path = getattr(_dbg_cfg, 'PHOTO_DIR', '/root/phot') + '/tri_otsu_debug.jpg'
|
||||
cv2.imwrite(_dbg_path, b_otsu)
|
||||
_log(f"[TRI] DEBUG: Otsu 二值图已保存到 {_dbg_path}")
|
||||
except Exception:
|
||||
pass
|
||||
_add_from_binary(b_otsu)
|
||||
|
||||
# 2. 只在 Otsu 不够时才跑自适应阈值(每次 ~100ms,尽早退出)
|
||||
@@ -414,7 +493,7 @@ def try_triangle_scoring(
|
||||
|
||||
# 缩图加速:嵌入式 CPU 上图像处理耗时与面积成正比,缩到最长边 320px 可获得 ~4× 加速
|
||||
# 检测完后把像素坐标乘以 inv_scale 还原到原始分辨率,再送入单应性/PnP(与 K 标定分辨率一致)
|
||||
MAX_DETECT_DIM = 320
|
||||
MAX_DETECT_DIM = 640
|
||||
long_side = max(h_orig, w_orig)
|
||||
if long_side > MAX_DETECT_DIM:
|
||||
det_scale = MAX_DETECT_DIM / long_side
|
||||
|
||||
Reference in New Issue
Block a user