Prechádzať zdrojové kódy

feat(calibration): 添加球机视频流支持以增强校准功能

为校准系统添加球机RTSP视频流支持,用于特征匹配和视野重叠发现
配置重叠发现参数并优化校准流程
完善错误处理和资源释放逻辑
wenhongquan 4 dní pred
rodič
commit
0e88feb6c4

BIN
dual_camera_system/__pycache__/calibration.cpython-313.pyc


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 533 - 367
dual_camera_system/calibration.py


+ 10 - 2
dual_camera_system/config/coordinator.py

@@ -14,6 +14,14 @@ CALIBRATION_CONFIG = {
     'interval': 24 * 60 * 60,        # 校准间隔(秒) - 1天
     'quick_mode': True,              # 快速校准模式
     'auto_save': True,               # 自动保存校准结果
-    'min_valid_points': 4,           # 最少有效校准点数
-    'rms_error_threshold': 5.0,      # RMS误差阈值(度)
+    'min_valid_points': 4,            # 最少有效校准点数
+    'rms_error_threshold': 5.0,       # RMS误差阈值(度)
+    'overlap_discovery': {
+        'pan_range': (0, 360),        # 球机水平扫描范围(度)
+        'tilt_range': (-45, 45),      # 球机垂直扫描范围(度)
+        'pan_step': 20,               # 水平扫描步进(度)
+        'tilt_step': 15,              # 垂直扫描步进(度)
+        'min_match_threshold': 8,     # 最少特征匹配数(判定重叠)
+        'stabilize_time': 2.0,        # 球机稳定等待时间(秒)
+    },
 }

+ 25 - 11
dual_camera_system/main.py

@@ -213,14 +213,28 @@ class DualCameraSystem:
             if not self.panorama_camera.start_stream():
                 logger.error("无法启动视频流,校准可能无法获取画面")
         
-        # 创建校准器 - 支持视觉检测
+        # 启动球机视频流(校准需要球机画面做特征匹配)
+        if not self.ptz_camera.start_stream_rtsp():
+            logger.warning("球机RTSP视频流启动失败,校准将无法进行特征匹配")
+            if not self.ptz_camera.start_stream():
+                logger.error("无法启动球机视频流,校准将仅依赖运动检测")
+        
+        # 创建校准器 - 支持视野重叠发现
         self.calibrator = CameraCalibrator(
             ptz_camera=self.ptz_camera,
             get_frame_func=self.panorama_camera.get_frame,
-            detect_marker_func=None,  # 使用自动检测
-            ptz_capture_func=self._capture_ptz_frame  # 添加球机抓拍功能
+            detect_marker_func=None,
+            ptz_capture_func=self._capture_ptz_frame
         )
         
+        # 配置重叠发现参数
+        overlap_cfg = CALIBRATION_CONFIG.get('overlap_discovery', {})
+        self.calibrator.overlap_pan_range = overlap_cfg.get('pan_range', (0, 360))
+        self.calibrator.overlap_tilt_range = overlap_cfg.get('tilt_range', (-45, 45))
+        self.calibrator.overlap_pan_step = overlap_cfg.get('pan_step', 20)
+        self.calibrator.overlap_tilt_step = overlap_cfg.get('tilt_step', 15)
+        self.calibrator.stabilize_time = overlap_cfg.get('stabilize_time', 2.0)
+        
         # 校准进度回调
         def on_progress(current: int, total: int, message: str):
             logger.info(f"校准进度: {current}/{total} - {message}")
@@ -250,8 +264,9 @@ class DualCameraSystem:
             logger.error("请检查以下问题:")
             logger.error("1. 全景摄像头和球机是否正确连接")
             logger.error("2. 球机PTZ控制是否正常")
-            logger.error("3. 摄像头视野范围是否正确配置")
-            logger.error("4. 场景是否有足够的特征点用于匹配")
+            logger.error("3. 两台摄像头的视野是否有重叠区域")
+            logger.error("4. 场景是否有足够的纹理/特征用于匹配")
+            logger.error("5. 球机RTSP视频流是否正常(特征匹配需要球机画面)")
             logger.error("")
             logger.error("您可以尝试:")
             logger.error("- 检查摄像头连接和网络")
@@ -262,6 +277,7 @@ class DualCameraSystem:
             
             # 释放资源
             self.panorama_camera.disconnect()
+            self.ptz_camera.stop_stream()
             self.ptz_camera.disconnect()
             
             return False
