Jelajahi Sumber

refactor: 优化多相机场景下的OSS上传和检测配置

1. 为OSS上传增加相机类型前缀区分文件
2. 调整大华相机subtype和rtsp配置适配设备
3. 降低检测置信度阈值适配全景远距离人员
4. 缩短本地文件保留天数至1天
5. 增加检测结果打印日志
6. 完善全景检测的第三方推送数据结构
wenhongquan 1 Minggu lalu
induk
melakukan
f01126cfb2

+ 4 - 0
dual_camera_system/app.py

@@ -154,6 +154,8 @@ def create_app(test_mode: bool = False) -> FastAPI:
                                             "bbox": [d.bbox[0], d.bbox[1], d.bbox[0]+d.bbox[2], d.bbox[1]+d.bbox[3]],
                                             "confidence": d.confidence,
                                         } for d in dets]
+                                        for dd in det_dicts:
+                                            print(f"[detect] {g} bbox={dd['bbox']} conf={dd['confidence']:.3f}")
                                         uploader.handle_detection("panorama", frame, det_dicts)
                                         for d in det_dicts:
                                             x1, y1, x2, y2 = d["bbox"]
@@ -185,6 +187,8 @@ def create_app(test_mode: bool = False) -> FastAPI:
                                                 "bbox": [d.bbox[0], d.bbox[1], d.bbox[0]+d.bbox[2], d.bbox[1]+d.bbox[3]],
                                                 "confidence": d.confidence,
                                             } for d in dets]
+                                            for dd in det_dicts:
+                                                print(f"[detect] {g} PTZ bbox={dd['bbox']} conf={dd['confidence']:.3f}")
                                             uploader.handle_detection("ptz", frame, det_dicts)
                                 except Exception as exc:
                                     print(f"[ptz_detect_loop {g}] error: {exc}")

+ 2 - 2
dual_camera_system/config/camera.py

@@ -74,9 +74,9 @@ CAMERA_GROUPS = [
             'password': 'Aa1234567',
             'channel': 1,
             'brand': 'dahua',
-            'subtype': 1,
+            'subtype': 0,
             'resolution': (3840, 2160),
-            'rtsp_url': 'rtsp://admin:Aa1234567@192.168.20.197:554/cam/realmonitor?channel=1&subtype=1',
+            'rtsp_url': 'rtsp://admin:Aa1234567@192.168.20.197:554/cam/realmonitor?channel=1&subtype=0',
             'pan_flip': False,
             'ceiling_mount': True,
         },

+ 2 - 2
dual_camera_system/config/detection.py

