|
|
@@ -258,6 +258,9 @@ class Coordinator:
|
|
|
|
|
|
# 控制标志
|
|
|
self.running = False
|
|
|
+ self._paused = False
|
|
|
+ self._paused_event = threading.Event()
|
|
|
+ self._paused_event.set() # 默认非暂停状态
|
|
|
self.coordinator_thread = None
|
|
|
|
|
|
# OCR频率控制
|
|
|
@@ -290,6 +293,22 @@ class Coordinator:
|
|
|
def set_calibrator(self, calibrator):
|
|
|
"""设置校准器"""
|
|
|
self.calibrator = calibrator
|
|
|
+
|
|
|
+ def pause_detection(self):
|
|
|
+ """暂停检测(校准时使用,线程不退出,仅跳过检测逻辑)"""
|
|
|
+ self._paused = True
|
|
|
+ self._paused_event.clear()
|
|
|
+ logger.info("[协调器] 检测已暂停")
|
|
|
+
|
|
|
+ def resume_detection(self):
|
|
|
+ """恢复检测(校准完成后恢复)"""
|
|
|
+ self._paused = False
|
|
|
+ self._paused_event.set()
|
|
|
+ logger.info("[协调器] 检测已恢复")
|
|
|
+
|
|
|
+ def is_paused(self) -> bool:
|
|
|
+ """检测是否暂停"""
|
|
|
+ return self._paused
|
|
|
|
|
|
def _transform_position(self, x_ratio: float, y_ratio: float) -> Tuple[float, float, int]:
|
|
|
"""
|
|
|
@@ -303,6 +322,8 @@ class Coordinator:
|
|
|
if self.enable_calibration and self.calibrator and self.calibrator.is_calibrated():
|
|
|
# 使用校准结果进行转换
|
|
|
pan, tilt = self.calibrator.transform(x_ratio, y_ratio)
|
|
|
+ # 应用tilt偏移补偿(校准系统性偏差)
|
|
|
+ tilt += PTZ_CONFIG.get('tilt_offset', 0)
|
|
|
zoom = 8 # 默认变倍
|
|
|
else:
|
|
|
# 使用默认估算
|
|
|
@@ -393,6 +414,9 @@ class Coordinator:
|
|
|
|
|
|
def _coordinator_worker(self):
|
|
|
"""联动工作线程"""
|
|
|
+ # 暂停时阻塞等待恢复,不消耗CPU
|
|
|
+ self._paused_event.wait()
|
|
|
+
|
|
|
last_detection_time = 0
|
|
|
# 从 DETECTION_CONFIG 获取检测帧率,默认每秒2帧
|
|
|
detection_fps = self.config.get('detection_fps', DETECTION_CONFIG.get('detection_fps', 2))
|
|
|
@@ -417,8 +441,8 @@ class Coordinator:
|
|
|
|
|
|
frame_size = (frame.shape[1], frame.shape[0])
|
|
|
|
|
|
- # 周期性检测
|
|
|
- if current_time - last_detection_time >= detection_interval:
|
|
|
+ # 周期性检测(暂停时跳过)
|
|
|
+ if not self._paused and current_time - last_detection_time >= detection_interval:
|
|
|
last_detection_time = current_time
|
|
|
|
|
|
# 检测人体
|
|
|
@@ -435,8 +459,9 @@ class Coordinator:
|
|
|
if detections:
|
|
|
self._process_detections(detections, frame, frame_size)
|
|
|
|
|
|
- # 处理当前跟踪目标
|
|
|
- self._process_current_target(frame, frame_size)
|
|
|
+ # 处理当前跟踪目标(暂停时跳过PTZ控制)
|
|
|
+ if not self._paused:
|
|
|
+ self._process_current_target(frame, frame_size)
|
|
|
|
|
|
# 清理过期目标
|
|
|
self._cleanup_expired_targets()
|
|
|
@@ -946,6 +971,9 @@ class AsyncCoordinator(Coordinator):
|
|
|
|
|
|
def _detection_worker(self):
|
|
|
"""检测线程:持续读帧 + YOLO推理 + 发送PTZ命令 + 打印检测日志"""
|
|
|
+ # 暂停时阻塞等待恢复
|
|
|
+ self._paused_event.wait()
|
|
|
+
|
|
|
last_detection_time = 0
|
|
|
# 从 DETECTION_CONFIG 获取检测帧率,默认每秒2帧
|
|
|
detection_fps = self.config.get('detection_fps', DETECTION_CONFIG.get('detection_fps', 2))
|
|
|
@@ -1004,8 +1032,8 @@ class AsyncCoordinator(Coordinator):
|
|
|
frame_count = 0
|
|
|
last_log_time = current_time
|
|
|
|
|
|
- # 周期性检测(约1次/秒)
|
|
|
- if current_time - last_detection_time >= detection_interval:
|
|
|
+ # 周期性检测(暂停时跳过检测和PTZ命令)
|
|
|
+ if not self._paused and current_time - last_detection_time >= detection_interval:
|
|
|
last_detection_time = current_time
|
|
|
detection_run_count += 1
|
|
|
|
|
|
@@ -1291,11 +1319,16 @@ class AsyncCoordinator(Coordinator):
|
|
|
"""PTZ控制线程:从队列接收命令并控制球机"""
|
|
|
while self.running:
|
|
|
try:
|
|
|
+ # 暂停时等待恢复
|
|
|
+ if self._paused:
|
|
|
+ self._paused_event.wait()
|
|
|
+ continue
|
|
|
+
|
|
|
try:
|
|
|
cmd = self._ptz_queue.get(timeout=0.1)
|
|
|
except queue.Empty:
|
|
|
continue
|
|
|
-
|
|
|
+
|
|
|
# 执行PTZ命令(batch_id 和 person_index 已在命令中)
|
|
|
self._execute_ptz_command(cmd)
|
|
|
|
|
|
@@ -1769,8 +1802,8 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
logger.debug(f"[顺序模式] 清空上一轮跟踪目标: {len(self.tracking_targets)} 个")
|
|
|
self.tracking_targets.clear()
|
|
|
|
|
|
- # 空闲状态:周期性检测
|
|
|
- if current_time - last_detection_time >= detection_interval:
|
|
|
+ # 空闲状态:周期性检测(暂停时跳过)
|
|
|
+ if not self._paused and current_time - last_detection_time >= detection_interval:
|
|
|
last_detection_time = current_time
|
|
|
detection_run_count += 1
|
|
|
|
|
|
@@ -1853,9 +1886,15 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
def _ptz_worker(self):
|
|
|
"""PTZ控制线程:顺序模式下的PTZ控制逻辑"""
|
|
|
logger.info("[PTZ线程] 顺序模式PTZ控制线程启动")
|
|
|
-
|
|
|
+
|
|
|
while self.running:
|
|
|
try:
|
|
|
+ # 暂停时等待恢复
|
|
|
+ if self._paused:
|
|
|
+ self._paused_event.wait()
|
|
|
+ time.sleep(0.05)
|
|
|
+ continue
|
|
|
+
|
|
|
state = self._get_capture_state()
|
|
|
|
|
|
if state == 'capturing':
|
|
|
@@ -2078,17 +2117,17 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
self.tracking_targets.clear()
|
|
|
logger.info("[顺序模式] 已清空跟踪目标列表")
|
|
|
|
|
|
- def _save_local_snapshot(self, frame: np.ndarray, index: int,
|
|
|
+ def _save_local_snapshot(self, frame: np.ndarray, index: int,
|
|
|
pan: float, tilt: float, zoom: int):
|
|
|
- """保存本地快照"""
|
|
|
+ """保存本地快照,返回文件路径"""
|
|
|
try:
|
|
|
import os
|
|
|
from datetime import datetime
|
|
|
-
|
|
|
+
|
|
|
# 创建保存目录
|
|
|
save_dir = '/home/admin/dsh/captures'
|
|
|
os.makedirs(save_dir, exist_ok=True)
|
|
|
-
|
|
|
+
|
|
|
# 生成文件名 - 使用 PNG 无损格式
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
|
|
|
filename = f"capture_{timestamp}_person{index}_p{int(pan)}_t{int(tilt)}_z{zoom}.png"
|
|
|
@@ -2097,9 +2136,16 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
# 保存图片 - 使用 PNG 无损格式
|
|
|
cv2.imwrite(filepath, frame)
|
|
|
logger.info(f"[顺序模式] 快照已保存: {filepath}")
|
|
|
-
|
|
|
+
|
|
|
+ # 记录到配对保存器,批次完成时删除
|
|
|
+ if self._paired_saver is not None and self._current_batch_id:
|
|
|
+ self._paired_saver.add_capture_path(self._current_batch_id, filepath)
|
|
|
+
|
|
|
+ return filepath
|
|
|
+
|
|
|
except Exception as e:
|
|
|
logger.error(f"[顺序模式] 保存快照失败: {e}")
|
|
|
+ return None
|
|
|
|
|
|
def set_capture_config(self, **kwargs):
|
|
|
"""设置抓拍配置"""
|