Selaa lähdekoodia

refactor(coordinator): 重构联动控制器及异步控制逻辑

- 大幅重构协调器代码,优化类结构和线程管理
- 将检测与PTZ控制线程分离,提升异步处理能力
- 引入目标选择器,支持多策略目标评分与选择
- 实现PTZ命令队列,避免命令冲突和频繁操作
- 优化人体检测、OCR识别与追踪目标更新流程
- 支持事件驱动控制器,响应入侵和越界等事件
- 新增配对图片保存机制,支持关联全景和PTZ图像
- 改进状态管理及性能统计,增强系统健壮性
wenhongquan 2 päivää sitten
vanhempi
commit
d4a2b6a53f

+ 1 - 1
dual_camera_system/config/detection.py

@@ -7,7 +7,7 @@ DETECTION_CONFIG = {
     'target_classes': ['person'],   # 检测目标类别 (支持中英文)
     'confidence_threshold': 0.5,     # 置信度阈值
     'detection_fps': 2,              # 检测帧率(每秒检测帧数),替代原来的detection_interval
-    'detection_interval': 0.5,       # 兼容保留:检测间隔(秒),当detection_fps=2时间隔为0.5秒
+    'detection_interval': 4,       # 兼容保留:检测间隔(秒),当detection_fps=2时间隔为0.5秒
     
     # 检测图片保存配置
     'save_detection_image': False,   # 是否保存检测到人的图片

+ 27 - 30
dual_camera_system/coordinator.py

@@ -15,7 +15,7 @@ import numpy as np
 import cv2
 
 from config import COORDINATOR_CONFIG, SYSTEM_CONFIG, PTZ_CONFIG, DETECTION_CONFIG
-from panorama_camera import PanoramaCamera, ObjectDetector, PersonTracker, DetectedObject
+from panorama_camera import PanoramaCamera, ObjectDetector, DetectedObject
 from ptz_camera import PTZCamera, PTZController
 from ocr_recognizer import NumberDetector, PersonInfo
 from ptz_person_tracker import PTZPersonDetector, PTZAutoZoomController
@@ -238,9 +238,6 @@ class Coordinator:
         self.ptz_detector = None
         self.auto_zoom_controller = None
         
-        # 跟踪器
-        self.tracker = PersonTracker()
-        
         # 状态
         self.state = TrackingState.IDLE
         self.state_lock = threading.Lock()
@@ -430,15 +427,16 @@ class Coordinator:
                     if detections:
                         self._update_stats('persons_detected', len(detections))
                     
-                    # 更新跟踪
-                    tracked = self.tracker.update(detections)
+                    # 为检测结果分配临时序号
+                    for idx, det in enumerate(detections):
+                        det.track_id = idx
                     
                     # 更新跟踪目标
-                    self._update_tracking_targets(tracked, frame_size)
+                    self._update_tracking_targets(detections, frame_size)
                     
                     # 处理检测结果
-                    if tracked:
-                        self._process_detections(tracked, frame, frame_size)
+                    if detections:
+                        self._process_detections(detections, frame, frame_size)
                 
                 # 处理当前跟踪目标
                 self._process_current_target(frame, frame_size)
@@ -716,8 +714,10 @@ class EventDrivenCoordinator(Coordinator):
                     frame_size = (frame.shape[1], frame.shape[0])
                     detections = self._detect_persons(frame)
                     if detections:
-                        tracked = self.tracker.update(detections)
-                        self._update_tracking_targets(tracked, frame_size)
+                        # 为检测结果分配临时序号
+                        for idx, det in enumerate(detections):
+                            det.track_id = idx
+                        self._update_tracking_targets(detections, frame_size)
                         self._process_current_target(frame, frame_size)
                 
                 self._cleanup_expired_targets()
@@ -944,20 +944,21 @@ class AsyncCoordinator(Coordinator):
                         self._update_stats('persons_detected', len(detections))
                         detection_person_count += 1
                     
-                    # 更新跟踪
-                    tracked = self.tracker.update(detections)
-                    self._update_tracking_targets(tracked, frame_size)
+                    # 为检测结果分配临时序号
+                    for idx, det in enumerate(detections):
+                        det.track_id = idx
+                    self._update_tracking_targets(detections, frame_size)
                     
                     # 配对图片保存:创建新批次
-                    if tracked and self._enable_paired_saving and self._paired_saver is not None:
-                        self._create_detection_batch(frame, tracked, frame_size)
+                    if detections and self._enable_paired_saving and self._paired_saver is not None:
+                        self._create_detection_batch(frame, detections, frame_size)
                     
                     # 打印检测日志(使用连续序号,与图片标记一致)
-                    if tracked:
+                    if detections:
                         person_threshold = DETECTION_CONFIG.get('person_threshold', 0.8)
                         person_idx = 0
-                        for t in tracked:
-                            # tracked 是 DetectedObject,使用 center 计算位置
+                        for t in detections:
+                            # detections 是 DetectedObject,使用 center 计算位置
                             x_ratio = t.center[0] / frame_size[0]
                             y_ratio = t.center[1] / frame_size[1]
                             _, _, w, h = t.bbox
@@ -976,10 +977,6 @@ class AsyncCoordinator(Coordinator):
                                     f"位置=({x_ratio:.3f}, {y_ratio:.3f}) "
                                     f"置信度={t.confidence:.2f}(低于阈值{person_threshold})"
                                 )
