|
@@ -1102,9 +1102,57 @@ class AsyncCoordinator(Coordinator):
|
|
|
print(f"[AsyncCoordinator] 球机端检测器初始化失败: {e}")
|
|
print(f"[AsyncCoordinator] 球机端检测器初始化失败: {e}")
|
|
|
self.enable_ptz_detection = False
|
|
self.enable_ptz_detection = False
|
|
|
|
|
|
|
|
|
|
+ def _deduplicate_detections(self, detections: List[DetectedObject],
|
|
|
|
|
+ frame_size: Tuple[int, int]) -> List[DetectedObject]:
|
|
|
|
|
+ """
|
|
|
|
|
+ 去重检测结果(按位置合并重叠的检测框)
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ detections: 检测列表
|
|
|
|
|
+ frame_size: 帧尺寸
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 去重后的人员检测列表
|
|
|
|
|
+ """
|
|
|
|
|
+ # 过滤有效人员
|
|
|
|
|
+ person_threshold = DETECTION_CONFIG.get('person_threshold', 0.5)
|
|
|
|
|
+ valid_persons = [d for d in detections
|
|
|
|
|
+ if d.class_name == 'person' and d.confidence >= person_threshold]
|
|
|
|
|
+
|
|
|
|
|
+ if not valid_persons:
|
|
|
|
|
+ return []
|
|
|
|
|
+
|
|
|
|
|
+ # 去重:按位置合并重叠的检测框
|
|
|
|
|
+ DEDUP_DISTANCE = 0.05 # 画面比例 5%
|
|
|
|
|
+ dedup_persons = []
|
|
|
|
|
+
|
|
|
|
|
+ for det in valid_persons:
|
|
|
|
|
+ det_x = det.center[0] / frame_size[0]
|
|
|
|
|
+ det_y = det.center[1] / frame_size[1]
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否与已有人员重叠
|
|
|
|
|
+ is_duplicate = False
|
|
|
|
|
+ for i, existing in enumerate(dedup_persons):
|
|
|
|
|
+ ex_x = existing.center[0] / frame_size[0]
|
|
|
|
|
+ ex_y = existing.center[1] / frame_size[1]
|
|
|
|
|
+
|
|
|
|
|
+ dist = math.sqrt((det_x - ex_x)**2 + (det_y - ex_y)**2)
|
|
|
|
|
+ if dist < DEDUP_DISTANCE:
|
|
|
|
|
+ # 重叠,保留置信度更高的
|
|
|
|
|
+ is_duplicate = True
|
|
|
|
|
+ if det.confidence > existing.confidence:
|
|
|
|
|
+ dedup_persons[i] = det
|
|
|
|
|
+ break
|
|
|
|
|
+
|
|
|
|
|
+ if not is_duplicate:
|
|
|
|
|
+ dedup_persons.append(det)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return dedup_persons
|
|
|
|
|
+
|
|
|
def _create_detection_batch(self, frame: np.ndarray,
|
|
def _create_detection_batch(self, frame: np.ndarray,
|
|
|
detections: List[DetectedObject],
|
|
detections: List[DetectedObject],
|
|
|
- frame_size: Tuple[int, int]):
|
|
|
|
|
|
|
+ frame_size: Tuple[int, int]) -> List[DetectedObject]:
|
|
|
"""
|
|
"""
|
|
|
创建检测批次,用于配对图片保存
|
|
创建检测批次,用于配对图片保存
|
|
|
|
|
|
|
@@ -1112,9 +1160,12 @@ class AsyncCoordinator(Coordinator):
|
|
|
frame: 全景帧
|
|
frame: 全景帧
|
|
|
detections: 检测到的人员列表
|
|
detections: 检测到的人员列表
|
|
|
frame_size: 帧尺寸
|
|
frame_size: 帧尺寸
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 去重后的人员检测列表
|
|
|
"""
|
|
"""
|
|
|
if self._paired_saver is None:
|
|
if self._paired_saver is None:
|
|
|
- return
|
|
|
|
|
|
|
+ return []
|
|
|
|
|
|
|
|
# 过滤有效人员(必须是 person 且置信度 >= 阈值)
|
|
# 过滤有效人员(必须是 person 且置信度 >= 阈值)
|
|
|
person_threshold = DETECTION_CONFIG.get('person_threshold', 0.8)
|
|
person_threshold = DETECTION_CONFIG.get('person_threshold', 0.8)
|
|
@@ -1152,7 +1203,7 @@ class AsyncCoordinator(Coordinator):
|
|
|
|
|
|
|
|
if not dedup_persons:
|
|
if not dedup_persons:
|
|
|
logger.debug(f"[配对保存] 无有效人员(阈值={person_threshold}),跳过批次创建")
|
|
logger.debug(f"[配对保存] 无有效人员(阈值={person_threshold}),跳过批次创建")
|
|
|
- return
|
|
|
|
|
|
|
+ return []
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"[配对保存] 检测结果去重: {len(valid_persons)} -> {len(dedup_persons)} 个人员")
|
|
logger.info(f"[配对保存] 检测结果去重: {len(valid_persons)} -> {len(dedup_persons)} 个人员")
|
|
@@ -1181,6 +1232,8 @@ class AsyncCoordinator(Coordinator):
|
|
|
if batch_id:
|
|
if batch_id:
|
|
|
self._current_batch_id = batch_id
|
|
self._current_batch_id = batch_id
|
|
|
logger.info(f"[配对保存] 创建批次: {batch_id}, 有效人员={len(persons)}/{len(detections)}")
|
|
logger.info(f"[配对保存] 创建批次: {batch_id}, 有效人员={len(persons)}/{len(detections)}")
|
|
|
|
|
+
|
|
|
|
|
+ return dedup_persons
|
|
|
|
|
|
|
|
def _save_ptz_image_for_person(self, track_id: int,
|
|
def _save_ptz_image_for_person(self, track_id: int,
|
|
|
ptz_frame: np.ndarray,
|
|
ptz_frame: np.ndarray,
|
|
@@ -1699,18 +1752,33 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
tracking_count = len(self.tracking_targets)
|
|
tracking_count = len(self.tracking_targets)
|
|
|
logger.info(f"[顺序模式] 检测到 {len(detections)} 个目标, 跟踪列表 {tracking_count} 个")
|
|
logger.info(f"[顺序模式] 检测到 {len(detections)} 个目标, 跟踪列表 {tracking_count} 个")
|
|
|
|
|
|
|
|
- # 获取有效目标列表
|
|
|
|
|
- targets = self._get_all_valid_targets()
|
|
|
|
|
|
|
+ # 【关键修复】先创建配对批次并获取去重后的人员列表
|
|
|
|
|
+ dedup_persons = []
|
|
|
|
|
+ if self._enable_paired_saving and self._paired_saver is not None:
|
|
|
|
|
+ dedup_persons = self._create_detection_batch(frame, detections, frame_size)
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 如果未启用配对保存,也需要去重
|
|
|
|
|
+ dedup_persons = self._deduplicate_detections(detections, frame_size)
|
|
|
|
|
|
|
|
- # 【调试日志】显示有效目标数量
|
|
|
|
|
- logger.info(f"[顺序模式] 有效目标数量: {len(targets) if targets else 0}")
|
|
|
|
|
|
|
+ # 【调试日志】显示去重后目标数量
|
|
|
|
|
+ logger.info(f"[顺序模式] 去重后有效目标数量: {len(dedup_persons)}")
|
|
|
|
|
|
|
|
- if targets:
|
|
|
|
|
- logger.info(f"[顺序模式] 检测到 {len(targets)} 个目标,开始顺序抓拍")
|
|
|
|
|
|
|
+ if dedup_persons:
|
|
|
|
|
+ # 将去重后的检测结果转换为抓拍目标
|
|
|
|
|
+ capture_targets = []
|
|
|
|
|
+ for i, det in enumerate(dedup_persons):
|
|
|
|
|
+ x_ratio = det.center[0] / frame_size[0]
|
|
|
|
|
+ y_ratio = det.center[1] / frame_size[1]
|
|
|
|
|
+ target = TrackingTarget(
|
|
|
|
|
+ track_id=det.track_id,
|
|
|
|
|
+ position=(x_ratio, y_ratio),
|
|
|
|
|
+ last_update=current_time,
|
|
|
|
|
+ area=det.bbox[2] * det.bbox[3],
|
|
|
|
|
+ confidence=det.confidence
|
|
|
|
|
+ )
|
|
|
|
|
+ capture_targets.append(target)
|
|
|
|
|
|
|
|
- # 创建配对保存批次
|
|
|
|
|
- if self._enable_paired_saving and self._paired_saver is not None:
|
|
|
|
|
- self._create_detection_batch(frame, detections, frame_size)
|
|
|
|
|
|
|
+ logger.info(f"[顺序模式] 检测到 {len(capture_targets)} 个目标,开始顺序抓拍")
|
|
|
|
|
|
|
|
# 【关键修复】切换到抓拍状态后立即清空 tracking_targets
|
|
# 【关键修复】切换到抓拍状态后立即清空 tracking_targets
|
|
|
# 防止后续检测再次获取到同一批目标
|
|
# 防止后续检测再次获取到同一批目标
|
|
@@ -1718,10 +1786,10 @@ class SequentialCoordinator(AsyncCoordinator):
|
|
|
self.tracking_targets.clear()
|
|
self.tracking_targets.clear()
|
|
|
logger.info("[顺序模式] 已清空跟踪目标,防止重复抓拍")
|
|
logger.info("[顺序模式] 已清空跟踪目标,防止重复抓拍")
|
|
|
|
|
|
|
|
- # 切换到抓拍状态
|
|
|
|
|
- self._start_capture_sequence(targets)
|
|
|
|
|
|
|
+ # 切换到抓拍状态(使用去重后的目标)
|
|
|
|
|
+ self._start_capture_sequence(capture_targets)
|
|
|
else:
|
|
else:
|
|
|
- logger.warning(f"[顺序模式] 有效目标为空,跳过抓拍")
|
|
|
|
|
|
|
+ logger.warning(f"[顺序模式] 去重后无有效目标,跳过抓拍")
|
|
|
else:
|
|
else:
|
|
|
# 未检测到人员
|
|
# 未检测到人员
|
|
|
if current_time - last_no_detect_log_time >= no_detect_log_interval:
|
|
if current_time - last_no_detect_log_time >= no_detect_log_interval:
|