|
|
@@ -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:
|
|
|
"""
|