@@ -28,7 +28,7 @@ _MODEL_PATH, _MODEL_TYPE = _default_model()
 # 人体检测配置(用于 panorama_camera.ObjectDetector,多组模式)
 DETECTION_CONFIG = {
     'target_classes': ['person'],   # 检测目标类别 (支持中英文)
-    'confidence_threshold': 0.45,     # 置信度阈值
+    'confidence_threshold': 0.30,     # 置信度阈值(yolo26n 对全景远距离人员置信度偏低)
     'detection_fps': 2,              # 检测帧率
     'detection_interval': 4,       # 兼容保留:检测间隔(秒)
 
@@ -47,7 +47,7 @@ DETECTION_CONFIG = {
     'use_gpu': False,               # 使用 CPU (yolo26n.pt via ultralytics)
 
     # 人体检测后处理阈值
-    'person_threshold': 0.45,    # 进入联动跟踪的人体置信度阈值
+    'person_threshold': 0.30,    # 进入联动跟踪的人体置信度阈值
 
     # 模型类别映射(yolo26n COCO80:0=person)
     'class_map': {0: 'person'},

+ 3 - 3
dual_camera_system/config/device.py

@@ -98,7 +98,7 @@ STORAGE_CONFIG = {
         # 第三方平台上报成功后是否保留本地图片
         'keep_local_copy': False,
         # 本地保留天数,超过此时间的文件会被定期清理
-        'retention_days': 7,
+        'retention_days': 1,
         # 每组目录最大文件数,超过时按修改时间删除最旧的
         'max_files': 2000,
         # 清理线程执行间隔(秒)
@@ -108,7 +108,7 @@ STORAGE_CONFIG = {
         # 预览图保存根目录
         'base_dir': 'data/previews',
         # 本地保留天数
-        'retention_days': 7,
+        'retention_days': 1,
         # 每组目录最大文件数
         'max_files': 1000,
         # 清理线程执行间隔(秒)
@@ -124,7 +124,7 @@ PAIRED_IMAGE_CONFIG = {
     # 清理策略
     'cleanup_enabled': True,           # 是否启用自动清理
     'max_batches': 10,                 # 最大保留批次数量
-    'retention_days': 7,                # 保留天数(与 max_batches 互斥,优先按数量清理)
+    'retention_days': 1,                # 保留天数(与 max_batches 互斥,优先按数量清理)
 
     # 时间窗口(秒):同一窗口内的检测归为一批
     'time_window': 5.0,

+ 1 - 1
dual_camera_system/core/capture_uploader.py

@@ -148,7 +148,7 @@ class CaptureUploader:
 
         image_urls = {}
         if self.oss_uploader is not None and self.oss_uploader.enabled:
-            image_urls = self.oss_uploader.upload_pair(original_path, marked_path)
+            image_urls = self.oss_uploader.upload_pair(original_path, marked_path, prefix=camera_type)
             logger.info("OSS URLs: %s", image_urls)
 
         if self.upload_callback and results:

+ 5 - 3
dual_camera_system/core/oss_uploader.py

@@ -97,9 +97,11 @@ class OSSUploader:
         logger.error("[OSS] 上传最终失败: %s", local_path)
         return None
 
-    def upload_pair(self, original_path: str, marked_path: str) -> dict:
+    def upload_pair(self, original_path: str, marked_path: str, prefix: str = "") -> dict:
         """上传原图和标注图,返回 URL 字典."""
+        orig_suffix = f"original_{prefix}" if prefix else "original"
+        mark_suffix = f"marked_{prefix}" if prefix else "marked"
         return {
-            "original": self.upload(original_path, suffix="original"),
-            "marked": self.upload(marked_path, suffix="marked"),
+            "original": self.upload(original_path, suffix=orig_suffix),
+            "marked": self.upload(marked_path, suffix=mark_suffix),
         }

+ 9 - 12
dual_camera_system/third_party_pusher.py

@@ -105,19 +105,16 @@ def _convert_to_legacy_batch_info(new_info: Dict[str, Any]) -> Dict[str, Any]:
             person["ptz_oss_url"] = oss_url_marked
             person["ptz_oss_url_original"] = oss_url_original
         else:
-            # 全景相机检测时,也需要包含 ptz_position,使用当前云台位置
-            current_ptz = new_info.get("current_ptz_position") or {}
+            # 全景相机检测时,复用当前检测框作为 ptz_bbox
             person["ptz_position"] = {
-                "pan": current_ptz.get("pan", 0),
-                "tilt": current_ptz.get("tilt", 0),
-                "zoom": current_ptz.get("zoom", 1),
+                "pan": 0, "tilt": 0, "zoom": 1,
             }
-            person["ptz_bbox"] = {}
-            person["ptz_image_saved"] = False
-            person["ptz_image_path"] = None
-            person["ptz_image_original_path"] = None
-            person["ptz_oss_url"] = None
-            person["ptz_oss_url_original"] = None
+            person["ptz_bbox"] = {"x1": x1, "y1": y1, "x2": x2, "y2": y2}
+            person["ptz_image_saved"] = bool(marked_path and os.path.exists(marked_path))
+            person["ptz_image_path"] = marked_path
+            person["ptz_image_original_path"] = original_path
+            person["ptz_oss_url"] = oss_url_marked
+            person["ptz_oss_url_original"] = oss_url_original
 
         persons.append(person)
 
@@ -130,7 +127,7 @@ def _convert_to_legacy_batch_info(new_info: Dict[str, Any]) -> Dict[str, Any]:
         "timestamp": normalized_ts,
         "datetime": datetime.fromtimestamp(normalized_ts).isoformat(),
         "total_persons": len(persons),
-        "ptz_images_count": len(persons) if is_ptz else 0,
+        "ptz_images_count": len(persons),
         "panorama": {
             "local_path": marked_path,
             "local_path_original": original_path,