1. 系统目标
# 检测靶纸四角的等腰直角三角形标记（每个角一个）
# 计算激光落点在靶面上的二维偏移（厘米）
# 通过PnP算法估算靶面到相机的距离（米）

2. 核心算法流程
2.1 三角形检测 (detect_triangle_markers)
采用多策略级联保证鲁棒性：
图像输入 → 多阈值策略 → 候选三角形过滤 → 四点匹配
检测策略（按优先级）：

1.全局Otsu二值化（最快，~10ms）
2.自适应阈值（多种block size，光照不均时）
3.ROI局部阈值（候选不足3个时，分象限独立处理）
4.Black-Hat形态学增强（仍不足时，突出暗色标记）

三角形几何验证：
# 必须是直角三角形（检查勾股定理，容差20%）
# 两直角边长度差<20%
# 内部像素足够暗（灰度≤130，暗像素比例≥30%）
# 与周围背景对比度≥15灰度级

四点匹配算法：
# 从候选三角形中枚举所有4点组合
# 计算四边形评分：(对角比-1)*3 + (水平比-1) + (垂直比-1) + (边长偏差)*2
# 选择评分最低的组合作为四角标记

2.2 单应性落点计算 (homography_calibration)
建立图像坐标系 → 靶面坐标系（二维平面）的透视变换

将激光点像素坐标映射到靶面坐标（厘米）

使用RANSAC提高鲁棒性（阈值1像素）

2.3 PnP距离估计 (pnp_distance_meters)
已知四个标记点的三维坐标（x,y,z，单位cm）

通过solvePnP求解相机外参（旋转+平移）

距离 = ‖平移向量‖ / 100（转换为米）

3. 关键优化策略
3.1 多路径投票
同一图像区域被不同二值化方法检测到时，path_votes++

选择投票数高的候选，提高检测可信度

3.2 早退机制
候选≥3个 且 覆盖3个以上象限 → 停止更多阈值尝试

大幅降低嵌入式设备计算开销

3.3 3点补全机制
当只检测到3个角时，通过仿射变换估算第4个角位置

公式：P_missing = M_inv @ [x_target, y_target, 1]

3.4 图像缩放
默认缩放到0.5倍进行检测（由config控制）

坐标还原时乘以inv_scale，保持与标定矩阵一致

4. 数据流示例
python
输入:
  - img_rgb: H×W×3 图像
  - laser_xy: (x_px, y_px) 激光点像素坐标
  - marker_positions: {0:[0,0,0], 1:[0,30,0], 2:[30,30,0], 3:[30,0,0]} # 4角3D坐标(cm)

输出:
  {
    "ok": True,
    "dx_cm": 2.5,        # 靶面X偏移(cm，向右为正)
    "dy_cm": -3.2,       # 靶面Y偏移(cm，向上为正)
    "distance_m": 5.43,  # 相机到靶面距离(米)
    "offset_method": "triangle_homography",
    "distance_method": "pnp_triangle"
  }
5. 鲁棒性设计
5.1 参数自适应
从config.py动态读取所有阈值（可在线调整）

三角形边长范围、灰度阈值、对比度要求等均可配置

5.2 异常处理
角点退化检测（距离<3像素判定为重复）

NaN/Inf校验（单应性矩阵、偏移量、距离）

距离合理性检查（0.3~20米）

5.3 降级策略
PnP失败 → 只输出偏移，距离置None

4角检测失败 → 尝试3角补全

快速路径失败 → CLAHE增强兜底（可选）

6. 性能特点
CPU友好：默认Otsu单次处理，多数场景10-30ms完成检测

内存可控：最大候选数截断（默认10个），避免组合爆炸

嵌入式适配：支持图像缩放、早退机制降低计算量

7. 局限性
依赖四个等腰直角三角形（需靶纸特殊设计）

要求三角形内部足够暗、与背景有对比度

单应性假设靶面为平面（实际靶纸可能有轻微起伏）

这套算法在射击训练系统中作为主要定位手段。

8. 为了加速单应性的计算，引入了yolo模型，一共做了两个模型，一个为靶纸和黑色三角形一体的识别模型，用于做原照片上快速找到靶纸区域。另一个模型是黑色三角形的模型，用于做靶纸区域再找黑色三角形。但是经过对比发现，引入黑色三角形模型反而更慢。入下面的流程A和流程B：
        yolo靶纸+传统（流程B） 	 yolo靶纸+yolo黑色三角形（流程A）
平均值   646.08	                916.4457143	
标准差   94.61300968	          57.40401849	

公共前置（两条路都一样）
是否用靶环模型裁 Stage1

