update power estimation and upload 2 models of yolo

This commit is contained in:
gcw_4spBpAfv
2026-05-08 23:41:42 +08:00
parent 5e7db5e271
commit a090579db9
12 changed files with 2061 additions and 159 deletions

155
vision.py
View File

@@ -678,6 +678,91 @@ def estimate_distance(pixel_radius):
return 0.0
return (config.REAL_RADIUS_CM * config.FOCAL_LENGTH_PIX) / pixel_radius / 100.0
def _draw_yolo_roi_on_rgb_numpy(img_cv, yolo_roi_xyxy):
"""
在 RGB numpy 图像上绘制靶环 YOLO ROI与原先 shoot_manager 主线程绘制语义一致)。
供存图 worker 异步调用,不阻塞射箭主流程。
"""
if yolo_roi_xyxy is None:
return
if not getattr(config, "TRIANGLE_YOLO_DRAW_ROI_ON_SHOT", True):
return
try:
rx0, ry0, rx1, ry1 = (int(round(float(v))) for v in yolo_roi_xyxy)
ih, iw = img_cv.shape[:2]
rx0 = max(0, min(rx0, iw - 1))
ry0 = max(0, min(ry0, ih - 1))
rx1 = max(rx0 + 1, min(rx1, iw))
ry1 = max(ry0 + 1, min(ry1, ih))
cv2.rectangle(
img_cv,
(rx0, ry0),
(rx1 - 1, ry1 - 1),
(0, 255, 255),
2,
)
cv2.putText(
img_cv,
"YOLO ROI",
(max(0, rx0), max(16, ry0 - 4)),
cv2.FONT_HERSHEY_SIMPLEX,
0.55,
(0, 255, 255),
1,
)
except Exception:
pass
def prune_old_images_in_dir(photo_dir, max_images, logger=None, log_prefix="[VISION]"):
"""
若目录内 bmp/jpg/jpeg 超过 max_images按 mtime 从最旧开始删,直到数量 ≤ max_images。
与射箭主图目录清理规则一致,供 PHOTO_DIR、stage2_roi 等共用。
"""
try:
max_images = int(max_images)
except (TypeError, ValueError):
return
if max_images <= 0 or not photo_dir:
return
if logger is None:
logger = logger_manager.logger
try:
if not os.path.isdir(photo_dir):
return
image_files = []
for f in os.listdir(photo_dir):
if f.endswith((".bmp", ".jpg", ".jpeg")):
filepath = os.path.join(photo_dir, f)
try:
mtime = os.path.getmtime(filepath)
image_files.append((mtime, filepath, f))
except Exception:
pass
if len(image_files) <= max_images:
return
image_files.sort(key=lambda x: x[0])
to_delete = len(image_files) - max_images
deleted_count = 0
for _, filepath, fname in image_files[:to_delete]:
try:
os.remove(filepath)
deleted_count += 1
if logger:
logger.debug(f"{log_prefix} 删除旧图片: {fname}")
except Exception as e:
if logger:
logger.warning(f"{log_prefix} 删除旧图片失败 {fname}: {e}")
if logger and deleted_count > 0:
logger.info(
f"{log_prefix} 已清理 {deleted_count} 张旧图,"
f"目录保留至多 {max_images} 张: {photo_dir}"
)
except Exception as e:
if logger:
logger.warning(f"{log_prefix} 清理旧图片时出错(可忽略): {e}")
def estimate_pixel(physical_distance_cm, target_distance_m):
"""
根据物理距离和目标距离计算对应的像素偏移
@@ -696,7 +781,8 @@ def estimate_pixel(physical_distance_cm, target_distance_m):
def _save_shot_image_impl(img_cv, center, radius, method, ellipse_params,
laser_point, distance_m, shot_id=None, photo_dir=None):
laser_point, distance_m, shot_id=None, photo_dir=None,
yolo_roi_xyxy=None):
"""
内部实现:在 img_cv (numpy HWC RGB) 上绘制标注并保存。
由 save_shot_image同步和存图 worker异步调用。
@@ -735,6 +821,8 @@ def _save_shot_image_impl(img_cv, center, radius, method, ellipse_params,
distance_str = str(round((distance_m or 0.0) * 100))
filename = f"{photo_dir}/{method_str}_{int(x)}_{int(y)}_{distance_str}_{img_count:04d}.jpg"
_draw_yolo_roi_on_rgb_numpy(img_cv, yolo_roi_xyxy)
logger = logger_manager.logger
if logger:
if shot_id:
@@ -786,37 +874,7 @@ def _save_shot_image_impl(img_cv, center, radius, method, ellipse_params,
else:
logger.debug(f"图像已保存(无靶心,含激光十字线): {filename}")
# 清理旧图片如果目录下图片超过100张删除最老的
try:
image_files = []
for f in os.listdir(photo_dir):
if f.endswith(('.bmp', '.jpg', '.jpeg')):
filepath = os.path.join(photo_dir, f)
try:
mtime = os.path.getmtime(filepath)
image_files.append((mtime, filepath, f))
except Exception:
pass
from config import MAX_IMAGES
if len(image_files) > MAX_IMAGES:
image_files.sort(key=lambda x: x[0])
to_delete = len(image_files) - MAX_IMAGES
deleted_count = 0
for _, filepath, fname in image_files[:to_delete]:
try:
os.remove(filepath)
deleted_count += 1
if logger:
logger.debug(f"[VISION] 删除旧图片: {fname}")
except Exception as e:
if logger:
logger.warning(f"[VISION] 删除旧图片失败 {fname}: {e}")
if logger and deleted_count > 0:
logger.info(f"[VISION] 已清理 {deleted_count} 张旧图片,当前剩余 {MAX_IMAGES}")
except Exception as e:
if logger:
logger.warning(f"[VISION] 清理旧图片时出错(可忽略): {e}")
prune_old_images_in_dir(photo_dir, config.MAX_IMAGES, logger, "[VISION]")
return filename
except Exception as e:
@@ -864,7 +922,8 @@ def start_save_shot_worker():
def enqueue_save_shot(result_img, center, radius, method, ellipse_params,
laser_point, distance_m, shot_id=None, photo_dir=None):
laser_point, distance_m, shot_id=None, photo_dir=None,
yolo_roi_xyxy=None):
"""
将存图任务放入队列,由 worker 异步保存。主线程传入 result_img 的复制,不阻塞。
"""
@@ -880,7 +939,18 @@ def enqueue_save_shot(result_img, center, radius, method, ellipse_params,
if logger:
logger.error(f"[VISION] enqueue_save_shot 复制图像失败: {e}")
return
task = (img_copy, center, radius, method, ellipse_params, laser_point, distance_m, shot_id, photo_dir)
task = (
img_copy,
center,
radius,
method,
ellipse_params,
laser_point,
distance_m,
shot_id,
photo_dir,
yolo_roi_xyxy,
)
try:
_save_queue.put_nowait(task)
except queue.Full:
@@ -890,7 +960,8 @@ def enqueue_save_shot(result_img, center, radius, method, ellipse_params,
def save_shot_image(result_img, center, radius, method, ellipse_params,
laser_point, distance_m, shot_id=None, photo_dir=None):
laser_point, distance_m, shot_id=None, photo_dir=None,
yolo_roi_xyxy=None):
"""
保存射击图像(带标注)。同步调用,会阻塞。
主流程建议使用 enqueue_save_shot此处保留供校准、测试等场景使用。
@@ -901,8 +972,18 @@ def save_shot_image(result_img, center, radius, method, ellipse_params,
photo_dir = config.PHOTO_DIR
try:
img_cv = image.image2cv(result_img, False, False)
return _save_shot_image_impl(img_cv, center, radius, method, ellipse_params,
laser_point, distance_m, shot_id, photo_dir)
return _save_shot_image_impl(
img_cv,
center,
radius,
method,
ellipse_params,
laser_point,
distance_m,
shot_id,
photo_dir,
yolo_roi_xyxy,
)
except Exception as e:
logger = logger_manager.logger
if logger: