Browse Source

refactor(coordinator): 重构联动控制器逻辑提升异步PTZ处理能力

- 拆分检测线程与PTZ控制线程,实现异步并发处理,提升系统响应速度和资源利用
- 引入PTZ命令队列,实现检测线程向PTZ线程异步传递目标位置信息
- 支持球机RTSP流启动与球机端人体检测,增强联动系统能力
- 增强日志系统,定期打印检测帧率及运行状态,便于监控调试
- 优化目标跟踪逻辑,支持跨帧目标跟踪与粘性匹配,减少误切换
- 新增配对图片保存机制,支持对检测到人员进行批次管理及图片配对存储
- 实现PTZ位置确认机制,确保球机到位后再进行下一步操作
- 提供健壮的异常处理与多线程锁机制,保证系统稳定安全运行
wenhongquan 2 days ago
parent
commit
5cd41af00a

BIN
dual_camera_system/__pycache__/coordinator.cpython-310.pyc


BIN
dual_camera_system/__pycache__/paired_image_saver.cpython-310.pyc


BIN
dual_camera_system/config/__pycache__/detection.cpython-310.pyc


+ 1 - 1
dual_camera_system/config/detection.py

@@ -31,7 +31,7 @@ DETECTION_CONFIG = {
         4: 'reflective'
     },
     'person_class_id': 3,           # 人员在模型中的类别ID
-    'person_threshold': 0.8,        # 人员检测置信度阈值
+    'person_threshold': 0.5,        # 人员检测置信度阈值(降低以捕获更多目标)
 }
 
 # 安全检测模型配置

+ 38 - 0
dual_camera_system/coordinator.py

@@ -468,13 +468,22 @@ class Coordinator:
         
         # 过滤有效人员
         valid_detections = []
+        low_conf_count = 0
         for det in detections:
             if det.class_name != 'person':
                 continue
             if det.confidence < person_threshold:
+                low_conf_count += 1
                 continue
             valid_detections.append(det)
         
+        # 【调试日志】显示过滤结果
+        if detections:
+            logger.info(f"[跟踪] 检测到 {len(detections)} 个目标, 置信度>={person_threshold} 的有 {len(valid_detections)} 个 (过滤掉 {low_conf_count} 个)")
+        
+        if not valid_detections:
+            return
+        
         with self.targets_lock:
             # 匹配阈值:位置距离小于此值认为是同一目标
             MATCH_THRESHOLD = 0.15  # 画面比例
@@ -1685,9 +1694,17 @@ class SequentialCoordinator(AsyncCoordinator):
                             # 更新跟踪目标
                             self._update_tracking_targets(detections, frame_size)
                             
+                            # 【调试日志】检查跟踪目标数量
+                            with self.targets_lock:
+                                tracking_count = len(self.tracking_targets)
+                            logger.info(f"[顺序模式] 检测到 {len(detections)} 个目标, 跟踪列表 {tracking_count} 个")
+                            
                             # 获取有效目标列表
                             targets = self._get_all_valid_targets()
                             
+                            # 【调试日志】显示有效目标数量
+                            logger.info(f"[顺序模式] 有效目标数量: {len(targets) if targets else 0}")
+                            
                             if targets:
                                 logger.info(f"[顺序模式] 检测到 {len(targets)} 个目标,开始顺序抓拍")
                                 
@@ -1695,8 +1712,16 @@ class SequentialCoordinator(AsyncCoordinator):
                                 if self._enable_paired_saving and self._paired_saver is not None:
                                     self._create_detection_batch(frame, detections, frame_size)
                                 
+                                # 【关键修复】切换到抓拍状态后立即清空 tracking_targets
+                                # 防止后续检测再次获取到同一批目标
+                                with self.targets_lock:
+                                    self.tracking_targets.clear()
+                                    logger.info("[顺序模式] 已清空跟踪目标,防止重复抓拍")
+                                
                                 # 切换到抓拍状态
                                 self._start_capture_sequence(targets)
+                            else:
+                                logger.warning(f"[顺序模式] 有效目标为空,跳过抓拍")
                         else:
                             # 未检测到人员
                             if current_time - last_no_detect_log_time >= no_detect_log_interval:
@@ -1777,6 +1802,11 @@ class SequentialCoordinator(AsyncCoordinator):
             targets = self._batch_targets.copy()
             current_idx = self._current_capture_index
             batch_size = self._capture_batch_size
+            batch_id = self._capture_batch_id
+        
+        # 【调试日志】详细输出抓拍状态
+        logger.info(f"[顺序模式] 执行抓拍: idx={current_idx}, batch_size={batch_size}, "
+                   f"targets_len={len(targets)}, batch_id={batch_id}")
         
         # 使用保存的批次大小进行检查,而不是 len(targets)
         if current_idx >= batch_size:
@@ -1785,6 +1815,14 @@ class SequentialCoordinator(AsyncCoordinator):
             self._set_capture_state('returning')
             return
         
+        
+        # 【安全检查】确保 targets 有足够的数据
+        if current_idx >= len(targets):
+            logger.warning(f"[顺序模式] 索引越界: idx={current_idx} >= targets_len={len(targets)}, "
+                          f"batch_size={batch_size}。可能批次信息不一致!")
+            self._set_capture_state('returning')
+            return
+        
         # 获取当前目标
         target = targets[current_idx]
         x_ratio, y_ratio = target.position

+ 12 - 7
dual_camera_system/paired_image_saver.py

@@ -305,18 +305,23 @@ class PairedImageSaver:
                     cv2.imwrite(str(filepath), marked_frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
                 
                 # 更新批次信息
+                # 【关键修复】只有当人员索引有效时才更新和计数
                 if person_index < len(self._current_batch.persons):
                     self._current_batch.persons[person_index].ptz_position = ptz_position
                     self._current_batch.persons[person_index].ptz_bbox = ptz_bbox
                     self._current_batch.persons[person_index].ptz_image_saved = True
                     self._current_batch.persons[person_index].ptz_image_path = str(filepath)
-                
-                self._current_batch.ptz_images_count += 1
-                
-                with self._stats_lock:
-                    self._stats['total_ptz_images'] += 1
-                
-                logger.info(f"[配对保存] 球机图已保存: {filepath}, BBox={ptz_bbox}")
+                    
+                    self._current_batch.ptz_images_count += 1
+                    
+                    with self._stats_lock:
+                        self._stats['total_ptz_images'] += 1
+                    
+                    logger.info(f"[配对保存] 球机图已保存: {filepath}, BBox={ptz_bbox}")
+                else:
+                    # 人员索引超出范围,说明批次信息不一致,跳过保存
+                    logger.warning(f"[配对保存] 人员索引 {person_index} 超出批次范围 {len(self._current_batch.persons)},跳过计数")
+                    
                 return str(filepath)
                 
             except Exception as e:

+ 1 - 1
dual_camera_system/panorama_camera.py

@@ -693,7 +693,7 @@ class ObjectDetector:
                     continue
                 
                 # 使用配置的类别映射获取类别名称
-                class_map = self.config.get('class_map', {0: 'person', 3: '人'})
+                class_map = self.config.get('class_map', {0: 'hat',3: 'person',4: 'reflective'})
                 cls_name = class_map.get(best_class, str(best_class))
                 
                 # 检查是否为目标类别