新分支 加入了标靶判断
This commit is contained in:
130
shoot_manager.py
130
shoot_manager.py
@@ -58,6 +58,7 @@ def analyze_shot(frame, laser_point=None):
|
||||
# ── Step 1: 确定激光点 ────────────────────────────────────────────────────
|
||||
laser_point_method = None
|
||||
distance_m_first = None
|
||||
best_radius1_temp = None
|
||||
|
||||
if config.HARDCODE_LASER_POINT:
|
||||
laser_point = laser_manager.laser_point
|
||||
@@ -102,9 +103,22 @@ def analyze_shot(frame, laser_point=None):
|
||||
r_img, center, radius, method, best_radius1, ellipse_params = cdata
|
||||
dx, dy = None, None
|
||||
d_m = distance_m_first
|
||||
tri_h = None
|
||||
if center and radius:
|
||||
dx, dy = laser_manager.compute_laser_position(center, (x, y), radius, method)
|
||||
d_m = estimate_distance(best_radius1) if best_radius1 else distance_m_first
|
||||
try:
|
||||
import numpy as _np
|
||||
px_per_cm = float(radius) / 10.0
|
||||
if px_per_cm > 1e-6:
|
||||
cxp, cyp = float(center[0]), float(center[1])
|
||||
tri_h = _np.array([
|
||||
[1.0 / px_per_cm, 0.0, -cxp / px_per_cm],
|
||||
[0.0, 1.0 / px_per_cm, -cyp / px_per_cm],
|
||||
[0.0, 0.0, 1.0],
|
||||
], dtype=float)
|
||||
except Exception:
|
||||
tri_h = None
|
||||
out = {
|
||||
"success": True,
|
||||
"result_img": r_img,
|
||||
@@ -114,6 +128,7 @@ def analyze_shot(frame, laser_point=None):
|
||||
"laser_point": laser_point, "laser_point_method": laser_point_method,
|
||||
"offset_method": "yellow_ellipse" if ellipse_params else "yellow_circle",
|
||||
"distance_method": "yellow_radius",
|
||||
"tri_homography": tri_h,
|
||||
}
|
||||
if yolo_roi_xyxy is not None:
|
||||
out["yolo_roi_xyxy"] = yolo_roi_xyxy
|
||||
@@ -129,8 +144,10 @@ def analyze_shot(frame, laser_point=None):
|
||||
roi_xyxy = None
|
||||
yolo_ring_ms = 0.0
|
||||
yolo_black_ms = 0.0
|
||||
_timing_on = bool(getattr(config, "ARCHERY_TIMING_ENABLE", True))
|
||||
_sample_on = bool(getattr(config, "TRIANGLE_SAMPLE_ENABLE", False))
|
||||
if getattr(config, "TRIANGLE_YOLO_ROI_ENABLE", False):
|
||||
_t_yolo_ring = time_std.perf_counter()
|
||||
_t_yolo_ring = time_std.perf_counter() if _timing_on else None
|
||||
try:
|
||||
from target_roi_yolo import try_get_triangle_roi_from_yolo
|
||||
roi_xyxy = try_get_triangle_roi_from_yolo(
|
||||
@@ -140,7 +157,8 @@ def analyze_shot(frame, laser_point=None):
|
||||
if logger:
|
||||
logger.warning(f"[YOLO-ROI] {e}")
|
||||
finally:
|
||||
yolo_ring_ms = (time_std.perf_counter() - _t_yolo_ring) * 1000.0
|
||||
if _timing_on and _t_yolo_ring is not None:
|
||||
yolo_ring_ms = (time_std.perf_counter() - _t_yolo_ring) * 1000.0
|
||||
|
||||
_loc_mode = str(
|
||||
getattr(config, "TRIANGLE_BLACK_TRIANGLE_LOCATE_MODE", "yolo")
|
||||
@@ -155,7 +173,7 @@ def analyze_shot(frame, laser_point=None):
|
||||
and roi_xyxy is not None
|
||||
)
|
||||
if _run_stage2_black_yolo:
|
||||
_t_yolo_black = time_std.perf_counter()
|
||||
_t_yolo_black = time_std.perf_counter() if _timing_on else None
|
||||
try:
|
||||
from target_roi_yolo import try_black_triangle_boxes_work
|
||||
|
||||
@@ -166,7 +184,8 @@ def analyze_shot(frame, laser_point=None):
|
||||
if logger:
|
||||
logger.warning(f"[YOLO-BLACK] {e}")
|
||||
finally:
|
||||
yolo_black_ms = (time_std.perf_counter() - _t_yolo_black) * 1000.0
|
||||
if _timing_on and _t_yolo_black is not None:
|
||||
yolo_black_ms = (time_std.perf_counter() - _t_yolo_black) * 1000.0
|
||||
elif (
|
||||
logger
|
||||
and _loc_mode == "traditional"
|
||||
@@ -184,7 +203,7 @@ def analyze_shot(frame, laser_point=None):
|
||||
try:
|
||||
logger.info(f"[TRI] begin {datetime.now()}")
|
||||
logger.info(f"[TRI] K: {K}, dist: {dist_coef}, pos: {pos}, {datetime.now()}")
|
||||
_t_wall_try = time_std.perf_counter()
|
||||
_t_wall_try = time_std.perf_counter() if _timing_on else None
|
||||
tri = try_triangle_scoring(
|
||||
img_cv, (x, y), pos, K, dist_coef,
|
||||
size_range=getattr(config, "TRIANGLE_SIZE_RANGE", (8, 500)),
|
||||
@@ -193,8 +212,8 @@ def analyze_shot(frame, laser_point=None):
|
||||
yolo_ring_ms=yolo_ring_ms,
|
||||
yolo_black_ms=yolo_black_ms,
|
||||
)
|
||||
_wall_try_ms = (time_std.perf_counter() - _t_wall_try) * 1000.0
|
||||
if logger and bool(getattr(config, "TRIANGLE_LOG_E2E_TIMING", True)):
|
||||
_wall_try_ms = (time_std.perf_counter() - _t_wall_try) * 1000.0 if _timing_on else 0.0
|
||||
if logger and bool(getattr(config, "TRIANGLE_LOG_E2E_TIMING", True)) and _timing_on:
|
||||
_e2e = float(yolo_ring_ms) + float(yolo_black_ms) + float(_wall_try_ms)
|
||||
logger.info(
|
||||
f"[TRI] timing_e2e_triangle_ms={_e2e:.1f} "
|
||||
@@ -280,6 +299,16 @@ def analyze_shot(frame, laser_point=None):
|
||||
"tri_markers_completed": tri.get("markers_completed", []),
|
||||
"tri_homography": tri.get("homography"),
|
||||
}
|
||||
try:
|
||||
import numpy as _np
|
||||
_H = tri.get("homography")
|
||||
if _H is not None and _np.all(_np.isfinite(_H)):
|
||||
_H_inv = _np.linalg.inv(_H)
|
||||
_pt = _np.array([[[0.0, 0.0]]], dtype=_np.float32)
|
||||
_center_pt = cv2.perspectiveTransform(_pt, _H_inv)[0][0]
|
||||
out["tri_center_px"] = [float(_center_pt[0]), float(_center_pt[1])]
|
||||
except Exception:
|
||||
pass
|
||||
if yolo_roi_xyxy is not None:
|
||||
out["yolo_roi_xyxy"] = yolo_roi_xyxy
|
||||
return out
|
||||
@@ -318,6 +347,7 @@ def process_shot(adc_val):
|
||||
:return: None
|
||||
"""
|
||||
logger = logger_manager.logger
|
||||
_timing_on = bool(getattr(config, "ARCHERY_TIMING_ENABLE", True))
|
||||
|
||||
try:
|
||||
network_manager.safe_enqueue({"shoot_event": "start"}, msg_type=2, high=True)
|
||||
@@ -356,6 +386,86 @@ def process_shot(adc_val):
|
||||
)
|
||||
x, y = laser_point
|
||||
|
||||
# 物方采样调试(config.TRIANGLE_SAMPLE_ENABLE):靶心为原点,取两个对称点判断黑白来区分 40/20 标靶
|
||||
# 逻辑:若两个采样点 RGB 均 < 阈值 → 全黑 → 40cm 标靶;否则 → 20cm 标靶
|
||||
sample_target_type = None
|
||||
_t_sample = time_std.perf_counter() if _timing_on else None
|
||||
_t_sample_ms = 0.0
|
||||
sample_points = []
|
||||
sample_patch_half = 2
|
||||
if bool(getattr(config, "TRIANGLE_SAMPLE_ENABLE", False)):
|
||||
sample_obj_radius_cm = float(getattr(config, "TRIANGLE_SAMPLE_RADIUS_CM", 15.0))
|
||||
sample_obj_angles_deg = (0, 180) # 只取两个对称点:+X 和 -X
|
||||
sample_patch_half = int(getattr(config, "TRIANGLE_SAMPLE_PATCH_HALF_PX", 2))
|
||||
sample_black_thresh = float(getattr(config, "TRIANGLE_SAMPLE_BLACK_THRESH", 30.0))
|
||||
try:
|
||||
import math as _math
|
||||
import numpy as _np
|
||||
import cv2 as _cv2
|
||||
|
||||
if tri_homography is not None:
|
||||
_H_inv = _np.linalg.inv(tri_homography)
|
||||
for _ang in sample_obj_angles_deg:
|
||||
_rad = _math.radians(float(_ang))
|
||||
_pt_obj = _np.array([
|
||||
[[sample_obj_radius_cm * _math.cos(_rad), sample_obj_radius_cm * _math.sin(_rad)]]
|
||||
], dtype=_np.float32)
|
||||
_pt_img = _cv2.perspectiveTransform(_pt_obj, _H_inv)[0][0]
|
||||
_px, _py = float(_pt_img[0]), float(_pt_img[1])
|
||||
sample_points.append({
|
||||
"angle_deg": float(_ang),
|
||||
"obj_cm": (float(sample_obj_radius_cm * _math.cos(_rad)), float(sample_obj_radius_cm * _math.sin(_rad))),
|
||||
"img_px": (int(round(_px)), int(round(_py))),
|
||||
})
|
||||
elif center and radius:
|
||||
_px_per_cm = float(radius) / 10.0
|
||||
for _ang in sample_obj_angles_deg:
|
||||
_rad = _math.radians(float(_ang))
|
||||
_px = float(center[0]) + sample_obj_radius_cm * _math.cos(_rad) * _px_per_cm
|
||||
_py = float(center[1]) + sample_obj_radius_cm * _math.sin(_rad) * _px_per_cm
|
||||
sample_points.append({
|
||||
"angle_deg": float(_ang),
|
||||
"obj_cm": (float(sample_obj_radius_cm * _math.cos(_rad)), float(sample_obj_radius_cm * _math.sin(_rad))),
|
||||
"img_px": (int(round(_px)), int(round(_py))),
|
||||
})
|
||||
|
||||
# 取样后立即读像素并判断黑白:三角成功用 H_inv;三角失败但圆心成功用 center/radius 近似物方半径
|
||||
_all_black = False
|
||||
_sample_infos = []
|
||||
if sample_points:
|
||||
_img_cv_for_sample = image.image2cv(result_img, False, False)
|
||||
_all_black = True
|
||||
for _sp in sample_points:
|
||||
_sx, _sy = _sp["img_px"]
|
||||
_hh = max(1, sample_patch_half)
|
||||
_patch = []
|
||||
for _yy in range(_sy - _hh, _sy + _hh + 1):
|
||||
if _yy < 0 or _yy >= _img_cv_for_sample.shape[0]:
|
||||
continue
|
||||
for _xx in range(_sx - _hh, _sx + _hh + 1):
|
||||
if _xx < 0 or _xx >= _img_cv_for_sample.shape[1]:
|
||||
continue
|
||||
_patch.append(_img_cv_for_sample[_yy, _xx].astype(float))
|
||||
if _patch:
|
||||
_mean_rgb = _np.mean(_patch, axis=0)
|
||||
_is_black = bool(_mean_rgb[0] < sample_black_thresh
|
||||
and _mean_rgb[1] < sample_black_thresh
|
||||
and _mean_rgb[2] < sample_black_thresh)
|
||||
if not _is_black:
|
||||
_all_black = False
|
||||
_sample_infos.append(
|
||||
f"{int(_sp['angle_deg'])}°@{_sx},{_sy} rgb=({int(_mean_rgb[0])},{int(_mean_rgb[1])},{int(_mean_rgb[2])})"
|
||||
)
|
||||
sample_target_type = "40cm_black" if _all_black else "20cm"
|
||||
if _sample_infos:
|
||||
logger.info("[采样] " + " | ".join(_sample_infos) + f" → {sample_target_type}")
|
||||
except Exception as _e_sample:
|
||||
sample_points = []
|
||||
if logger:
|
||||
logger.warning(f"[采样] 标靶类型判断失败: {_e_sample}")
|
||||
if _timing_on and _t_sample is not None:
|
||||
_t_sample_ms = (time_std.perf_counter() - _t_sample) * 1000.0
|
||||
|
||||
# 三角形路径成功时 center/radius 为空是正常的;此时用 triangle 方法名用于保存文件名与上报字段 m
|
||||
if (not method) and tri_markers:
|
||||
method = "triangle_homography"
|
||||
@@ -397,6 +507,7 @@ def process_shot(adc_val):
|
||||
"target_y": float(y),
|
||||
"offset_method": offset_method,
|
||||
"distance_method": distance_method,
|
||||
"target_type": 40 if sample_target_type == "40cm_black" else (20 if sample_target_type == "20cm" else None),
|
||||
}
|
||||
|
||||
if ellipse_params:
|
||||
@@ -471,6 +582,11 @@ def process_shot(adc_val):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 物方采样标靶类型判断耗时(合并在上面采样块内,单独统计)
|
||||
if _timing_on and bool(getattr(config, "TRIANGLE_SAMPLE_ENABLE", False)) and sample_target_type is not None:
|
||||
logger.info(f"[采样] 标靶类型: {sample_target_type} 耗时: {_t_sample_ms:.2f}ms")
|
||||
|
||||
|
||||
# 叠加信息:落点-圆心距离 / 相机-靶距离等
|
||||
try:
|
||||
import math as _math
|
||||
|
||||
Reference in New Issue
Block a user