feat: 2.18.10 靶规格复检与置信度门槛
- 检出靶框裁剪放大后二次 detect 复检(conf 0.25),以复检结果为准 - 全图结果上报门槛 MIN_REPORT_CONF=0.5,复检结果 >0.25 即采纳 - 0 结果时按 RETRY_CONF_TH 自动重检;class_id=1 解析阶段过滤 - 换用 model_326162,去除调试 print 改走 logger
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
id: t11
|
||||
name: t11
|
||||
version: 2.18.9
|
||||
version: 2.18.10
|
||||
author: t11
|
||||
icon: ''
|
||||
desc: t11
|
||||
@@ -18,8 +18,8 @@ files:
|
||||
- laser_manager.py
|
||||
- logger_manager.py
|
||||
- main.py
|
||||
- model_325964.cvimodel
|
||||
- model_325964.mud
|
||||
- model_326162.cvimodel
|
||||
- model_326162.mud
|
||||
- network.py
|
||||
- ota_curl.sh
|
||||
- ota_manager.py
|
||||
|
||||
@@ -264,7 +264,7 @@ TRIANGLE_YOLO_PRELOAD_ON_BOOT = False
|
||||
|
||||
# YOLO target size classification: class_id → label,下标即 class_id:0=40cm, 1=0(靶心), 2=20cm.
|
||||
TARGET_CLASS_YOLO_ENABLE = True
|
||||
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_325964.mud"
|
||||
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_326162.mud"
|
||||
TARGET_CLASS_YOLO_LABELS = (40, 0, 20)
|
||||
# 只有映射结果落在该集合内才用于上报(如 class 1→label 0 直接忽略)
|
||||
TARGET_CLASS_YOLO_VALID_LABELS = (20, 40)
|
||||
@@ -273,6 +273,13 @@ TARGET_CLASS_YOLO_IGNORE_CLASS_IDS = (1,)
|
||||
TARGET_CLASS_YOLO_CONF_TH = 0.5
|
||||
# 首次检测 0 结果时用该阈值重新检测一次(≥CONF_TH 时按 CONF_TH 同参重跑)
|
||||
TARGET_CLASS_YOLO_RETRY_CONF_TH = 0.25
|
||||
# 检出靶框后裁剪放大再 detect 一次,与全图结果互相印证(不一致以复检为准)
|
||||
TARGET_CLASS_YOLO_RECHECK_CROP = True
|
||||
TARGET_CLASS_YOLO_RECHECK_MARGIN_FRAC = 0.1
|
||||
# 复检的 detect 阈值与接受门槛:置信度大于该值即采纳复检结果
|
||||
TARGET_CLASS_YOLO_RECHECK_CONF_TH = 0.25
|
||||
# 全图结果(无复检时)的上报置信度门槛,None/0 表示不启用
|
||||
TARGET_CLASS_YOLO_MIN_REPORT_CONF = 0.5
|
||||
TARGET_CLASS_YOLO_IOU_TH = 0.45
|
||||
TARGET_CLASS_YOLO_PRELOAD_ON_BOOT = True
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
|
||||
[basic]
|
||||
type = cvimodel
|
||||
model = model_325964.cvimodel
|
||||
model = model_326162.cvimodel
|
||||
|
||||
[extra]
|
||||
model_type = yolov5
|
||||
+141
-33
@@ -312,7 +312,8 @@ def _det_obj_score(o):
|
||||
def try_get_target_class_from_yolo(maix_frame, logger=None):
|
||||
"""调用 YOLO 检测:class_id∈IGNORE_CLASS_IDS(默认 1) 的框在解析时直接过滤;
|
||||
detections 输出其余全部框(不按置信度过滤);
|
||||
上报字段 label/class_id/confidence 仅在有效靶规格(20/40)中取置信度最高的一条。"""
|
||||
上报以「检出框裁剪放大后的复检结果」为主,复检不可用时用全图结果;
|
||||
有效靶规格(20/40)中取置信度最高的一条,且低于 MIN_REPORT_CONF 不上报类别。"""
|
||||
try:
|
||||
import config as cfg
|
||||
except Exception:
|
||||
@@ -365,7 +366,6 @@ def try_get_target_class_from_yolo(maix_frame, logger=None):
|
||||
try:
|
||||
return detector.detect(maix_frame, conf_th=threshold, iou_th=iou_th)
|
||||
except Exception as exc:
|
||||
print(f"[YOLO-TARGET] detect 异常: {exc}")
|
||||
if logger:
|
||||
logger.warning(f"[YOLO-TARGET] detect 异常: {exc}")
|
||||
return None
|
||||
@@ -408,57 +408,165 @@ def try_get_target_class_from_yolo(maix_frame, logger=None):
|
||||
# 第 1 次检测
|
||||
raw = _run(conf_th)
|
||||
detections = _parse(raw)
|
||||
print(f"[YOLO-TARGET] 第1次 conf_th={conf_th} iou_th={iou_th} raw={raw!r}")
|
||||
print(
|
||||
f"[YOLO-TARGET] 检测结果={detections}"
|
||||
f"(已忽略 class_id∈{tuple(sorted(ignore_class_ids))} 共{n_ignored[0]} 条)"
|
||||
)
|
||||
|
||||
# 0 结果时重新检测一次(阈值取更小值,仍为 0 则同参数重跑)
|
||||
if not detections:
|
||||
retry_th = float(getattr(cfg, "TARGET_CLASS_YOLO_RETRY_CONF_TH", 0.25) or 0)
|
||||
if not (0 < retry_th < conf_th):
|
||||
retry_th = conf_th
|
||||
print(f"[YOLO-TARGET] 0 结果,重新检测 第2次 conf_th={retry_th}")
|
||||
if logger:
|
||||
logger.info(f"[YOLO-TARGET] 0 结果,重新检测 conf_th={retry_th}")
|
||||
raw_retry = _run(retry_th)
|
||||
detections_retry = _parse(raw_retry)
|
||||
print(f"[YOLO-TARGET] 第2次 raw={raw_retry!r}")
|
||||
print(
|
||||
f"[YOLO-TARGET] 检测结果={detections_retry}"
|
||||
f"(已忽略 class_id∈{tuple(sorted(ignore_class_ids))} 共{n_ignored[0]} 条)"
|
||||
)
|
||||
if detections_retry:
|
||||
raw, detections = raw_retry, detections_retry
|
||||
detections = detections_retry
|
||||
|
||||
if logger:
|
||||
logger.info(f"[YOLO-TARGET] 检测结果={detections}")
|
||||
logger.info(
|
||||
f"[YOLO-TARGET] 检测结果={detections}"
|
||||
f"(已忽略 class_id∈{tuple(sorted(ignore_class_ids))} 共{n_ignored[0]} 条)"
|
||||
)
|
||||
|
||||
if not detections:
|
||||
if logger:
|
||||
logger.warning("[YOLO-TARGET] 未检测到目标(已重检 1 次)")
|
||||
return None
|
||||
|
||||
# 上报口径:只在有效靶规格(20/40)里选置信度最高的一条,忽略 label=0 等无效类
|
||||
valid = [d for d in detections if d["label"] in valid_labels]
|
||||
if valid:
|
||||
picked = max(valid, key=lambda d: d["confidence"])
|
||||
else:
|
||||
picked = {
|
||||
"class_id": None,
|
||||
"label": None,
|
||||
"confidence": None,
|
||||
"box_xywh": None,
|
||||
}
|
||||
def _best_valid(dets):
|
||||
valid = [d for d in dets if d["label"] in valid_labels]
|
||||
if not valid:
|
||||
return None
|
||||
return max(valid, key=lambda d: d["confidence"])
|
||||
|
||||
print(
|
||||
f"[YOLO-TARGET] 上报={picked['label']}, class_id={picked['class_id']}, "
|
||||
f"conf={picked['confidence']}, 有效候选={[(d['label'], d['confidence']) for d in valid]}"
|
||||
)
|
||||
def _crop_target(box, margin_frac):
|
||||
"""按检出框(带 margin)裁出放大 ROI;返回 Maix image,失败返回 None。"""
|
||||
if not box or len(box) != 4:
|
||||
return None
|
||||
x, y, w, h = [float(v) for v in box]
|
||||
try:
|
||||
fw, fh = int(maix_frame.width()), int(maix_frame.height())
|
||||
except Exception:
|
||||
fw = fh = 0
|
||||
mx, my = w * margin_frac, h * margin_frac
|
||||
x0, y0 = int(max(0, x - mx)), int(max(0, y - my))
|
||||
x1 = int(min(fw, x + w + mx)) if fw else int(x + w + mx)
|
||||
y1 = int(min(fh, y + h + my)) if fh else int(y + h + my)
|
||||
if (x1 - x0) < 32 or (y1 - y0) < 32:
|
||||
if logger:
|
||||
logger.warning(
|
||||
f"[YOLO-TARGET] 复检裁剪过小 [{x0},{y0},{x1},{y1}]"
|
||||
)
|
||||
return None
|
||||
try:
|
||||
cropped = maix_frame.crop(x0, y0, x1 - x0, y1 - y0)
|
||||
if cropped is not None:
|
||||
return cropped
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
from maix import image as maix_image
|
||||
|
||||
arr = maix_frame.to_numpy()
|
||||
slab = np.ascontiguousarray(arr[y0:y1, x0:x1], dtype=np.uint8)
|
||||
if slab.size == 0:
|
||||
return None
|
||||
return maix_image.cv2image(slab, False, True)
|
||||
except Exception as exc:
|
||||
if logger:
|
||||
logger.warning(f"[YOLO-TARGET] 复检裁剪失败: {exc}")
|
||||
return None
|
||||
|
||||
# 上报口径:只在有效靶规格(20/40)里取置信度最高的一条,class_id=1 已在解析阶段过滤
|
||||
full_best = _best_valid(detections)
|
||||
|
||||
# 二次裁剪复检:把全图检出的靶框裁剪放大后再 detect 一次,与全图结果互相印证
|
||||
# 复检用更低阈值 RECHECK_CONF_TH,置信度大于该值即接受
|
||||
recheck_th = float(getattr(cfg, "TARGET_CLASS_YOLO_RECHECK_CONF_TH", 0.25))
|
||||
crop_best = None
|
||||
recheck_status = "off"
|
||||
if full_best is not None and bool(
|
||||
getattr(cfg, "TARGET_CLASS_YOLO_RECHECK_CROP", True)
|
||||
):
|
||||
recheck_status = "fail"
|
||||
margin_frac = float(
|
||||
getattr(cfg, "TARGET_CLASS_YOLO_RECHECK_MARGIN_FRAC", 0.1)
|
||||
)
|
||||
crop_img = _crop_target(full_best["box_xywh"], margin_frac)
|
||||
if crop_img is not None:
|
||||
try:
|
||||
raw_crop = detector.detect(
|
||||
crop_img, conf_th=recheck_th, iou_th=iou_th
|
||||
)
|
||||
except Exception as exc:
|
||||
raw_crop = None
|
||||
if logger:
|
||||
logger.warning(f"[YOLO-TARGET] 复检 detect 异常: {exc}")
|
||||
if raw_crop is not None:
|
||||
crop_dets = _parse(raw_crop)
|
||||
recheck_status = "ok"
|
||||
crop_best = _best_valid(crop_dets)
|
||||
if crop_best is not None and crop_best["confidence"] <= recheck_th:
|
||||
crop_best = None
|
||||
if logger:
|
||||
logger.info(
|
||||
f"[YOLO-TARGET] 复检(conf_th={recheck_th}, "
|
||||
f"裁剪框={full_best['box_xywh']}) 结果={crop_dets}"
|
||||
)
|
||||
|
||||
# 合并:以复检(裁剪放大)结果为主;复检无结果才用全图
|
||||
picked = full_best
|
||||
picked_from_crop = False
|
||||
note = ""
|
||||
if recheck_status == "ok":
|
||||
if crop_best is None:
|
||||
note = f"复检无 conf>{recheck_th} 的有效框,沿用全图"
|
||||
else:
|
||||
picked = crop_best
|
||||
picked_from_crop = True
|
||||
if crop_best["label"] == full_best["label"]:
|
||||
note = f"复检一致({crop_best['label']}),以复检为准"
|
||||
else:
|
||||
note = (
|
||||
f"复检与全图不一致(全图={full_best['label']}@{full_best['confidence']}, "
|
||||
f"裁剪={crop_best['label']}@{crop_best['confidence']}),以复检为准"
|
||||
)
|
||||
|
||||
# 置信度门槛:低于阈值不上报类别,避免硬猜(复检结果只需 > RECHECK_CONF_TH)
|
||||
if picked is not None:
|
||||
if picked_from_crop:
|
||||
min_report_conf = recheck_th
|
||||
below = picked["confidence"] <= min_report_conf
|
||||
else:
|
||||
min_report_conf = float(
|
||||
getattr(cfg, "TARGET_CLASS_YOLO_MIN_REPORT_CONF", 0.5)
|
||||
)
|
||||
below = picked["confidence"] < min_report_conf
|
||||
if below:
|
||||
note = (note + "; " if note else "") + (
|
||||
f"conf={picked['confidence']} 未达门槛{min_report_conf},置信度不足"
|
||||
)
|
||||
picked = None
|
||||
|
||||
if picked is None:
|
||||
label, class_id, confidence = None, None, None
|
||||
else:
|
||||
label = picked["label"]
|
||||
class_id = picked["class_id"]
|
||||
confidence = picked["confidence"]
|
||||
|
||||
if logger:
|
||||
logger.info(
|
||||
f"[YOLO-TARGET] 上报={label}, class_id={class_id}, conf={confidence}, "
|
||||
f"全图候选={[(d['label'], d['confidence']) for d in detections if d['label'] in valid_labels]}, "
|
||||
f"复检={recheck_status} {note}"
|
||||
)
|
||||
if note:
|
||||
logger.warning(f"[YOLO-TARGET] {note}")
|
||||
|
||||
return {
|
||||
"label": picked["label"],
|
||||
"class_id": picked["class_id"],
|
||||
"confidence": picked["confidence"],
|
||||
"label": label,
|
||||
"class_id": class_id,
|
||||
"confidence": confidence,
|
||||
"detections": detections,
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
应用版本号
|
||||
每次 OTA 更新时,只需要更新这个文件中的版本号
|
||||
"""
|
||||
VERSION = '2.18.9'
|
||||
VERSION = '2.18.10'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user