feat: 2.18.9 靶规格YOLO分类重构
- 换用 model_325964(class_id: 0=40cm, 1=靶心, 2=20cm) - class_id=1 解析阶段直接过滤,不参与上报选择 - 上报仅在有效靶规格(20/40)中取置信度最高的一条 - 0 结果时按 RETRY_CONF_TH 自动重检一次 - 增加检测调试打印(raw/detections/上报/忽略计数)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 训练数据一致)→ 子框内传统算法取直角点 ──
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
+115
-45
@@ -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):
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
应用版本号
|
||||
每次 OTA 更新时,只需要更新这个文件中的版本号
|
||||
"""
|
||||
VERSION = '2.18.6'
|
||||
VERSION = '2.18.9'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user