Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
563f76745a | ||
|
|
48368316d7 | ||
|
|
a6547e5c32 | ||
|
|
2cf223a9da | ||
|
|
ffeb82cf8f |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
id: t11
|
id: t11
|
||||||
name: t11
|
name: t11
|
||||||
version: 3.0.0
|
version: 2.18.2
|
||||||
author: t11
|
author: t11
|
||||||
icon: ''
|
icon: ''
|
||||||
desc: t11
|
desc: t11
|
||||||
@@ -18,8 +18,8 @@ files:
|
|||||||
- laser_manager.py
|
- laser_manager.py
|
||||||
- logger_manager.py
|
- logger_manager.py
|
||||||
- main.py
|
- main.py
|
||||||
- model_285484.cvimodel
|
- model_317828.cvimodel
|
||||||
- model_285484.mud
|
- model_317828.mud
|
||||||
- network.py
|
- network.py
|
||||||
- ota_curl.sh
|
- ota_curl.sh
|
||||||
- ota_manager.py
|
- ota_manager.py
|
||||||
|
|||||||
@@ -241,10 +241,10 @@ TRIANGLE_BLACKHAT_KERNEL_FRAC = 0.018 # 核大小 ≈ min(h,w)*frac,取奇数
|
|||||||
# ── YOLO(NPU) 靶环 ROI → 裁剪后再跑三角形(减小 CPU 处理面积)──────────────────
|
# ── YOLO(NPU) 靶环 ROI → 裁剪后再跑三角形(减小 CPU 处理面积)──────────────────
|
||||||
# 日志里 net_in=W×H 来自 .mud 模型(det.input_width/height),不是这里配置的。
|
# 日志里 net_in=W×H 来自 .mud 模型(det.input_width/height),不是这里配置的。
|
||||||
TRIANGLE_YOLO_ROI_ENABLE = True
|
TRIANGLE_YOLO_ROI_ENABLE = True
|
||||||
TRIANGLE_YOLO_MODEL_PATH = APP_DIR + "/model_270139.mud"
|
TRIANGLE_YOLO_MODEL_PATH = APP_DIR + "/model_317828.mud"
|
||||||
# 参与 ROI 的类别:多类时只填「整靶/靶环」的 id;不要填角标类,否则 union 仍可对,但 largest 会偏小。
|
# 参与 ROI 的类别:多类时只填「整靶/靶环」的 id;不要填角标类,否则 union 仍可对,但 largest 会偏小。
|
||||||
TRIANGLE_YOLO_RING_CLASS_IDS = (0,)
|
TRIANGLE_YOLO_RING_CLASS_IDS = (0,)
|
||||||
TRIANGLE_YOLO_CONF_TH = 0.7
|
TRIANGLE_YOLO_CONF_TH = 0.9
|
||||||
TRIANGLE_YOLO_IOU_TH = 0.45
|
TRIANGLE_YOLO_IOU_TH = 0.45
|
||||||
# YOLO 首次/临界帧可能在高阈值下 0 框;启用后仅在 0 候选时用较低阈值重试一次。
|
# YOLO 首次/临界帧可能在高阈值下 0 框;启用后仅在 0 候选时用较低阈值重试一次。
|
||||||
# 后续仍会经过 min_box_side、ROI aspect、三角形几何校验,避免直接放大假阳性。
|
# 后续仍会经过 min_box_side、ROI aspect、三角形几何校验,避免直接放大假阳性。
|
||||||
@@ -271,9 +271,9 @@ TRIANGLE_YOLO_PRELOAD_ON_BOOT = False
|
|||||||
|
|
||||||
# YOLO target size classification: class 0=20cm, class 1=40cm.
|
# YOLO target size classification: class 0=20cm, class 1=40cm.
|
||||||
TARGET_CLASS_YOLO_ENABLE = True
|
TARGET_CLASS_YOLO_ENABLE = True
|
||||||
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_285484.mud"
|
TARGET_CLASS_YOLO_MODEL_PATH = APP_DIR + "/model_317828.mud"
|
||||||
TARGET_CLASS_YOLO_LABELS = (20, 40)
|
TARGET_CLASS_YOLO_LABELS = (20, 40)
|
||||||
TARGET_CLASS_YOLO_CONF_TH = 0.50
|
TARGET_CLASS_YOLO_CONF_TH = 0.66
|
||||||
TARGET_CLASS_YOLO_IOU_TH = 0.45
|
TARGET_CLASS_YOLO_IOU_TH = 0.45
|
||||||
TARGET_CLASS_YOLO_RETRY_ON_EMPTY = False
|
TARGET_CLASS_YOLO_RETRY_ON_EMPTY = False
|
||||||
TARGET_CLASS_YOLO_RETRY_CONF_TH = 0.25
|
TARGET_CLASS_YOLO_RETRY_CONF_TH = 0.25
|
||||||
@@ -333,14 +333,17 @@ LOG_QUEUE_MAXSIZE = 10000 # 日志队列上限
|
|||||||
MAX_CMD_THREADS = 10 # 并发命令线程上限(防止服务器下发命令时无限创建线程)
|
MAX_CMD_THREADS = 10 # 并发命令线程上限(防止服务器下发命令时无限创建线程)
|
||||||
|
|
||||||
# ==================== 图像保存配置 ====================
|
# ==================== 图像保存配置 ====================
|
||||||
SAVE_IMAGE_ENABLED = False # 是否保存图像(True=保存,False=不保存)
|
SAVE_IMAGE_ENABLED = True # 是否保存图像(True=保存,False=不保存)
|
||||||
SAVE_IMAGE_ON_FAILURE = True # 检测失败时是否强制保存图像(供调试测试用)
|
SAVE_IMAGE_ON_FAILURE = False # 检测失败时是否强制保存图像(供调试测试用)
|
||||||
PHOTO_DIR = "/root/phot" # 照片存储目录
|
PHOTO_DIR = "/root/phot" # 照片存储目录
|
||||||
MAX_IMAGES = 1000
|
MAX_IMAGES = 1000
|
||||||
|
SAVE_RAW_IMAGE_ENABLED = False # 原图保存功能保留,但当前关闭
|
||||||
|
RAW_IMAGE_DIR = PHOTO_DIR + "/raw"
|
||||||
|
RAW_IMAGE_MAX_IMAGES = MAX_IMAGES
|
||||||
# Stage2 调试目录(默认 PHOTO_DIR/stage2_roi)内 JPEG 最多保留张数;None 表示与 MAX_IMAGES 相同
|
# Stage2 调试目录(默认 PHOTO_DIR/stage2_roi)内 JPEG 最多保留张数;None 表示与 MAX_IMAGES 相同
|
||||||
TRIANGLE_BLACK_YOLO_STAGE2_ROI_MAX_IMAGES = None
|
TRIANGLE_BLACK_YOLO_STAGE2_ROI_MAX_IMAGES = None
|
||||||
|
|
||||||
SHOW_CAMERA_PHOTO_WHILE_SHOOTING = False # 是否在拍摄时显示摄像头图像(True=显示,False=不显示),建议在连着USB测试过程中打开
|
SHOW_CAMERA_PHOTO_WHILE_SHOOTING = False # 关闭拍摄实时显示
|
||||||
|
|
||||||
# ==================== OTA配置 ====================
|
# ==================== OTA配置 ====================
|
||||||
MAX_BACKUPS = 5
|
MAX_BACKUPS = 5
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ def cmd_str():
|
|||||||
sync_system_time_from_4g()
|
sync_system_time_from_4g()
|
||||||
|
|
||||||
# 2.1 WiFi 热点配网兜底:仅当 STA 与 4G 均不可用时起 AP + HTTP;提交后删 /boot/wifi.ap、建 wifi.sta 并 reboot
|
# 2.1 WiFi 热点配网兜底:仅当 STA 与 4G 均不可用时起 AP + HTTP;提交后删 /boot/wifi.ap、建 wifi.sta 并 reboot
|
||||||
|
_ota_pending_path = f"{config.APP_DIR}/ota_pending.json"
|
||||||
try:
|
try:
|
||||||
from wifi_config_httpd import maybe_start_wifi_ap_fallback
|
from wifi_config_httpd import maybe_start_wifi_ap_fallback
|
||||||
|
|
||||||
@@ -171,8 +172,10 @@ def cmd_str():
|
|||||||
and bool(getattr(config, "TARGET_CLASS_YOLO_PRELOAD_ON_BOOT", True))
|
and bool(getattr(config, "TARGET_CLASS_YOLO_PRELOAD_ON_BOOT", True))
|
||||||
)
|
)
|
||||||
_preload_yolo = _preload_yolo or _need_black_preload or _need_target_preload
|
_preload_yolo = _preload_yolo or _need_black_preload or _need_target_preload
|
||||||
if _preload_yolo:
|
if _preload_yolo and not os.path.exists(f"{config.APP_DIR}/ota_pending.json"):
|
||||||
preload_yolo_detector(logger)
|
preload_yolo_detector(logger)
|
||||||
|
elif _preload_yolo and logger:
|
||||||
|
logger.warning("[YOLO] ota_pending.json found; skip model preload until rollback check")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if logger:
|
if logger:
|
||||||
logger.warning(f"[YOLO-ROI] 启动预加载异常(不影响后续射箭): {e}")
|
logger.warning(f"[YOLO-ROI] 启动预加载异常(不影响后续射箭): {e}")
|
||||||
@@ -254,7 +257,11 @@ def cmd_str():
|
|||||||
network_manager.read_device_id()
|
network_manager.read_device_id()
|
||||||
|
|
||||||
# 5. 创建照片存储目录(如果启用图像保存或检测失败时强制保存)
|
# 5. 创建照片存储目录(如果启用图像保存或检测失败时强制保存)
|
||||||
if config.SAVE_IMAGE_ENABLED or getattr(config, "SAVE_IMAGE_ON_FAILURE", False):
|
if (
|
||||||
|
config.SAVE_IMAGE_ENABLED
|
||||||
|
or getattr(config, "SAVE_IMAGE_ON_FAILURE", False)
|
||||||
|
or getattr(config, "SAVE_RAW_IMAGE_ENABLED", False)
|
||||||
|
):
|
||||||
photo_dir = config.PHOTO_DIR
|
photo_dir = config.PHOTO_DIR
|
||||||
if photo_dir not in os.listdir("/root"):
|
if photo_dir not in os.listdir("/root"):
|
||||||
try:
|
try:
|
||||||
@@ -286,12 +293,13 @@ def cmd_str():
|
|||||||
logger.info("系统准备完成...")
|
logger.info("系统准备完成...")
|
||||||
|
|
||||||
last_adc_trigger = 0
|
last_adc_trigger = 0
|
||||||
|
trigger_adc_val = 0 # 触发时的气压值,气压需降回此值以下才能再次触发
|
||||||
# 读取一次ADC初始值,防止开机时传感器已有压力导致误触发
|
# 读取一次ADC初始值,防止开机时传感器已有压力导致误触发
|
||||||
|
enable_check = True
|
||||||
try:
|
try:
|
||||||
last_adc_val = hardware_manager.adc_obj.read()
|
last_adc_val = hardware_manager.adc_obj.read()
|
||||||
except Exception:
|
except Exception:
|
||||||
last_adc_val = 0
|
last_adc_val = 0
|
||||||
peak_adc_val = 0 # 当前周期内的压力峰值
|
|
||||||
# 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样
|
# 气压采样:减少日志频率(每 N 个点输出一条),避免 logger.debug 拖慢采样
|
||||||
PRESSURE_BATCH_SIZE = 100
|
PRESSURE_BATCH_SIZE = 100
|
||||||
|
|
||||||
@@ -381,22 +389,16 @@ def cmd_str():
|
|||||||
pressure_max = adc_val
|
pressure_max = adc_val
|
||||||
if len(pressure_buf) >= PRESSURE_BATCH_SIZE:
|
if len(pressure_buf) >= PRESSURE_BATCH_SIZE:
|
||||||
_flush_pressure_buf("batch")
|
_flush_pressure_buf("batch")
|
||||||
# 峰值检测:压力从峰值下降时触发,确保捕获到最大冲击时刻
|
# 突变增量检测:压力增量大于300时触发
|
||||||
if adc_val > peak_adc_val:
|
# 触发后需等气压降到触发值以下才重新检测增量
|
||||||
peak_adc_val = adc_val # 更新峰值
|
if adc_val < trigger_adc_val :
|
||||||
if (peak_adc_val >= config.ADC_TRIGGER_THRESHOLD
|
enable_check = True
|
||||||
and adc_val < peak_adc_val
|
if (adc_val - last_adc_val) > 500 and enable_check:
|
||||||
and last_adc_val >= peak_adc_val):
|
|
||||||
# 封顶后下降沿触发:peak是最大值,当前值开始下降,且上次值还在peak位置
|
|
||||||
hardware_manager.start_idle_timer() # 重新计时
|
hardware_manager.start_idle_timer() # 重新计时
|
||||||
diff_ms = current_time - last_adc_trigger
|
|
||||||
if diff_ms < 3000:
|
|
||||||
peak_adc_val = 0 # 去抖期间重置峰值
|
|
||||||
time.sleep_ms(5)
|
|
||||||
continue
|
|
||||||
last_adc_trigger = current_time
|
last_adc_trigger = current_time
|
||||||
peak_adc_val = 0 # 触发后重置峰值
|
trigger_adc_val = adc_val # 记录触发时的气压值
|
||||||
# 触发前先把缓存刷出来,避免波形被长耗时处理截断
|
last_adc_val = adc_val # 更新基准值,防止连续增量误触发
|
||||||
|
enable_check = False
|
||||||
_flush_pressure_buf("before_trigger")
|
_flush_pressure_buf("before_trigger")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -415,7 +417,7 @@ def cmd_str():
|
|||||||
camera_manager.show(camera_manager.read_frame())
|
camera_manager.show(camera_manager.read_frame())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
time.sleep_ms(5)
|
time.sleep_ms(1)
|
||||||
last_adc_val = adc_val
|
last_adc_val = adc_val
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[basic]
|
[basic]
|
||||||
type = cvimodel
|
type = cvimodel
|
||||||
model = model_285484.cvimodel
|
model = model_317828.cvimodel
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
model_type = yolov5
|
model_type = yolov5
|
||||||
+13
-5
@@ -8,7 +8,7 @@ from laser_manager import laser_manager
|
|||||||
from logger_manager import logger_manager
|
from logger_manager import logger_manager
|
||||||
from network import network_manager
|
from network import network_manager
|
||||||
from triangle_target import load_camera_from_xml, load_triangle_positions, try_triangle_scoring
|
from triangle_target import load_camera_from_xml, load_triangle_positions, try_triangle_scoring
|
||||||
from vision import estimate_distance, detect_circle_v3, enqueue_save_shot
|
from vision import estimate_distance, detect_circle_v3, enqueue_save_shot, enqueue_save_raw_shot
|
||||||
from maix import image, time
|
from maix import image, time
|
||||||
|
|
||||||
# 缓存相机标定与三角形位置,避免每次射箭重复读磁盘
|
# 缓存相机标定与三角形位置,避免每次射箭重复读磁盘
|
||||||
@@ -322,6 +322,11 @@ def process_shot(adc_val):
|
|||||||
try:
|
try:
|
||||||
frame = camera_manager.read_frame()
|
frame = camera_manager.read_frame()
|
||||||
|
|
||||||
|
# 在任何检测和绘图之前复制原始帧;默认由配置关闭,不增加量产开销。
|
||||||
|
from shot_id_generator import shot_id_generator
|
||||||
|
shot_id = shot_id_generator.generate_id()
|
||||||
|
enqueue_save_raw_shot(frame, shot_id)
|
||||||
|
|
||||||
# 网络事件移到拍照之后,避免阻塞拍照
|
# 网络事件移到拍照之后,避免阻塞拍照
|
||||||
network_manager.safe_enqueue({"shoot_event": "start"}, msg_type=2, high=True)
|
network_manager.safe_enqueue({"shoot_event": "start"}, msg_type=2, high=True)
|
||||||
|
|
||||||
@@ -380,10 +385,6 @@ def process_shot(adc_val):
|
|||||||
if dx is None and dy is None and logger:
|
if dx is None and dy is None and logger:
|
||||||
logger.warning("[MAIN] 未检测到偏移量(三角形与圆形均失败),但会保存图像")
|
logger.warning("[MAIN] 未检测到偏移量(三角形与圆形均失败),但会保存图像")
|
||||||
|
|
||||||
# 生成射箭ID
|
|
||||||
from shot_id_generator import shot_id_generator
|
|
||||||
shot_id = shot_id_generator.generate_id()
|
|
||||||
|
|
||||||
if logger:
|
if logger:
|
||||||
logger.info(f"[MAIN] 射箭ID: {shot_id}")
|
logger.info(f"[MAIN] 射箭ID: {shot_id}")
|
||||||
|
|
||||||
@@ -441,6 +442,13 @@ def process_shot(adc_val):
|
|||||||
inner_data["ellipse_center_x"] = None
|
inner_data["ellipse_center_x"] = None
|
||||||
inner_data["ellipse_center_y"] = None
|
inner_data["ellipse_center_y"] = None
|
||||||
|
|
||||||
|
upload_time_ms = int(time_std.time() * 1000)
|
||||||
|
upload_time_sec, upload_time_millis = divmod(upload_time_ms, 1000)
|
||||||
|
inner_data["upload_time"] = (
|
||||||
|
time_std.strftime("%Y-%m-%d %H:%M:%S", time_std.localtime(upload_time_sec))
|
||||||
|
+ f".{upload_time_millis:03d}"
|
||||||
|
)
|
||||||
|
|
||||||
report_data = {"cmd": 1, "data": inner_data}
|
report_data = {"cmd": 1, "data": inner_data}
|
||||||
if logger:
|
if logger:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
Binary file not shown.
+5
-1
@@ -30,4 +30,8 @@
|
|||||||
# 2.15.17 修复wifi连接问题
|
# 2.15.17 修复wifi连接问题
|
||||||
# 2.15.18 wifi连接成功重新登录
|
# 2.15.18 wifi连接成功重新登录
|
||||||
# 2.16.4 优化射箭延迟
|
# 2.16.4 优化射箭延迟
|
||||||
# 2.17.0 yolo标靶类别识别
|
# 2.17.0 yolo标靶类别识别
|
||||||
|
# 2.17.1 26-08-19 17:39 压力传感修改 增量方式
|
||||||
|
# 2.17.2 26-08-24 17:56 靶纸识别模型更替
|
||||||
|
# 2.17.3 26-08-25 9:57 原图拍摄开关
|
||||||
|
# 2.17.4 26-08-25 14:57 模型修改
|
||||||
|
|||||||
+1
-1
@@ -4,6 +4,6 @@
|
|||||||
应用版本号
|
应用版本号
|
||||||
每次 OTA 更新时,只需要更新这个文件中的版本号
|
每次 OTA 更新时,只需要更新这个文件中的版本号
|
||||||
"""
|
"""
|
||||||
VERSION = '3.0.0'
|
VERSION = '2.18.2'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -902,13 +902,16 @@ def _save_shot_image_impl(img_cv, center, radius, method, ellipse_params,
|
|||||||
|
|
||||||
|
|
||||||
def _save_worker_loop():
|
def _save_worker_loop():
|
||||||
"""存图 worker:从队列取任务并调用 _save_shot_image_impl。"""
|
"""存图 worker:处理标注图和可选的纯原图任务。"""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
item = _save_queue.get()
|
item = _save_queue.get()
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
_save_shot_image_impl(*item)
|
if isinstance(item, dict) and item.get("kind") == "raw":
|
||||||
|
_save_raw_image_impl(item["img_cv"], item["shot_id"], item["photo_dir"])
|
||||||
|
else:
|
||||||
|
_save_shot_image_impl(*item)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger = logger_manager.logger
|
logger = logger_manager.logger
|
||||||
if logger:
|
if logger:
|
||||||
@@ -936,6 +939,52 @@ def start_save_shot_worker():
|
|||||||
logger.info("[VISION] 存图 worker 线程已启动")
|
logger.info("[VISION] 存图 worker 线程已启动")
|
||||||
|
|
||||||
|
|
||||||
|
def _save_raw_image_impl(img_cv, shot_id, photo_dir):
|
||||||
|
"""保存未标注、未裁剪的完整原始帧。"""
|
||||||
|
logger = logger_manager.logger
|
||||||
|
try:
|
||||||
|
os.makedirs(photo_dir, exist_ok=True)
|
||||||
|
filename = os.path.join(photo_dir, f"shot_{shot_id}_raw.jpg")
|
||||||
|
image.cv2image(img_cv, False, False).save(filename)
|
||||||
|
prune_old_images_in_dir(
|
||||||
|
photo_dir,
|
||||||
|
getattr(config, "RAW_IMAGE_MAX_IMAGES", config.MAX_IMAGES),
|
||||||
|
logger,
|
||||||
|
"[VISION-RAW]",
|
||||||
|
)
|
||||||
|
if logger:
|
||||||
|
logger.info(f"[VISION-RAW] 已保存纯原图: {filename}")
|
||||||
|
return filename
|
||||||
|
except Exception as e:
|
||||||
|
if logger:
|
||||||
|
logger.error(f"[VISION-RAW] 保存纯原图失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def enqueue_save_raw_shot(frame, shot_id, photo_dir=None):
|
||||||
|
"""复制并异步保存原始帧;由 SAVE_RAW_IMAGE_ENABLED 控制是否启用。"""
|
||||||
|
if not getattr(config, "SAVE_RAW_IMAGE_ENABLED", False):
|
||||||
|
return
|
||||||
|
if photo_dir is None:
|
||||||
|
photo_dir = getattr(config, "RAW_IMAGE_DIR", os.path.join(config.PHOTO_DIR, "raw"))
|
||||||
|
try:
|
||||||
|
img_copy = np.copy(image.image2cv(frame, False, False))
|
||||||
|
_save_queue.put_nowait({
|
||||||
|
"kind": "raw",
|
||||||
|
"img_cv": img_copy,
|
||||||
|
"shot_id": shot_id,
|
||||||
|
"photo_dir": photo_dir,
|
||||||
|
})
|
||||||
|
except queue.Full:
|
||||||
|
logger = logger_manager.logger
|
||||||
|
if logger:
|
||||||
|
logger.warning("[VISION-RAW] 存图队列已满,跳过本次纯原图保存")
|
||||||
|
except Exception as e:
|
||||||
|
logger = logger_manager.logger
|
||||||
|
if logger:
|
||||||
|
logger.error(f"[VISION-RAW] 复制纯原图失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
def enqueue_save_shot(result_img, center, radius, method, ellipse_params,
|
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, force_save=False):
|
yolo_roi_xyxy=None, force_save=False):
|
||||||
|
|||||||
Reference in New Issue
Block a user