-                    elif detections:
-                        # 有检测但没跟踪上
-                        for d in detections:
-                            logger.debug(f"[检测] 未跟踪: {d.class_name} @ {d.center}")
                     else:
                         if current_time - last_no_detect_log_time >= no_detect_log_interval:
                             logger.info(
@@ -988,14 +985,14 @@ class AsyncCoordinator(Coordinator):
                             )
                             last_no_detect_log_time = current_time
                     
-                    if tracked:
-                        self._process_detections(tracked, frame, frame_size)
+                    if detections:
+                        self._process_detections(detections, frame, frame_size)
                     
                     # 选择跟踪目标并发送PTZ命令
                     target = self._select_tracking_target()
                     if target and self.enable_ptz_tracking and self.enable_ptz_camera:
                         self._send_ptz_command_with_log(target, frame_size)
-                    elif not tracked and self.current_target:
+                    elif not detections and self.current_target:
                         # 目标消失,切回IDLE
                         self._set_state(TrackingState.IDLE)
                         logger.info("[检测] 目标丢失,球机进入IDLE状态")
@@ -1035,14 +1032,14 @@ class AsyncCoordinator(Coordinator):
             self.enable_ptz_detection = False
 
     def _create_detection_batch(self, frame: np.ndarray, 
-                                 tracked: List[DetectedObject],
+                                 detections: List[DetectedObject],
                                  frame_size: Tuple[int, int]):
         """
         创建检测批次,用于配对图片保存
         
         Args:
             frame: 全景帧
-            tracked: 跟踪到的人员列表
+            detections: 检测到的人员列表
             frame_size: 帧尺寸
         """
         if self._paired_saver is None:
@@ -1051,7 +1048,7 @@ class AsyncCoordinator(Coordinator):
         # 过滤有效人员(必须是 person 且置信度 >= 阈值)
         person_threshold = DETECTION_CONFIG.get('person_threshold', 0.8)
         valid_persons = []
-        for det in tracked:
+        for det in detections:
             # 只处理 class_name 为 person 的目标,排除安全帽、反光衣等
             if det.class_name == 'person' and det.confidence >= person_threshold:
                 valid_persons.append(det)
@@ -1083,7 +1080,7 @@ class AsyncCoordinator(Coordinator):
         batch_id = self._paired_saver.start_new_batch(frame, persons)
         if batch_id:
             self._current_batch_id = batch_id
