triangle algo
This commit is contained in:
199
main.py
199
main.py
@@ -21,24 +21,25 @@ from version import VERSION
|
|||||||
# from logger import init_logging, get_logger, stop_logging
|
# from logger import init_logging, get_logger, stop_logging
|
||||||
from logger_manager import logger_manager
|
from logger_manager import logger_manager
|
||||||
from time_sync import sync_system_time_from_4g
|
from time_sync import sync_system_time_from_4g
|
||||||
from power import init_ina226, get_bus_voltage, voltage_to_percent
|
from power import init_ina226
|
||||||
from laser_manager import laser_manager
|
from laser_manager import laser_manager
|
||||||
from vision import detect_circle_v3, estimate_distance, enqueue_save_shot, start_save_shot_worker
|
from vision import start_save_shot_worker
|
||||||
from network import network_manager
|
from network import network_manager
|
||||||
from ota_manager import ota_manager
|
from ota_manager import ota_manager
|
||||||
from hardware import hardware_manager
|
from hardware import hardware_manager
|
||||||
from camera_manager import camera_manager
|
from camera_manager import camera_manager
|
||||||
|
from shoot_manager import process_shot, preload_triangle_calib
|
||||||
|
|
||||||
|
|
||||||
def laser_calibration_worker():
|
def laser_calibration_worker():
|
||||||
"""后台线程:持续检测是否需要执行激光校准"""
|
"""后台线程:持续检测是否需要执行激光校准"""
|
||||||
from laser_manager import laser_manager
|
from laser_manager import laser_manager
|
||||||
from ota_manager import ota_manager
|
from ota_manager import ota_manager
|
||||||
|
|
||||||
logger = logger_manager.logger
|
logger = logger_manager.logger
|
||||||
if logger:
|
if logger:
|
||||||
logger.info("[LASER] 激光校准线程启动")
|
logger.info("[LASER] 激光校准线程启动")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -55,7 +56,7 @@ def laser_calibration_worker():
|
|||||||
if laser_manager.calibration_active:
|
if laser_manager.calibration_active:
|
||||||
# 调用校准方法,所有逻辑都在 LaserManager 中
|
# 调用校准方法,所有逻辑都在 LaserManager 中
|
||||||
result = laser_manager.calibrate_laser_position(timeout_ms=8000, check_sharpness=True)
|
result = laser_manager.calibrate_laser_position(timeout_ms=8000, check_sharpness=True)
|
||||||
|
|
||||||
# 如果超时仍未成功,稍微休息一下
|
# 如果超时仍未成功,稍微休息一下
|
||||||
if laser_manager.calibration_active:
|
if laser_manager.calibration_active:
|
||||||
time.sleep_ms(300)
|
time.sleep_ms(300)
|
||||||
@@ -78,37 +79,38 @@ def cmd_str():
|
|||||||
"""主程序入口"""
|
"""主程序入口"""
|
||||||
# ==================== 第一阶段:硬件初始化 ====================
|
# ==================== 第一阶段:硬件初始化 ====================
|
||||||
# 按照 main104.py 的顺序,先完成所有硬件初始化
|
# 按照 main104.py 的顺序,先完成所有硬件初始化
|
||||||
|
|
||||||
# 1. 引脚功能映射
|
# 1. 引脚功能映射
|
||||||
for pin, func in config.PIN_MAPPINGS.items():
|
for pin, func in config.PIN_MAPPINGS.items():
|
||||||
try:
|
try:
|
||||||
pinmap.set_pin_function(pin, func)
|
pinmap.set_pin_function(pin, func)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 2. 初始化硬件对象(UART、I2C、ADC)
|
# 2. 初始化硬件对象(UART、I2C、ADC)
|
||||||
hardware_manager.init_uart4g()
|
hardware_manager.init_uart4g()
|
||||||
hardware_manager.init_bus()
|
hardware_manager.init_bus()
|
||||||
hardware_manager.init_adc()
|
hardware_manager.init_adc()
|
||||||
hardware_manager.init_at_client()
|
hardware_manager.init_at_client()
|
||||||
|
|
||||||
# 3. 初始化激光模块(串口 + 开机关闭激光防误触发)
|
# 3. 初始化激光模块(串口 + 开机关闭激光防误触发)
|
||||||
laser_manager.init()
|
laser_manager.init()
|
||||||
|
|
||||||
# 3. 初始化 INA226 电量监测芯片
|
# 3. 初始化 INA226 电量监测芯片
|
||||||
init_ina226()
|
init_ina226()
|
||||||
|
|
||||||
# 4. 初始化显示和相机
|
# 4. 初始化显示和相机
|
||||||
camera_manager.init_camera(640, 480)
|
camera_manager.init_camera(640, 480)
|
||||||
|
# camera_manager.init_camera(1280,720)
|
||||||
camera_manager.init_display()
|
camera_manager.init_display()
|
||||||
|
|
||||||
# ==================== 第二阶段:软件初始化 ====================
|
# ==================== 第二阶段:软件初始化 ====================
|
||||||
|
|
||||||
# 1. 初始化日志系统
|
# 1. 初始化日志系统
|
||||||
import logging
|
import logging
|
||||||
logger_manager.init_logging(log_level=logging.DEBUG)
|
logger_manager.init_logging(log_level=logging.DEBUG)
|
||||||
logger = logger_manager.logger
|
logger = logger_manager.logger
|
||||||
|
|
||||||
# 补充:因为初始化的时候,激光会亮,先关了它
|
# 补充:因为初始化的时候,激光会亮,先关了它
|
||||||
# laser_manager.turn_off_laser()
|
# laser_manager.turn_off_laser()
|
||||||
|
|
||||||
@@ -126,25 +128,31 @@ def cmd_str():
|
|||||||
|
|
||||||
# 2.5. 启动存图 worker 线程(队列 + worker,避免主循环阻塞)
|
# 2.5. 启动存图 worker 线程(队列 + worker,避免主循环阻塞)
|
||||||
start_save_shot_worker()
|
start_save_shot_worker()
|
||||||
|
|
||||||
|
# 2.6 预加载三角形标定/坐标文件(避免首次射箭卡顿)
|
||||||
|
try:
|
||||||
|
preload_triangle_calib()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# 3. 启动时检查:是否需要恢复备份
|
# 3. 启动时检查:是否需要恢复备份
|
||||||
pending_path = f"{config.APP_DIR}/ota_pending.json"
|
pending_path = f"{config.APP_DIR}/ota_pending.json"
|
||||||
if os.path.exists(pending_path):
|
if os.path.exists(pending_path):
|
||||||
try:
|
try:
|
||||||
with open(pending_path, 'r', encoding='utf-8') as f:
|
with open(pending_path, 'r', encoding='utf-8') as f:
|
||||||
pending_obj = json.load(f)
|
pending_obj = json.load(f)
|
||||||
|
|
||||||
restart_count = pending_obj.get('restart_count', 0)
|
restart_count = pending_obj.get('restart_count', 0)
|
||||||
max_restarts = pending_obj.get('max_restarts', 3)
|
max_restarts = pending_obj.get('max_restarts', 3)
|
||||||
backup_dir = pending_obj.get('backup_dir')
|
backup_dir = pending_obj.get('backup_dir')
|
||||||
|
|
||||||
if logger:
|
if logger:
|
||||||
logger.info(f"检测到 ota_pending.json,重启计数: {restart_count}/{max_restarts}")
|
logger.info(f"检测到 ota_pending.json,重启计数: {restart_count}/{max_restarts}")
|
||||||
|
|
||||||
if restart_count >= max_restarts:
|
if restart_count >= max_restarts:
|
||||||
if logger:
|
if logger:
|
||||||
logger.error(f"[STARTUP] 重启次数 ({restart_count}) 超过阈值 ({max_restarts}),执行恢复...")
|
logger.error(f"[STARTUP] 重启次数 ({restart_count}) 超过阈值 ({max_restarts}),执行恢复...")
|
||||||
|
|
||||||
if backup_dir and os.path.exists(backup_dir):
|
if backup_dir and os.path.exists(backup_dir):
|
||||||
if ota_manager.restore_from_backup(backup_dir):
|
if ota_manager.restore_from_backup(backup_dir):
|
||||||
if logger:
|
if logger:
|
||||||
@@ -160,7 +168,7 @@ def cmd_str():
|
|||||||
else:
|
else:
|
||||||
if logger:
|
if logger:
|
||||||
logger.error(f"[STARTUP] 恢复备份失败")
|
logger.error(f"[STARTUP] 恢复备份失败")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.remove(pending_path)
|
os.remove(pending_path)
|
||||||
if logger:
|
if logger:
|
||||||
@@ -168,7 +176,7 @@ def cmd_str():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if logger:
|
if logger:
|
||||||
logger.error(f"[STARTUP] 删除 pending 文件失败: {e}")
|
logger.error(f"[STARTUP] 删除 pending 文件失败: {e}")
|
||||||
|
|
||||||
if logger:
|
if logger:
|
||||||
logger.info(f"[STARTUP] 恢复完成,准备重启系统...")
|
logger.info(f"[STARTUP] 恢复完成,准备重启系统...")
|
||||||
time.sleep_ms(2000)
|
time.sleep_ms(2000)
|
||||||
@@ -199,10 +207,10 @@ def cmd_str():
|
|||||||
return
|
return
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 4. 初始化设备ID(network_manager 内部会自动设置 device_id 和 password)
|
# 4. 初始化设备ID(network_manager 内部会自动设置 device_id 和 password)
|
||||||
network_manager.read_device_id()
|
network_manager.read_device_id()
|
||||||
|
|
||||||
# 5. 创建照片存储目录(如果启用图像保存)
|
# 5. 创建照片存储目录(如果启用图像保存)
|
||||||
if config.SAVE_IMAGE_ENABLED:
|
if config.SAVE_IMAGE_ENABLED:
|
||||||
photo_dir = config.PHOTO_DIR
|
photo_dir = config.PHOTO_DIR
|
||||||
@@ -281,7 +289,7 @@ def cmd_str():
|
|||||||
while not app.need_exit():
|
while not app.need_exit():
|
||||||
try:
|
try:
|
||||||
current_time = time.ticks_ms()
|
current_time = time.ticks_ms()
|
||||||
|
|
||||||
# OTA 期间暂停相机预览
|
# OTA 期间暂停相机预览
|
||||||
try:
|
try:
|
||||||
if ota_manager.ota_in_progress:
|
if ota_manager.ota_in_progress:
|
||||||
@@ -346,146 +354,9 @@ def cmd_str():
|
|||||||
last_adc_trigger = current_time
|
last_adc_trigger = current_time
|
||||||
# 触发前先把缓存刷出来,避免波形被长耗时处理截断
|
# 触发前先把缓存刷出来,避免波形被长耗时处理截断
|
||||||
_flush_pressure_buf("before_trigger")
|
_flush_pressure_buf("before_trigger")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
frame = camera_manager.read_frame()
|
process_shot(adc_val)
|
||||||
|
|
||||||
laser_point_method = None # 记录激光点选择方法
|
|
||||||
if config.HARDCODE_LASER_POINT:
|
|
||||||
# 硬编码模式:使用硬编码值
|
|
||||||
laser_point = laser_manager.laser_point
|
|
||||||
laser_point_method = "hardcode"
|
|
||||||
elif laser_manager.has_calibrated_point():
|
|
||||||
# 假如校准过,并且有保存值,使用校准值
|
|
||||||
laser_point = laser_manager.laser_point
|
|
||||||
laser_point_method = "calibrated"
|
|
||||||
logger_manager.logger.info(f"[算法] 使用校准值: {laser_manager.laser_point}")
|
|
||||||
elif distance_m and distance_m > 0:
|
|
||||||
# 动态计算模式:根据距离计算激光点
|
|
||||||
# 先检测靶心以获取距离(用于计算激光点)
|
|
||||||
# 第一次检测不使用激光点,仅用于获取距离
|
|
||||||
result_img_temp, center_temp, radius_temp, method_temp, best_radius1_temp, ellipse_params_temp = detect_circle_v3(frame, None)
|
|
||||||
# 计算距离
|
|
||||||
distance_m = estimate_distance(best_radius1_temp) if best_radius1_temp else None
|
|
||||||
laser_point = laser_manager.calculate_laser_point_from_distance(distance_m)
|
|
||||||
laser_point_method = "dynamic"
|
|
||||||
if laser_point is None:
|
|
||||||
logger = logger_manager.logger
|
|
||||||
if logger:
|
|
||||||
logger.warning("[MAIN] 激光点未初始化,跳过本次检测")
|
|
||||||
time.sleep_ms(100)
|
|
||||||
continue
|
|
||||||
|
|
||||||
x, y = laser_point
|
|
||||||
|
|
||||||
# 检测靶心
|
|
||||||
result_img, center, radius, method, best_radius1, ellipse_params = detect_circle_v3(frame, laser_point)
|
|
||||||
|
|
||||||
if config.SHOW_CAMERA_PHOTO_WHILE_SHOOTING:
|
|
||||||
camera_manager.show(result_img)
|
|
||||||
|
|
||||||
# 计算偏移与距离(如果检测到靶心)
|
|
||||||
if center and radius:
|
|
||||||
dx, dy = laser_manager.compute_laser_position(center, (x, y), radius, method)
|
|
||||||
distance_m = estimate_distance(best_radius1)
|
|
||||||
else:
|
|
||||||
# 未检测到靶心
|
|
||||||
dx, dy = None, None
|
|
||||||
distance_m = None
|
|
||||||
if logger:
|
|
||||||
logger.warning("[MAIN] 未检测到靶心,但会保存图像")
|
|
||||||
|
|
||||||
# 快速激光测距(激光一闪而过,约500-600ms)
|
|
||||||
laser_distance_m = None
|
|
||||||
laser_signal_quality = 0
|
|
||||||
# try:
|
|
||||||
# result = laser_manager.quick_measure_distance()
|
|
||||||
# if isinstance(result, tuple) and len(result) == 2:
|
|
||||||
# laser_distance_m, laser_signal_quality = result
|
|
||||||
# else:
|
|
||||||
# # 向后兼容:如果返回的是单个值
|
|
||||||
# laser_distance_m = result if isinstance(result, (int, float)) else 0.0
|
|
||||||
# laser_signal_quality = 0
|
|
||||||
# if logger:
|
|
||||||
# if laser_distance_m > 0:
|
|
||||||
# logger.info(f"[MAIN] 激光测距成功: {laser_distance_m:.3f} m, 信号质量: {laser_signal_quality}")
|
|
||||||
# else:
|
|
||||||
# logger.warning("[MAIN] 激光测距失败或返回0")
|
|
||||||
# except Exception as e:
|
|
||||||
# if logger:
|
|
||||||
# logger.error(f"[MAIN] 激光测距异常: {e}")
|
|
||||||
|
|
||||||
# 读取电量
|
|
||||||
voltage = get_bus_voltage()
|
|
||||||
battery_percent = voltage_to_percent(voltage)
|
|
||||||
|
|
||||||
# 生成射箭ID
|
|
||||||
from shot_id_generator import shot_id_generator
|
|
||||||
shot_id = shot_id_generator.generate_id() # 不需要使用device_id
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 构造上报数据
|
|
||||||
inner_data = {
|
|
||||||
"shot_id": shot_id, # 射箭ID,用于关联图片和服务端日志
|
|
||||||
"x": float(dx) if dx is not None else 200.0,
|
|
||||||
"y": float(dy) if dy is not None else 200.0,
|
|
||||||
"r": 90.0,
|
|
||||||
"d": round((distance_m or 0.0) * 100), # 视觉测距值(厘米)
|
|
||||||
"d_laser": round((laser_distance_m or 0.0) * 100), # 激光测距值(厘米)
|
|
||||||
"d_laser_quality": laser_signal_quality, # 激光测距信号质量
|
|
||||||
"m": method if method else "no_target",
|
|
||||||
"adc": adc_val,
|
|
||||||
# 新增字段:激光点选择方法
|
|
||||||
"laser_method": laser_point_method, # 激光点选择方法:hardcode/calibrated/dynamic/default
|
|
||||||
# 激光点坐标(像素)
|
|
||||||
"target_x": float(x), # 激光点 X 坐标(像素)
|
|
||||||
"target_y": float(y), # 激光点 Y 坐标(像素)
|
|
||||||
}
|
|
||||||
|
|
||||||
# 添加椭圆参数(如果存在)
|
|
||||||
if ellipse_params:
|
|
||||||
(ell_center, (width, height), angle) = ellipse_params
|
|
||||||
inner_data["ellipse_major_axis"] = float(max(width, height)) # 长轴(像素)
|
|
||||||
inner_data["ellipse_minor_axis"] = float(min(width, height)) # 短轴(像素)
|
|
||||||
inner_data["ellipse_angle"] = float(angle) # 椭圆角度(度)
|
|
||||||
inner_data["ellipse_center_x"] = float(ell_center[0]) # 椭圆中心 X 坐标(像素)
|
|
||||||
inner_data["ellipse_center_y"] = float(ell_center[1]) # 椭圆中心 Y 坐标(像素)
|
|
||||||
else:
|
|
||||||
inner_data["ellipse_major_axis"] = None
|
|
||||||
inner_data["ellipse_minor_axis"] = None
|
|
||||||
inner_data["ellipse_angle"] = None
|
|
||||||
inner_data["ellipse_center_x"] = None
|
|
||||||
inner_data["ellipse_center_y"] = None
|
|
||||||
|
|
||||||
report_data = {"cmd": 1, "data": inner_data}
|
|
||||||
network_manager.safe_enqueue(report_data, msg_type=2, high=True)
|
|
||||||
# 闪一下激光(射箭反馈)
|
|
||||||
if config.FLASH_LASER_WHILE_SHOOTING:
|
|
||||||
laser_manager.flash_laser(config.FLASH_LASER_DURATION_MS)
|
|
||||||
|
|
||||||
# 保存图像(无论是否检测到靶心都保存):放入队列由 worker 异步保存,不阻塞主循环
|
|
||||||
enqueue_save_shot(
|
|
||||||
result_img,
|
|
||||||
center,
|
|
||||||
radius,
|
|
||||||
method,
|
|
||||||
ellipse_params,
|
|
||||||
(x, y),
|
|
||||||
distance_m,
|
|
||||||
shot_id=shot_id,
|
|
||||||
photo_dir=config.PHOTO_DIR if config.SAVE_IMAGE_ENABLED else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if center and radius:
|
|
||||||
logger.info(f"射箭事件已加入发送队列(已检测到靶心),ID: {shot_id}")
|
|
||||||
else:
|
|
||||||
logger.info(f"射箭事件已加入发送队列(未检测到靶心,已保存图像),ID: {shot_id}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
time.sleep_ms(100)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger = logger_manager.logger
|
logger = logger_manager.logger
|
||||||
if logger:
|
if logger:
|
||||||
@@ -537,13 +408,13 @@ if __name__ == "__main__":
|
|||||||
# 用于测试图片清晰度
|
# 用于测试图片清晰度
|
||||||
# 方式1: 测试单张图片
|
# 方式1: 测试单张图片
|
||||||
# test_sharpness("/root/phot/image.bmp")
|
# test_sharpness("/root/phot/image.bmp")
|
||||||
|
|
||||||
# 方式2: 测试目录下所有图片
|
# 方式2: 测试目录下所有图片
|
||||||
# test_sharpness("/root/phot")
|
# test_sharpness("/root/phot")
|
||||||
|
|
||||||
# 方式3: 使用默认路径(config.PHOTO_DIR)
|
# 方式3: 使用默认路径(config.PHOTO_DIR)
|
||||||
# test_sharpness("/root/phot/")
|
# test_sharpness("/root/phot/")
|
||||||
|
|
||||||
# 用于测试激光点清晰度
|
# 用于测试激光点清晰度
|
||||||
# 方式1: 测试单张图片
|
# 方式1: 测试单张图片
|
||||||
# test_laser_point_sharpness("/root/phot/image.bmp")
|
# test_laser_point_sharpness("/root/phot/image.bmp")
|
||||||
|
|||||||
Reference in New Issue
Block a user