Spaces:
Sleeping
Sleeping
| """ | |
| Murderer Detector - A Humorous Webcam Person Detection Demo | |
| This app demonstrates real-time person detection with humorous labeling. | |
| It's structured to be easily modified for serious applications like: | |
| - Security monitoring | |
| - PPE detection | |
| - Customer analytics | |
| - Social distancing monitoring | |
| Architecture: | |
| 1. Detection Module: Uses YOLO for person detection (easily swappable) | |
| 2. Classification Logic: Generates humorous labels (swap for real ML inference) | |
| 3. Annotation Layer: Draws boxes and labels (customize visuals) | |
| 4. Streaming Handler: Processes webcam feed via Gradio | |
| """ | |
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from ultralytics import YOLO | |
| import random | |
| from typing import Tuple, List, Dict | |
| import time | |
| import os | |
| # ============================================================================ | |
| # DETECTION MODULE | |
| # Swap this section for different detection models or backends | |
| # ============================================================================ | |
| class PersonDetector: | |
| """ | |
| Person detection using YOLO. | |
| For serious applications, modify this to: | |
| - Use different models (MediaPipe, custom trained models) | |
| - Add specific object detection (weapons, PPE, etc.) | |
| - Integrate with cloud APIs (AWS Rekognition, Google Vision) | |
| """ | |
| def __init__(self, model_name: str = "yolov8n.pt", confidence: float = 0.5): | |
| """ | |
| Initialize the person detector. | |
| Args: | |
| model_name: YOLO model to use (n=nano, s=small, m=medium, l=large) | |
| confidence: Detection confidence threshold | |
| """ | |
| self.model = YOLO(model_name) | |
| self.confidence = confidence | |
| def detect_persons(self, frame: np.ndarray) -> List[Dict]: | |
| """ | |
| Detect persons in a frame. | |
| Args: | |
| frame: Input image as numpy array (BGR format) | |
| Returns: | |
| List of detections with bounding boxes and confidence scores | |
| """ | |
| # Run inference | |
| results = self.model(frame, conf=self.confidence, | |
| classes=[0], verbose=False) | |
| detections = [] | |
| for result in results: | |
| boxes = result.boxes | |
| for box in boxes: | |
| x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() | |
| confidence = float(box.conf[0]) | |
| detections.append({ | |
| 'bbox': (int(x1), int(y1), int(x2), int(y2)), | |
| 'confidence': confidence | |
| }) | |
| return detections | |
| # ============================================================================ | |
| # CLASSIFICATION LOGIC | |
| # Replace this section for serious applications | |
| # ============================================================================ | |
| class MurdererClassifier: | |
| """ | |
| Humorous 'threat' classification. | |
| For serious applications, replace with: | |
| - Emotion detection models | |
| - Action recognition (violence, falls, etc.) | |
| - Anomaly detection | |
| - Age/gender classification | |
| """ | |
| # Short, punchy threat labels | |
| LABELS = [ | |
| "π₯£ SERIAL BREAKFAST SKIPPER", | |
| "π΄ SUSPICIOUSLY WELL-RESTED", | |
| "πΊ TRUE CRIME WATCHER", | |
| "π DANGEROUS BOOK READER", | |
| "π₯ SERIAL CEREAL KILLER", | |
| "π TOO POLITE. SUSPICIOUS.", | |
| "π PROFESSIONAL LURKER", | |
| "πͺ΄ PLANT WHISPERER", | |
| "π ALLEGED DOG PETTER", | |
| "β NOTORIOUS TEA DRINKER", | |
| "π€ CONFIRMED OVERTHINKER", | |
| "π§ BACKGROUND STANDER", | |
| "π€ GETS 8 HOURS SLEEP", | |
| "π§ DRINKS WATER DAILY", | |
| "π OWNS MULTIPLE USB-C CABLES", | |
| "π΅ CAN WHISTLE & SNAP", | |
| "ποΈ FITTED SHEET FOLDER", | |
| "π READ 3+ BOOKS", | |
| "πΏ TALKS TO HOUSEPLANTS", | |
| "π― UNUSUALLY GOOD AT TRIVIA", | |
| ] | |
| def __init__(self): | |
| """Initialize the classifier with tracking for consistent labels.""" | |
| self.person_history = {} | |
| self.next_id = 0 | |
| def classify(self, detection: Dict) -> Dict: | |
| """ | |
| Generate humorous classification for a detected person. | |
| Args: | |
| detection: Detection dict with bbox and confidence | |
| Returns: | |
| Classification dict with label and threat level | |
| """ | |
| # Generate random threat assessment | |
| threat_level = random.randint(45, 99) | |
| label = random.choice(self.LABELS) | |
| return { | |
| 'threat_level': threat_level, | |
| 'label': label, | |
| 'confidence': detection['confidence'] | |
| } | |
| # ============================================================================ | |
| # ANNOTATION LAYER | |
| # Customize this section for different visual styles | |
| # ============================================================================ | |
| class FrameAnnotator: | |
| """ | |
| Draws annotations on frames. | |
| Modify this to: | |
| - Change colors, styles, fonts | |
| - Add different visualization modes | |
| - Include overlay graphics or warnings | |
| - Show statistics or heatmaps | |
| """ | |
| def __init__(self): | |
| """Initialize annotator with color schemes.""" | |
| self.colors = { | |
| 'high_threat': (0, 0, 255), # Red | |
| 'medium_threat': (0, 165, 255), # Orange | |
| 'low_threat': (0, 255, 255), # Yellow | |
| 'text_bg': (0, 0, 0), # Black | |
| 'text': (255, 255, 255) # White | |
| } | |
| def get_threat_color(self, threat_level: int) -> Tuple[int, int, int]: | |
| """Get color based on threat level.""" | |
| if threat_level >= 80: | |
| return self.colors['high_threat'] | |
| elif threat_level >= 60: | |
| return self.colors['medium_threat'] | |
| else: | |
| return self.colors['low_threat'] | |
| def annotate_frame( | |
| self, | |
| frame: np.ndarray, | |
| detections: List[Dict], | |
| classifications: List[Dict] | |
| ) -> np.ndarray: | |
| """ | |
| Draw annotations on frame. | |
| Args: | |
| frame: Input frame | |
| detections: List of detection dicts | |
| classifications: List of classification dicts | |
| Returns: | |
| Annotated frame | |
| """ | |
| annotated = frame.copy() | |
| # Draw header | |
| self._draw_header(annotated, len(detections)) | |
| # Annotate each detection | |
| for detection, classification in zip(detections, classifications): | |
| self._draw_detection(annotated, detection, classification) | |
| return annotated | |
| def _draw_header(self, frame: np.ndarray, num_suspects: int): | |
| """Draw header with suspect count.""" | |
| header_text = f"π¨ SUSPECTS DETECTED: {num_suspects} π¨" | |
| font = cv2.FONT_HERSHEY_DUPLEX | |
| font_scale = 0.8 | |
| thickness = 2 | |
| # Get text size | |
| (text_width, text_height), baseline = cv2.getTextSize( | |
| header_text, font, font_scale, thickness | |
| ) | |
| # Draw background | |
| cv2.rectangle( | |
| frame, | |
| (0, 0), | |
| (frame.shape[1], text_height + baseline + 20), | |
| (0, 0, 0), | |
| -1 | |
| ) | |
| # Draw text | |
| x = (frame.shape[1] - text_width) // 2 | |
| y = text_height + 10 | |
| cv2.putText( | |
| frame, header_text, (x, y), | |
| font, font_scale, (0, 0, 255), thickness | |
| ) | |
| def _draw_detection( | |
| self, | |
| frame: np.ndarray, | |
| detection: Dict, | |
| classification: Dict | |
| ): | |
| """Draw bounding box and labels for a detection.""" | |
| x1, y1, x2, y2 = detection['bbox'] | |
| threat_level = classification['threat_level'] | |
| color = self.get_threat_color(threat_level) | |
| # Draw bounding box | |
| cv2.rectangle(frame, (x1, y1), (x2, y2), color, 3) | |
| # Create single label with threat level | |
| label = f"{classification['label']} ({threat_level}%)" | |
| # Draw label with larger, more readable text | |
| font = cv2.FONT_HERSHEY_DUPLEX | |
| font_scale = 0.7 | |
| thickness = 2 | |
| padding = 8 | |
| (text_width, text_height), baseline = cv2.getTextSize( | |
| label, font, font_scale, thickness | |
| ) | |
| # Position label above bounding box, or below if too close to top | |
| y_offset = y1 - 10 | |
| if y_offset - text_height - padding < 0: | |
| y_offset = y2 + text_height + padding + 10 | |
| # Draw background rectangle | |
| cv2.rectangle( | |
| frame, | |
| (x1, y_offset - text_height - padding), | |
| (x1 + text_width + padding * 2, y_offset + baseline + padding), | |
| self.colors['text_bg'], | |
| -1 | |
| ) | |
| # Draw text | |
| cv2.putText( | |
| frame, | |
| label, | |
| (x1 + padding, y_offset), | |
| font, | |
| font_scale, | |
| color, | |
| thickness | |
| ) | |
| # ============================================================================ | |
| # STREAMING HANDLER | |
| # Main processing pipeline | |
| # ============================================================================ | |
| class MurdererDetector: | |
| """ | |
| Main application class that combines all modules. | |
| """ | |
| def __init__(self): | |
| """Initialize all components.""" | |
| print("π Initializing Murderer Detector...") | |
| self.detector = PersonDetector(model_name="yolov8n.pt", confidence=0.5) | |
| self.classifier = MurdererClassifier() | |
| self.annotator = FrameAnnotator() | |
| self.previous_suspect_count = 0 | |
| print("β Ready to detect suspicious individuals!") | |
| def process_frame(self, frame: np.ndarray) -> Tuple[np.ndarray, int]: | |
| """ | |
| Process a single frame. | |
| Args: | |
| frame: Input frame from webcam | |
| Returns: | |
| Tuple of (annotated frame, suspect count) | |
| """ | |
| if frame is None: | |
| return None, 0 | |
| # Detect all persons | |
| all_detections = self.detector.detect_persons(frame) | |
| # Filter out the user (largest person, presumably closest to camera) | |
| # Only flag people behind the user | |
| detections = self._filter_user(all_detections) | |
| # Classify each detection | |
| classifications = [ | |
| self.classifier.classify(det) for det in detections | |
| ] | |
| # Annotate frame | |
| annotated = self.annotator.annotate_frame( | |
| frame, detections, classifications | |
| ) | |
| # Track count for sound alerts | |
| current_count = len(detections) | |
| return annotated, current_count | |
| def _filter_user(self, detections: List[Dict]) -> List[Dict]: | |
| """ | |
| Filter out the user (largest person) from detections. | |
| The assumption is that the user is sitting in front of the webcam | |
| and will be the largest person in the frame. We want to flag only | |
| people behind them. | |
| Args: | |
| detections: List of all person detections | |
| Returns: | |
| Filtered detections excluding the user | |
| """ | |
| if len(detections) <= 1: | |
| # If only one person or no people, don't flag anyone | |
| return [] | |
| # Calculate area for each detection | |
| detections_with_area = [] | |
| for det in detections: | |
| x1, y1, x2, y2 = det['bbox'] | |
| area = (x2 - x1) * (y2 - y1) | |
| detections_with_area.append((det, area)) | |
| # Sort by area (largest first) | |
| detections_with_area.sort(key=lambda x: x[1], reverse=True) | |
| # Return all except the largest (the user) | |
| return [det for det, _ in detections_with_area[1:]] | |
| # ============================================================================ | |
| # GRADIO INTERFACE | |
| # ============================================================================ | |
| def create_interface(): | |
| """Create and configure the Gradio interface.""" | |
| # Initialize detector | |
| app = MurdererDetector() | |
| # Create interface | |
| with gr.Blocks(title="Murderer Detector πͺ") as demo: | |
| gr.Markdown(""" | |
| # πͺ Murderer Detector π | |
| **DISCLAIMER:** This is for funs. | |
| """) | |
| # Unified webcam display (input and output combined) | |
| webcam = gr.Image( | |
| sources="webcam", | |
| streaming=True, | |
| label="Murderer Detector (requires webcam on)" | |
| ) | |
| # Hidden counter for sound alerts (triggers JS when value changes) | |
| suspect_count = gr.Number(value=0, visible=False) | |
| # Sound alert JavaScript | |
| alert_sound = gr.HTML(""" | |
| <script> | |
| let previousCount = 0; | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| function playAlertSound() { | |
| // Create funny "ding-dong" doorbell sound | |
| const now = audioContext.currentTime; | |
| const oscillator1 = audioContext.createOscillator(); | |
| const oscillator2 = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| // First "ding" (higher note) | |
| oscillator1.connect(gainNode); | |
| oscillator1.frequency.value = 800; | |
| oscillator1.type = 'sine'; | |
| // Second "dong" (lower note) | |
| oscillator2.connect(gainNode); | |
| oscillator2.frequency.value = 600; | |
| oscillator2.type = 'sine'; | |
| gainNode.connect(audioContext.destination); | |
| gainNode.gain.setValueAtTime(0.3, now); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.15); | |
| // Play ding | |
| oscillator1.start(now); | |
| oscillator1.stop(now + 0.15); | |
| // Play dong | |
| oscillator2.start(now + 0.15); | |
| oscillator2.stop(now + 0.3); | |
| } | |
| // Monitor for count changes | |
| setInterval(() => { | |
| const countElement = document.querySelector('input[type="number"]'); | |
| if (countElement) { | |
| const currentCount = parseInt(countElement.value) || 0; | |
| if (currentCount > previousCount && currentCount > 0) { | |
| playAlertSound(); | |
| console.log('π¨ New suspect detected! Count:', currentCount); | |
| } | |
| previousCount = currentCount; | |
| } | |
| }, 100); | |
| </script> | |
| """) | |
| gr.Markdown(""" | |
| # π How It Works | |
| 1. ** Enable your webcam ** - Click the webcam button above, then Record | |
| 2. ** Detects creepers behind you ** | |
| 3. ** Plays alert sound ** when new suspects appear! π | |
| # π― Detection Features | |
| - Real-time person detection using YOLOv8 | |
| - Threat level assessment(totally scientific π ) | |
| - Color-coded danger ratings | |
| - Running suspect count | |
| - Sound alerts (ding-dong when count increases!) | |
| """) | |
| # Set up streaming (unified input/output) | |
| webcam.stream( | |
| fn=app.process_frame, | |
| inputs=[webcam], | |
| outputs=[webcam, suspect_count], | |
| stream_every=0.1, | |
| concurrency_limit=30 | |
| ) | |
| return demo | |
| # ============================================================================ | |
| # MAIN | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| demo = create_interface() | |
| # Only use share=True for local development (not on HF Spaces) | |
| is_spaces = os.getenv("SPACE_ID") is not None | |
| demo.launch(share=not is_spaces) | |