@@ -277,14 +293,12 @@ class DualCameraSystem:
     def _capture_ptz_frame(self) -> Optional[np.ndarray]:
         """
         从球机抓拍一帧图像
-        用于特征匹配校准
-        Returns:
-            球机画面或None
+        用于校准时特征匹配
         """
-        try:
-            # 这里应该调用SDK的抓拍功能
-            # 简化处理: 返回None表示暂不支持
+        if self.ptz_camera is None:
             return None
+        try:
+            return self.ptz_camera.get_frame()
         except Exception as e:
             logger.error(f"球机抓拍失败: {e}")
             return None

+ 74 - 1
dual_camera_system/ptz_camera.py

@@ -1,14 +1,19 @@
 """
 球机(PTZ)控制模块
-负责PTZ控制和精确定位
+负责PTZ控制、精确定位和视频流获取
 """
 
 import math
 import time
 import threading
+import queue
+import os
 from typing import Optional, Tuple, Dict
 from dataclasses import dataclass
 
+import cv2
+import numpy as np
+
 from config import PTZ_CAMERA, PTZ_CONFIG
 from dahua_sdk import DahuaSDK, PTZCommand
 
@@ -41,6 +46,13 @@ class PTZCamera:
         # 当前位置
         self.current_position = PTZPosition(pan=0, tilt=0, zoom=1)
         self.position_lock = threading.Lock()
+        
+        # 视频流 (用于校准时抓拍球机画面)
+        self.rtsp_cap = None
+        self.current_frame = None
+        self.frame_lock = threading.Lock()
+        self.stream_thread = None
+        self.running_stream = False
     
     def connect(self) -> bool:
         """
@@ -70,11 +82,72 @@ class PTZCamera:
     
     def disconnect(self):
         """断开连接"""
+        self.stop_stream()
         if self.login_handle:
             self.sdk.logout(self.login_handle)
             self.login_handle = None
         self.connected = False
     
+    def start_stream_rtsp(self, rtsp_url: str = None) -> bool:
+        """启动RTSP视频流 (用于校准时获取球机画面)"""
+        if rtsp_url is None:
+            rtsp_url = self.config.get('rtsp_url') or \
+                f"rtsp://{self.config['username']}:{self.config['password']}@{self.config['ip']}:{self.config.get('rtsp_port', 554)}/cam/realmonitor?channel=1&subtype=1"
+        
+        try:
+            os.environ['OPENCV_FFMPEG_CAPTURE_OPTIONS'] = 'threads;1'
+            self.rtsp_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
+            if not self.rtsp_cap.isOpened():
+                print(f"[PTZCamera] 无法打开RTSP流: {rtsp_url}")
+                return False
+            
+            self.rtsp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
+            
+            self.running_stream = True
+            self.stream_thread = threading.Thread(target=self._stream_worker, daemon=True)
+            self.stream_thread.start()
+            print(f"[PTZCamera] RTSP视频流已启动")
+            return True
+        except Exception as e:
+            print(f"[PTZCamera] RTSP流启动失败: {e}")
+            return False
+    
+    def _stream_worker(self):
+        """视频流工作线程"""
+        while self.running_stream:
+            try:
+                if self.rtsp_cap is None or not self.rtsp_cap.isOpened():
+                    time.sleep(0.1)
+                    continue
+                
+                ret, frame = self.rtsp_cap.read()
+                if not ret or frame is None:
+                    time.sleep(0.01)
+                    continue
+                
+                with self.frame_lock:
+                    self.current_frame = frame.copy()
+                
+                time.sleep(0.001)
+            except Exception as e:
+                print(f"[PTZCamera] 视频流错误: {e}")
+                time.sleep(0.1)
+    
+    def get_frame(self) -> Optional[np.ndarray]:
+        """获取球机当前帧"""
+        with self.frame_lock:
+            return self.current_frame.copy() if self.current_frame is not None else None
+    
+    def stop_stream(self):
+        """停止视频流"""
+        self.running_stream = False
+        if self.stream_thread:
+            self.stream_thread.join(timeout=2)
+            self.stream_thread = None
+        if self.rtsp_cap:
+            self.rtsp_cap.release()
+            self.rtsp_cap = None
+    
     def ptz_control(self, command: int, param1: int = 0, param2: int = 0, 
                     param3: int = 0, stop: bool = False) -> bool:
         """

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov