yolo-object-detection / multi_camera.py
techventurecrew
Deploying Streamlit YOLO application with CPU-optimized weights
f434690
Raw
History Blame Contribute Delete
14.1 kB
import cv2
import threading
import queue
import time
import numpy as np
from ultralytics import YOLO
from datetime import datetime
import json
class CameraThread:
"""Individual camera thread"""
def __init__(self, camera_id, url=None, name=None):
self.camera_id = camera_id
self.url = url if url else camera_id
self.name = name if name else f"Camera_{camera_id}"
self.cap = None
self.running = False
self.frame_queue = queue.Queue(maxsize=2)
self.last_frame = None
self.fps = 0
self.stats = {'total_frames': 0, 'dropped_frames': 0}
def start(self):
"""Start camera capture thread"""
self.cap = cv2.VideoCapture(self.url)
if not self.cap.isOpened():
print(f"Failed to open {self.name}")
return False
self.running = True
self.thread = threading.Thread(target=self._capture_loop)
self.thread.daemon = True
self.thread.start()
return True
def _capture_loop(self):
"""Capture frames in loop"""
prev_time = time.time()
while self.running:
ret, frame = self.cap.read()
if not ret:
print(f"Failed to read from {self.name}")
break
# Calculate FPS
current_time = time.time()
self.fps = 1 / (current_time - prev_time)
prev_time = current_time
# Add to queue (drop if full)
if self.frame_queue.full():
try:
self.frame_queue.get_nowait()
self.stats['dropped_frames'] += 1
except queue.Empty:
pass
try:
self.frame_queue.put_nowait(frame)
self.stats['total_frames'] += 1
except queue.Full:
pass
self.last_frame = frame
self.cap.release()
def get_frame(self):
"""Get latest frame from queue"""
try:
frame = self.frame_queue.get_nowait()
return frame
except queue.Empty:
return self.last_frame
def stop(self):
"""Stop camera thread"""
self.running = False
if hasattr(self, 'thread'):
self.thread.join(timeout=2)
if self.cap:
self.cap.release()
def get_stats(self):
"""Get camera statistics"""
return {
'name': self.name,
'camera_id': self.camera_id,
'fps': round(self.fps, 2),
'total_frames': self.stats['total_frames'],
'dropped_frames': self.stats['dropped_frames'],
'is_running': self.running
}
class MultiCameraDetector:
"""Multi-camera object detection system"""
def __init__(self, model_path='yolov8n.pt'):
self.model = YOLO(model_path)
self.cameras = {}
self.detection_results = {}
self.running = False
def add_camera(self, camera_id, url=None, name=None):
"""Add a camera to the system"""
camera = CameraThread(camera_id, url, name)
self.cameras[camera_id] = camera
print(f"Added camera: {camera.name}")
return camera
def add_cameras_batch(self, camera_configs):
"""Add multiple cameras from configuration"""
for config in camera_configs:
self.add_camera(
config['id'],
config.get('url'),
config.get('name')
)
def start_all(self):
"""Start all cameras"""
for camera_id, camera in self.cameras.items():
if camera.start():
print(f"Started {camera.name}")
else:
print(f"Failed to start {camera.name}")
self.running = True
def detect_frame(self, frame):
"""Run detection on single frame"""
results = self.model(frame, verbose=False)
detections = []
if results[0].boxes is not None:
for box in results[0].boxes:
detections.append({
'class': self.model.names[int(box.cls)],
'confidence': float(box.conf),
'bbox': box.xyxy.tolist()[0]
})
annotated = results[0].plot()
return annotated, detections
def process_all_cameras(self):
"""Process all cameras and run detection"""
results = {}
for camera_id, camera in self.cameras.items():
frame = camera.get_frame()
if frame is not None:
annotated, detections = self.detect_frame(frame)
results[camera_id] = {
'frame': annotated,
'detections': detections,
'camera_stats': camera.get_stats(),
'timestamp': datetime.now().isoformat()
}
self.detection_results[camera_id] = results[camera_id]
return results
def create_grid_display(self, max_cols=2):
"""Create grid display of all camera feeds"""
frames = []
labels = []
for camera_id, camera in self.cameras.items():
frame = camera.get_frame()
if frame is not None:
# Add detection overlay if available
if camera_id in self.detection_results:
detections = self.detection_results[camera_id]['detections']
for det in detections:
bbox = det['bbox']
cv2.rectangle(frame, (int(bbox[0]), int(bbox[1])),
(int(bbox[2]), int(bbox[3])), (0, 255, 0), 2)
label = f"{det['class']}: {det['confidence']:.2f}"
cv2.putText(frame, label, (int(bbox[0]), int(bbox[1])-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# Add camera name
cv2.putText(frame, camera.name, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
# Add FPS
fps_text = f"FPS: {camera.fps:.1f}"
cv2.putText(frame, fps_text, (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
frames.append(frame)
# Create grid
if frames:
rows = (len(frames) + max_cols - 1) // max_cols
grid_height = max(frame.shape[0] for frame in frames)
grid_width = max(frame.shape[1] for frame in frames)
grid = np.zeros((grid_height * rows, grid_width * max_cols, 3), dtype=np.uint8)
for idx, frame in enumerate(frames):
row = idx // max_cols
col = idx % max_cols
h, w = frame.shape[:2]
grid[row*grid_height:row*grid_height+h,
col*grid_width:col*grid_width+w] = frame
return grid
return None
def stop_all(self):
"""Stop all cameras"""
for camera in self.cameras.values():
camera.stop()
self.running = False
print("All cameras stopped")
def get_system_stats(self):
"""Get overall system statistics"""
total_frames = sum(c.stats['total_frames'] for c in self.cameras.values())
total_dropped = sum(c.stats['dropped_frames'] for c in self.cameras.values())
return {
'total_cameras': len(self.cameras),
'active_cameras': sum(1 for c in self.cameras.values() if c.running),
'total_frames_processed': total_frames,
'total_frames_dropped': total_dropped,
'drop_rate': total_dropped / total_frames if total_frames > 0 else 0,
'cameras': {cid: c.get_stats() for cid, c in self.cameras.items()}
}
# Configuration examples
CAMERA_CONFIGS = [
{'id': 0, 'name': 'Front Door', 'url': 0},
{'id': 1, 'name': 'Back Door', 'url': 1},
# {'id': 2, 'name': 'IP Camera 1', 'url': 'http://192.168.1.100:8080/video'},
# {'id': 3, 'name': 'IP Camera 2', 'url': 'rtsp://192.168.1.101:554/stream'},
]
# Multi-camera GUI using tkinter
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
class MultiCameraGUI:
def __init__(self, root):
self.root = root
self.root.title("Multi-Camera Object Detection System")
self.root.geometry("1400x800")
self.detector = MultiCameraDetector()
self.setup_ui()
def setup_ui(self):
# Control panel
control_frame = tk.Frame(self.root)
control_frame.pack(pady=10)
tk.Button(control_frame, text="Start All Cameras",
command=self.start_cameras, bg="green", fg="white").pack(side=tk.LEFT, padx=5)
tk.Button(control_frame, text="Stop All Cameras",
command=self.stop_cameras, bg="red", fg="white").pack(side=tk.LEFT, padx=5)
tk.Button(control_frame, text="Add Camera",
command=self.add_camera_dialog, bg="blue", fg="white").pack(side=tk.LEFT, padx=5)
tk.Button(control_frame, text="Save Stats",
command=self.save_stats, bg="orange").pack(side=tk.LEFT, padx=5)
# Camera grid display
self.canvas_frame = tk.Frame(self.root)
self.canvas_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
self.canvas = tk.Canvas(self.canvas_frame, bg='black')
self.canvas.pack(expand=True, fill=tk.BOTH)
# Stats panel
self.stats_text = tk.Text(self.root, height=8, width=50)
self.stats_text.pack(side=tk.RIGHT, padx=10, pady=10)
# Status bar
self.status_var = tk.StringVar()
self.status_var.set("Ready")
status_bar = tk.Label(self.root, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# Update loop
self.update_display()
def start_cameras(self):
# Add default cameras
for config in CAMERA_CONFIGS:
self.detector.add_camera(config['id'], config.get('url'), config.get('name'))
self.detector.start_all()
self.status_var.set(f"Started {len(self.detector.cameras)} cameras")
def stop_cameras(self):
self.detector.stop_all()
self.status_var.set("All cameras stopped")
def add_camera_dialog(self):
dialog = tk.Toplevel(self.root)
dialog.title("Add Camera")
dialog.geometry("400x250")
tk.Label(dialog, text="Camera ID:").pack(pady=5)
camera_id_entry = tk.Entry(dialog)
camera_id_entry.pack(pady=5)
tk.Label(dialog, text="Camera Name:").pack(pady=5)
name_entry = tk.Entry(dialog)
name_entry.pack(pady=5)
tk.Label(dialog, text="URL (optional):").pack(pady=5)
url_entry = tk.Entry(dialog)
url_entry.pack(pady=5)
def add():
camera_id = int(camera_id_entry.get())
name = name_entry.get()
url = url_entry.get() or camera_id
self.detector.add_camera(camera_id, url, name)
dialog.destroy()
self.status_var.set(f"Added camera: {name}")
tk.Button(dialog, text="Add", command=add).pack(pady=10)
def update_display(self):
if self.detector.running:
# Process detections
results = self.detector.process_all_cameras()
# Create grid display
grid = self.detector.create_grid_display(max_cols=2)
if grid is not None:
# Resize for display
h, w = grid.shape[:2]
scale = min(800/h, 1200/w)
new_w, new_h = int(w * scale), int(h * scale)
grid = cv2.resize(grid, (new_w, new_h))
# Convert to PhotoImage
grid_rgb = cv2.cvtColor(grid, cv2.COLOR_BGR2RGB)
img = Image.fromarray(grid_rgb)
photo = ImageTk.PhotoImage(img)
self.canvas.config(width=new_w, height=new_h)
self.canvas.create_image(0, 0, anchor=tk.NW, image=photo)
self.canvas.image = photo
# Update stats
stats = self.detector.get_system_stats()
self.stats_text.delete(1.0, tk.END)
self.stats_text.insert(tk.END, json.dumps(stats, indent=2))
self.stats_text.see(tk.END)
self.status_var.set(f"Active: {stats['active_cameras']}/{stats['total_cameras']} cameras")
# Schedule next update
self.root.after(100, self.update_display)
def save_stats(self):
stats = self.detector.get_system_stats()
filename = f"camera_stats_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
with open(filename, 'w') as f:
json.dump(stats, f, indent=2)
self.status_var.set(f"Stats saved to {filename}")
if __name__ == "__main__":
# Option 1: Run GUI
root = tk.Tk()
app = MultiCameraGUI(root)
root.mainloop()
# Option 2: Run headless
# detector = MultiCameraDetector()
# detector.add_cameras_batch(CAMERA_CONFIGS)
# detector.start_all()
#
# while True:
# results = detector.process_all_cameras()
# grid = detector.create_grid_display()
# if grid is not None:
# cv2.imshow('Multi-Camera Detection', grid)
#
# if cv2.waitKey(1) & 0xFF == ord('q'):
# break
#
# detector.stop_all()
# cv2.destroyAllWindows()