""" Camera Module for USB camera capture and frame processing Integrates face detection and zone tracking. """ import cv2 import threading import time from face_detector import FaceDetector from zone_tracker import ZoneTracker class Camera: def __init__(self, camera_index=0, process_every_n_frames=3, face_confidence=0.5, frame_width=640, frame_height=480): """ Initialize camera and processing components. Args: camera_index: Index of the USB camera (usually 0) process_every_n_frames: Process face detection every N frames for performance face_confidence: Confidence threshold for face detection frame_width: Desired frame width frame_height: Desired frame height """ self.camera_index = camera_index self.process_every_n_frames = process_every_n_frames self.frame_width = frame_width self.frame_height = frame_height # Initialize camera self.cap = cv2.VideoCapture(camera_index) if not self.cap.isOpened(): raise RuntimeError(f"Failed to open camera {camera_index}") # Set camera properties self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, frame_width) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height) # Initialize face detector and zone tracker self.face_detector = FaceDetector(confidence_threshold=face_confidence) self.zone_tracker = None # Will be initialized after first frame # Frame processing state self.frame_counter = 0 self.current_frame = None self.processed_frame = None self.current_counts = { 'total_entered': 0, 'total_exited': 0, 'current_occupancy': 0 } # Thread safety self.lock = threading.Lock() self.running = False self.processing_thread = None # Initialize zone tracker after getting first frame dimensions ret, frame = self.cap.read() if ret: h, w = frame.shape[:2] self.zone_tracker = ZoneTracker(w) self.frame_width = w self.frame_height = h def start(self): """Start the camera and processing thread.""" if self.running: return self.running = True self.processing_thread = threading.Thread(target=self._process_loop, daemon=True) self.processing_thread.start() def stop(self): """Stop the camera and processing thread.""" self.running = False if self.processing_thread: self.processing_thread.join(timeout=2.0) if self.cap: self.cap.release() def _process_loop(self): """Main processing loop running in background thread.""" while self.running: ret, frame = self.cap.read() if not ret: time.sleep(0.1) continue self.frame_counter += 1 # Store current frame with self.lock: self.current_frame = frame.copy() # Process face detection every N frames if self.frame_counter % self.process_every_n_frames == 0: processed_frame, counts = self._process_frame(frame) with self.lock: self.processed_frame = processed_frame self.current_counts = counts def _process_frame(self, frame): """ Process a single frame: detect faces, track zones, update counts. Args: frame: Input frame from camera Returns: Tuple of (processed_frame, counts_dict) """ # Detect faces faces = self.face_detector.detect_faces(frame) # Track zones and update counts if self.zone_tracker: counts = self.zone_tracker.process_faces(faces) else: counts = { 'total_entered': 0, 'total_exited': 0, 'current_occupancy': 0 } # Draw zones on frame if self.zone_tracker: processed_frame = self.zone_tracker.draw_zones(frame) else: processed_frame = frame.copy() # Draw faces on frame processed_frame = self.face_detector.draw_faces(processed_frame, faces) # Draw count information on frame text_y = 60 cv2.putText(processed_frame, f"Entered: {counts['total_entered']}", (10, text_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(processed_frame, f"Exited: {counts['total_exited']}", (10, text_y + 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.putText(processed_frame, f"Occupancy: {counts['current_occupancy']}", (10, text_y + 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) return processed_frame, counts def get_frame(self): """ Get the most recent processed frame. Returns: JPEG encoded frame bytes, or None if no frame available """ with self.lock: if self.processed_frame is not None: ret, buffer = cv2.imencode('.jpg', self.processed_frame, [cv2.IMWRITE_JPEG_QUALITY, 85]) if ret: return buffer.tobytes() return None def get_counts(self): """ Get current count statistics. Returns: Dictionary with total_entered, total_exited, current_occupancy """ with self.lock: return self.current_counts.copy() def reset_counts(self): """Reset all counters.""" with self.lock: if self.zone_tracker: self.zone_tracker.reset_counts() self.current_counts = { 'total_entered': 0, 'total_exited': 0, 'current_occupancy': 0 } def __enter__(self): """Context manager entry.""" self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit.""" self.stop()