TRIANGLE_YOLO_ROI_ENABLE=True 时：跑 靶环 YOLO，得到全图上的 roi_xyxy，后面的三角形都在 img_work = 全图[roi] 上做（必要时再缩成 img_det 给整图传统分支用）。
False 时：roi_xyxy=None，三角形在 整幅相机图 上当 img_work。
之后都进入 try_triangle_scoring(img_cv, …, roi_xyxy=…, black_yolo_boxes_work=…)

在里面先做灰度、v_suppress、锐化、det_scale 缩略图等 prep（与是否黑三角模型无关）。
差别从 black_yolo_boxes_work 有没有有效子框列表 开始。

流程 A：用黑色三角形模型（Stage2 黑三角 YOLO）
配置要点：TRIANGLE_BLACK_YOLO_ENABLE=True，且 TRIANGLE_BLACK_TRIANGLE_LOCATE_MODE="yolo"，并且 已有 Stage1 裁切（roi_xyxy 不能为 None，否则根本不会跑黑三角 YOLO）。

步骤概要：

try_black_triangle_boxes_work

输入：全图 RGB + Stage1 的 ring_roi_xyxy。
在 Stage1 裁切图（与训练一致的 slab）上跑 黑三角 YOLO，得到若干个 子框（black_boxes_work，坐标在 裁切图/work 系）。
try_triangle_scoring 内

若 black_yolo_boxes_work 非空：
按配置在 Stage1 全分辨率灰度（或缩略灰度，视 det_scale / TRIANGLE_BLACK_YOLO_PATCH_GRAY_SOURCE）上，对每个子框裁 patch，跑 _extract_triangle_from_yolo_patch（子框内：Otsu → 失败再单次 Adaptive + 轮廓 + 形状/颜色）。
median_leg 过滤，再 四点分配 ID。
若 ≥3 个（通常 4 个）有效：认为 Stage2 成功，跳过 整幅 Stage1 上的 detect_triangle_markers。
若 不足 3 个 且未关 fallback：在 缩略后的整幅 work 灰度上再走 detect_triangle_markers（整图 Otsu + 整图 Adaptive×block_sizes + 各类 fallback），与「不用黑三角模型时的传统主路径」同类。
后续

角点从 det 坐标 ×inv_scale 回到 work，再 +roi 原点 回到全图；单应性、补第 4 点、PnP 等与另一条路相同。
耗时上多出来的部分：黑三角 YOLO 推理 + 每个子框一遍传统小流水线（成功时通常 不再付整图 detect_triangle_markers）。

流程 B：不用黑色三角形模型（纯传统定位三角）
典型配置（任一即可达到「不用黑三角模型」的效果）：

TRIANGLE_BLACK_YOLO_ENABLE=False，或
TRIANGLE_BLACK_TRIANGLE_LOCATE_MODE="traditional"（即使模型开关开着也不跑黑三角 YOLO），或
没有 Stage1 ROI（roi_xyxy is None）时，当前逻辑下 也不会跑 Stage2 黑三角 YOLO。
此时 black_yolo_boxes_work=None（或不等价于「有子框」）。

步骤概要：

try_triangle_scoring 内
不跑 子框 _extract_triangle_from_yolo_patch。
直接在 img_det（缩略后的 work） 上调用 detect_triangle_markers：
全局 Otsu（若 TRIANGLE_SKIP_GLOBAL_OTSU_EXTRACT_ON_YOLO_ROI 在有 ROI 时可能 不算 Otsu 轮廓，但仍会生成 Otsu 图供后续用）；
可选 象限 ROI（TRIANGLE_ROI_ENABLED）；
整图 Adaptive（TRIANGLE_ADAPTIVE_BLOCK_SIZES，例如 (11,)）；
不足再走 放宽 approxPolyDP、BlackHat 等。
后面同样是过滤、四点组合/象限分配、单应性、PnP 等。
特点：没有黑三角 NPU 时间，也 没有「按框重复 4 次子框传统」；但要在 一整张（缩略）ROI 图 上跑一套更重的 整图 pipeline。

对照一句话
用黑三角 YOLO（流程 A）	不用黑三角 YOLO（流程 B）
Stage2
黑三角模型给子框 → 子框内 Otsu + 至多一次 Adaptive
无 Stage2 模型
三角角点从哪来
优先 子框传统；不够再 整图 detect_triangle_markers
只有 整图 detect_triangle_markers
和「全图是否只做 Adaptive」
子框 不是只做 Adaptive；整图回退时也与全图路径一致（先 Otsu 等）
整图路径 也不是只做 Adaptive
靶环 YOLO（Stage1 裁切）在 A/B 里都可以开或关，与「黑三角模型」是独立开关。


