diff --git a/app.yaml b/app.yaml index f08be6c..ff58127 100644 --- a/app.yaml +++ b/app.yaml @@ -1,6 +1,6 @@ id: t11 name: t11 -version: 2.18.6 +version: 2.18.9 author: t11 icon: '' desc: t11 @@ -18,8 +18,8 @@ files: - laser_manager.py - logger_manager.py - main.py - - model_317828.cvimodel - - model_317828.mud + - model_325964.cvimodel + - model_325964.mud - network.py - ota_curl.sh - ota_manager.py diff --git a/config.py b/config.py index 649933c..1cc0109 100644 --- a/config.py +++ b/config.py @@ -262,14 +262,18 @@ TRIANGLE_SAMPLE_PATCH_HALF_PX = 2 # 开机阶段预加载 YOLO detector;detect 使用 dual_buff=False,避免返回上一帧结果。 TRIANGLE_YOLO_PRELOAD_ON_BOOT = False -# YOLO target size classification: class 0=20cm, class 1=40cm. +# 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_317828.mud" -TARGET_CLASS_YOLO_LABELS = (20, 40) -TARGET_CLASS_YOLO_CONF_TH = 0.66 -TARGET_CLASS_YOLO_IOU_TH = 0.45 -TARGET_CLASS_YOLO_RETRY_ON_EMPTY = False +TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_325964.mud" +TARGET_CLASS_YOLO_LABELS = (40, 0, 20) +# 只有映射结果落在该集合内才用于上报(如 class 1→label 0 直接忽略) +TARGET_CLASS_YOLO_VALID_LABELS = (20, 40) +# 解析阶段直接丢弃的 class_id(1=靶心),不参与任何选择/上报 +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 +TARGET_CLASS_YOLO_IOU_TH = 0.45 TARGET_CLASS_YOLO_PRELOAD_ON_BOOT = True # ── 第二段 YOLO:仅在 Stage1 裁切出的靶环图上推理(与合成 stage2 训练数据一致)→ 子框内传统算法取直角点 ── diff --git a/model_317828.cvimodel b/model_325964.cvimodel similarity index 52% rename from model_317828.cvimodel rename to model_325964.cvimodel index 0216585..ca6d035 100644 Binary files a/model_317828.cvimodel and b/model_325964.cvimodel differ diff --git a/model_317828.mud b/model_325964.mud similarity index 82% rename from model_317828.mud rename to model_325964.mud index d67e0e2..97d06d2 100644 --- a/model_317828.mud +++ b/model_325964.mud @@ -1,7 +1,7 @@ [basic] type = cvimodel -model = model_317828.cvimodel +model = model_325964.cvimodel [extra] model_type = yolov5 @@ -9,5 +9,5 @@ input_type = rgb mean = 0, 0, 0 scale = 0.00392156862745098, 0.00392156862745098, 0.00392156862745098 anchors = 10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326 -labels = 20, 40 +labels = 40, 00, 20 diff --git a/target_roi_yolo.py b/target_roi_yolo.py index eb1c321..913d78d 100644 --- a/target_roi_yolo.py +++ b/target_roi_yolo.py @@ -310,7 +310,9 @@ def _det_obj_score(o): def try_get_target_class_from_yolo(maix_frame, logger=None): - """Classify the current target as 20cm or 40cm; return None if unknown.""" + """调用 YOLO 检测:class_id∈IGNORE_CLASS_IDS(默认 1) 的框在解析时直接过滤; + detections 输出其余全部框(不按置信度过滤); + 上报字段 label/class_id/confidence 仅在有效靶规格(20/40)中取置信度最高的一条。""" try: import config as cfg except Exception: @@ -333,64 +335,132 @@ def try_get_target_class_from_yolo(maix_frame, logger=None): conf_th = float(getattr(cfg, "TARGET_CLASS_YOLO_CONF_TH", 0.5)) iou_th = float(getattr(cfg, "TARGET_CLASS_YOLO_IOU_TH", 0.45)) - labels = getattr(cfg, "TARGET_CLASS_YOLO_LABELS", (20, 40)) + + # class_id → 靶规格标签,下标即 class_id:0→40, 1→0, 2→20 + labels = getattr(cfg, "TARGET_CLASS_YOLO_LABELS", (40, 0, 20)) if isinstance(labels, str): labels = tuple(x.strip() for x in labels.split(",") if x.strip()) labels = tuple(labels) - def _detect(threshold): + # 只有映射到这些标签的检测才算有效靶规格(class 1→label 0 直接忽略) + valid_labels = getattr(cfg, "TARGET_CLASS_YOLO_VALID_LABELS", (20, 40)) + if isinstance(valid_labels, str): + valid_labels = tuple( + int(float(x.strip())) for x in valid_labels.split(",") if x.strip() + ) + valid_labels = frozenset(int(float(v)) for v in valid_labels) + + # 解析阶段就丢弃这些 class_id(1=靶心),不进入 detections,也不参与上报选择 + ignore_class_ids = getattr(cfg, "TARGET_CLASS_YOLO_IGNORE_CLASS_IDS", (1,)) + if isinstance(ignore_class_ids, int): + ignore_class_ids = (ignore_class_ids,) + if isinstance(ignore_class_ids, str): + ignore_class_ids = tuple( + int(float(x.strip())) for x in ignore_class_ids.split(",") if x.strip() + ) + ignore_class_ids = frozenset(int(float(v)) for v in ignore_class_ids) + n_ignored = [0] + + def _run(threshold): try: - raw = detector.detect(maix_frame, conf_th=threshold, iou_th=iou_th) + 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 [] - return _normalize_objs(raw if raw is not None else []) + return None - def _candidates(objs): - found = [] - for obj in objs: - class_id = _det_obj_class_id(obj) - if class_id is None or class_id < 0 or class_id >= len(labels): - continue - try: - label = int(float(labels[class_id])) - except (TypeError, ValueError): - continue - if label in (20, 40): - found.append((label, class_id, _det_obj_score(obj))) - return found - - objects = _detect(conf_th) - candidates = _candidates(objects) - if logger and objects: - logger.info( - "[YOLO-TARGET] 原始框=%d, 解析类别=%s" - % ( - len(objects), - [(_det_obj_class_id(o), _det_obj_score(o)) for o in objects[:8]], + def _obj_box(o): + try: + return ( + round(float(o.x), 1), + round(float(o.y), 1), + round(float(o.w), 1), + round(float(o.h), 1), ) - ) - if not candidates and bool( - getattr(cfg, "TARGET_CLASS_YOLO_RETRY_ON_EMPTY", False) - ): - retry_th = float(getattr(cfg, "TARGET_CLASS_YOLO_RETRY_CONF_TH", conf_th)) - if 0 < retry_th < conf_th: - candidates = _candidates(_detect(retry_th)) + except (TypeError, ValueError, AttributeError): + return None - if not candidates: + def _parse(raw_result): + out = [] + n_ignored[0] = 0 + for obj in _normalize_objs(raw_result if raw_result is not None else []): + class_id = _det_obj_class_id(obj) + if class_id is None or class_id in ignore_class_ids: + n_ignored[0] += 1 + continue + label = None + if 0 <= class_id < len(labels): + try: + label = int(float(labels[class_id])) + except (TypeError, ValueError): + label = None + out.append( + { + "class_id": class_id, + "label": label, + "confidence": round(_det_obj_score(obj), 4), + "box_xywh": _obj_box(obj), + } + ) + return out + + # 第 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}") + 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 + + if logger: + logger.info(f"[YOLO-TARGET] 检测结果={detections}") + + if not detections: if logger: - logger.warning("[YOLO-TARGET] 当前帧未识别到 20/40,按未知处理") + logger.warning("[YOLO-TARGET] 未检测到目标(已重检 1 次)") return None - label, class_id, confidence = max(candidates, key=lambda item: item[2]) - result = {"label": label, "class_id": class_id, "confidence": confidence} - if logger: - logger.info( - f"[YOLO-TARGET] 当前帧分类={label}, class_id={class_id}, " - f"conf={confidence:.3f}" - ) - return result + # 上报口径:只在有效靶规格(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, + } + + print( + f"[YOLO-TARGET] 上报={picked['label']}, class_id={picked['class_id']}, " + f"conf={picked['confidence']}, 有效候选={[(d['label'], d['confidence']) for d in valid]}" + ) + + return { + "label": picked["label"], + "class_id": picked["class_id"], + "confidence": picked["confidence"], + "detections": detections, + } def _det_to_src_xyxy(o, coord_mode: str, src_w: int, src_h: int, net_w: int, net_h: int): diff --git a/version.py b/version.py index b9624e6..71a68bb 100644 --- a/version.py +++ b/version.py @@ -4,6 +4,6 @@ 应用版本号 每次 OTA 更新时,只需要更新这个文件中的版本号 """ -VERSION = '2.18.6' +VERSION = '2.18.9'