-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmulti_camera_manager.py
More file actions
311 lines (254 loc) · 10.9 KB
/
Copy pathmulti_camera_manager.py
File metadata and controls
311 lines (254 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
"""
Multi-Camera Manager for STAMPede Detection System
Handles multiple camera feeds simultaneously with load balancing
"""
import cv2
import threading
import time
import queue
from typing import Dict, List, Optional, Callable
from dataclasses import dataclass
from enum import Enum
import numpy as np
from collections import deque
class CameraStatus(Enum):
CONNECTING = "connecting"
CONNECTED = "connected"
DISCONNECTED = "disconnected"
ERROR = "error"
@dataclass
class CameraConfig:
camera_id: int
name: str
resolution: tuple = (1280, 720)
fps: int = 30
enabled: bool = True
area_m2: float = 25.0
confidence: float = 0.20
grid_w: int = 32
grid_h: int = 24
@dataclass
class CameraFrame:
camera_id: int
frame: np.ndarray
timestamp: float
frame_number: int
detection_results: Optional[dict] = None
class MultiCameraManager:
"""Manages multiple camera feeds with load balancing and failover"""
def __init__(self, max_cameras: int = 4):
self.max_cameras = max_cameras
self.cameras: Dict[int, CameraConfig] = {}
self.camera_threads: Dict[int, threading.Thread] = {}
self.camera_caps: Dict[int, cv2.VideoCapture] = {}
self.camera_status: Dict[int, CameraStatus] = {}
self.frame_queues: Dict[int, queue.Queue] = {}
self.is_running = False
self.frame_callbacks: List[Callable] = []
self.detection_callbacks: List[Callable] = []
# Performance monitoring
self.fps_counters: Dict[int, deque] = {}
self.error_counts: Dict[int, int] = {}
def add_camera(self, config: CameraConfig) -> bool:
"""Add a new camera configuration"""
if len(self.cameras) >= self.max_cameras:
print(f"[MultiCamera] Maximum cameras ({self.max_cameras}) reached")
return False
if config.camera_id in self.cameras:
print(f"[MultiCamera] Camera {config.camera_id} already exists")
return False
self.cameras[config.camera_id] = config
self.camera_status[config.camera_id] = CameraStatus.DISCONNECTED
self.frame_queues[config.camera_id] = queue.Queue(maxsize=10)
self.fps_counters[config.camera_id] = deque(maxlen=30)
self.error_counts[config.camera_id] = 0
print(f"[MultiCamera] Added camera {config.camera_id}: {config.name}")
return True
def remove_camera(self, camera_id: int) -> bool:
"""Remove a camera and stop its processing"""
if camera_id not in self.cameras:
return False
# Stop camera thread
if camera_id in self.camera_threads:
self.camera_threads[camera_id].join(timeout=2)
del self.camera_threads[camera_id]
# Release camera
if camera_id in self.camera_caps:
self.camera_caps[camera_id].release()
del self.camera_caps[camera_id]
# Clean up data structures
del self.cameras[camera_id]
del self.camera_status[camera_id]
del self.frame_queues[camera_id]
del self.fps_counters[camera_id]
del self.error_counts[camera_id]
print(f"[MultiCamera] Removed camera {camera_id}")
return True
def start_camera(self, camera_id: int) -> bool:
"""Start processing a specific camera"""
if camera_id not in self.cameras:
return False
if camera_id in self.camera_threads and self.camera_threads[camera_id].is_alive():
print(f"[MultiCamera] Camera {camera_id} already running")
return True
config = self.cameras[camera_id]
if not config.enabled:
print(f"[MultiCamera] Camera {camera_id} is disabled")
return False
# Initialize camera
cap = cv2.VideoCapture(camera_id)
if not cap.isOpened():
print(f"[MultiCamera] Failed to open camera {camera_id}")
self.camera_status[camera_id] = CameraStatus.ERROR
return False
# Configure camera settings
cap.set(cv2.CAP_PROP_FRAME_WIDTH, config.resolution[0])
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, config.resolution[1])
cap.set(cv2.CAP_PROP_FPS, config.fps)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self.camera_caps[camera_id] = cap
self.camera_status[camera_id] = CameraStatus.CONNECTED
# Start processing thread
thread = threading.Thread(target=self._process_camera, args=(camera_id,))
thread.daemon = True
thread.start()
self.camera_threads[camera_id] = thread
print(f"[MultiCamera] Started camera {camera_id}: {config.name}")
return True
def stop_camera(self, camera_id: int) -> bool:
"""Stop processing a specific camera"""
if camera_id not in self.cameras:
return False
# Release camera
if camera_id in self.camera_caps:
self.camera_caps[camera_id].release()
del self.camera_caps[camera_id]
self.camera_status[camera_id] = CameraStatus.DISCONNECTED
# Wait for thread to finish
if camera_id in self.camera_threads:
self.camera_threads[camera_id].join(timeout=2)
del self.camera_threads[camera_id]
print(f"[MultiCamera] Stopped camera {camera_id}")
return True
def start_all_cameras(self) -> bool:
"""Start all enabled cameras"""
success_count = 0
for camera_id in self.cameras:
if self.cameras[camera_id].enabled:
if self.start_camera(camera_id):
success_count += 1
print(f"[MultiCamera] Started {success_count}/{len(self.cameras)} cameras")
return success_count > 0
def stop_all_cameras(self) -> bool:
"""Stop all cameras"""
for camera_id in list(self.cameras.keys()):
self.stop_camera(camera_id)
print("[MultiCamera] Stopped all cameras")
return True
def _process_camera(self, camera_id: int):
"""Process frames from a specific camera"""
cap = self.camera_caps[camera_id]
config = self.cameras[camera_id]
frame_count = 0
last_time = time.time()
while self.is_running and camera_id in self.camera_caps:
ret, frame = cap.read()
if not ret:
print(f"[MultiCamera] Camera {camera_id} failed to read frame")
self.error_counts[camera_id] += 1
time.sleep(0.1)
continue
# Calculate FPS
current_time = time.time()
fps = 1.0 / (current_time - last_time) if current_time > last_time else 0
self.fps_counters[camera_id].append(fps)
last_time = current_time
# Create frame object
camera_frame = CameraFrame(
camera_id=camera_id,
frame=frame.copy(),
timestamp=current_time,
frame_number=frame_count
)
# Add to queue (non-blocking)
try:
self.frame_queues[camera_id].put_nowait(camera_frame)
except queue.Full:
# Remove oldest frame if queue is full
try:
self.frame_queues[camera_id].get_nowait()
self.frame_queues[camera_id].put_nowait(camera_frame)
except queue.Empty:
pass
# Notify callbacks
for callback in self.frame_callbacks:
try:
callback(camera_frame)
except Exception as e:
print(f"[MultiCamera] Frame callback error: {e}")
frame_count += 1
# Control frame rate
target_interval = 1.0 / config.fps
elapsed = time.time() - current_time
if elapsed < target_interval:
time.sleep(target_interval - elapsed)
print(f"[MultiCamera] Camera {camera_id} processing stopped")
def get_latest_frame(self, camera_id: int) -> Optional[CameraFrame]:
"""Get the latest frame from a specific camera"""
if camera_id not in self.frame_queues:
return None
try:
return self.frame_queues[camera_id].get_nowait()
except queue.Empty:
return None
def get_all_latest_frames(self) -> Dict[int, CameraFrame]:
"""Get latest frames from all cameras"""
frames = {}
for camera_id in self.cameras:
frame = self.get_latest_frame(camera_id)
if frame:
frames[camera_id] = frame
return frames
def add_frame_callback(self, callback: Callable[[CameraFrame], None]):
"""Add a callback for new frames"""
self.frame_callbacks.append(callback)
def add_detection_callback(self, callback: Callable[[int, dict], None]):
"""Add a callback for detection results"""
self.detection_callbacks.append(callback)
def get_camera_status(self, camera_id: int) -> Optional[CameraStatus]:
"""Get status of a specific camera"""
return self.camera_status.get(camera_id)
def get_all_camera_status(self) -> Dict[int, CameraStatus]:
"""Get status of all cameras"""
return self.camera_status.copy()
def get_camera_fps(self, camera_id: int) -> float:
"""Get average FPS for a camera"""
if camera_id not in self.fps_counters or not self.fps_counters[camera_id]:
return 0.0
return sum(self.fps_counters[camera_id]) / len(self.fps_counters[camera_id])
def get_camera_error_count(self, camera_id: int) -> int:
"""Get error count for a camera"""
return self.error_counts.get(camera_id, 0)
def start(self):
"""Start the multi-camera manager"""
self.is_running = True
self.start_all_cameras()
print("[MultiCamera] Manager started")
def stop(self):
"""Stop the multi-camera manager"""
self.is_running = False
self.stop_all_cameras()
print("[MultiCamera] Manager stopped")
def get_camera_config(self, camera_id: int) -> Optional[CameraConfig]:
"""Get configuration for a specific camera"""
return self.cameras.get(camera_id)
def update_camera_config(self, camera_id: int, **kwargs) -> bool:
"""Update configuration for a specific camera"""
if camera_id not in self.cameras:
return False
config = self.cameras[camera_id]
for key, value in kwargs.items():
if hasattr(config, key):
setattr(config, key, value)
print(f"[MultiCamera] Updated camera {camera_id} configuration")
return True