feat: initial implementation of People Counter web app
- Add Flask application with MJPEG video streaming - Implement OpenCV DNN face detection module - Add zone-based entry/exit tracking with cooldown mechanism - Create web interface with real-time WebSocket updates - Add model download script and comprehensive README - Include OpenCV DNN model files for face detection
This commit is contained in:
191
camera.py
Normal file
191
camera.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user