-            logger.info(f"[配对保存] 创建批次: {batch_id}, 有效人员={len(persons)}/{len(tracked)}")
+            logger.info(f"[配对保存] 创建批次: {batch_id}, 有效人员={len(persons)}/{len(detections)}")
 
     def _save_ptz_image_for_person(self, track_id: int, 
                                     ptz_frame: np.ndarray,

+ 1 - 1
dual_camera_system/main.py

@@ -31,7 +31,7 @@ from config import (
     CALIBRATION_CONFIG, LOG_CONFIG, SYSTEM_CONFIG
 )
 from dahua_sdk import DahuaSDK
-from panorama_camera import PanoramaCamera, ObjectDetector, PersonTracker, DetectedObject
+from panorama_camera import PanoramaCamera, ObjectDetector, DetectedObject
 from ptz_camera import PTZCamera, PTZController
 from ocr_recognizer import NumberDetector, PersonInfo
 from coordinator import Coordinator, EventDrivenCoordinator, AsyncCoordinator

+ 0 - 99
dual_camera_system/panorama_camera.py

@@ -820,102 +820,3 @@ class ObjectDetector:
             self.rknn = None
         self.model = None
         self.session = None
-
-
-class PersonTracker:
-    """
-    人体跟踪器
-    使用简单的质心跟踪算法
-    """
-    
-    def __init__(self, max_disappeared: int = 30):
-        """
-        初始化跟踪器
-        Args:
-            max_disappeared: 最大消失帧数
-        """
-        self.max_disappeared = max_disappeared
-        self.next_id = 0
-        self.objects = {}  # id -> center
-        self.disappeared = {}  # id -> disappeared count
-    
-    def update(self, detections: List[DetectedObject]) -> List[DetectedObject]:
-        """
-        更新跟踪状态
-        Args:
-            detections: 当前帧检测结果
-        Returns:
-            带有跟踪ID的检测结果
-        """
-        # 如果没有检测结果
-        if len(detections) == 0:
-            # 标记所有已跟踪对象为消失
-            for obj_id in list(self.disappeared.keys()):
-                self.disappeared[obj_id] += 1
-                if self.disappeared[obj_id] > self.max_disappeared:
-                    self._deregister(obj_id)
-            return []
-        
-        # 计算当前检测中心点
-        input_centers = np.array([d.center for d in detections])
-        
-        # 如果没有已跟踪对象
-        if len(self.objects) == 0:
-            for det in detections:
-                self._register(det)
-        else:
-            # 计算距离矩阵
-            object_ids = list(self.objects.keys())
-            object_centers = np.array([self.objects[obj_id] for obj_id in object_ids])
-            
-            # 计算欧氏距离
-            distances = np.linalg.norm(
-                object_centers[:, np.newaxis] - input_centers, 
-                axis=2
-            )
-            
-            # 匈牙利算法匹配 (简化版: 贪心匹配)
-            rows = distances.min(axis=1).argsort()
-            cols = distances.argmin(axis=1)[rows]
-            
-            used_rows = set()
-            used_cols = set()
-            
-            for (row, col) in zip(rows, cols):
-                if row in used_rows or col in used_cols:
-                    continue
-                
-                obj_id = object_ids[row]
-                self.objects[obj_id] = input_centers[col]
-                self.disappeared[obj_id] = 0
-                detections[col].track_id = obj_id
-                
-                used_rows.add(row)
-                used_cols.add(col)
-            
-            # 处理未匹配的已跟踪对象
-            unused_rows = set(range(len(object_ids))) - used_rows
-            for row in unused_rows:
-                obj_id = object_ids[row]
-                self.disappeared[obj_id] += 1
-                if self.disappeared[obj_id] > self.max_disappeared:
-                    self._deregister(obj_id)
-            
-            # 处理未匹配的新检测
-            unused_cols = set(range(len(input_centers))) - used_cols
-            for col in unused_cols:
-                self._register(detections[col])
-        
-        return [d for d in detections if d.track_id is not None]
-    
-    def _register(self, detection: DetectedObject):
-        """注册新对象"""
-        detection.track_id = self.next_id
-        self.objects[self.next_id] = detection.center
-        self.disappeared[self.next_id] = 0
-        self.next_id += 1
-    
-    def _deregister(self, obj_id: int):
-        """注销对象"""
-        del self.objects[obj_id]
-        del self.disappeared[obj_id]