Spaces:
Runtime error
Runtime error
| import cv2 | |
| import numpy as np | |
| import mediapipe as mp | |
| import gradio as gr | |
| import math | |
| from collections import deque | |
| import time | |
| # Initialize MediaPipe Hand solution | |
| mp_hands = mp.solutions.hands | |
| mp_drawing = mp.solutions.drawing_utils | |
| hands = mp_hands.Hands( | |
| static_image_mode=False, | |
| max_num_hands=1, | |
| min_detection_confidence=0.5, | |
| min_tracking_confidence=0.5 | |
| ) | |
| # Initialize deque to store finger path points (with max length to avoid memory issues) | |
| max_path_length = 100 | |
| finger_path = deque(maxlen=max_path_length) | |
| # Flag to start/stop drawing | |
| is_drawing = False | |
| # Perfect shapes for comparison | |
| def create_perfect_circle(center, radius, num_points=100): | |
| """Create points for a perfect circle""" | |
| circle_points = [] | |
| for i in range(num_points): | |
| angle = 2 * math.pi * i / num_points | |
| x = int(center[0] + radius * math.cos(angle)) | |
| y = int(center[1] + radius * math.sin(angle)) | |
| circle_points.append((x, y)) | |
| return circle_points | |
| # Initial target shape - circle | |
| target_shape_name = "circle" | |
| target_shape = None # Will be set based on frame dimensions | |
| # Calculate similarity between drawn path and target shape | |
| def calculate_similarity(path, target_shape): | |
| """Calculate similarity between drawn path and target shape using shape metrics""" | |
| if len(path) < 5: # Not enough points to calculate | |
| return 0 | |
| # Convert path to numpy array | |
| path_array = np.array(list(path)) | |
| # Simple metrics - compare against circle properties | |
| # 1. Calculate centroid of the drawn shape | |
| centroid = np.mean(path_array, axis=0) | |
| # 2. Calculate distances from centroid to each point | |
| distances = np.sqrt(np.sum((path_array - centroid)**2, axis=1)) | |
| # 3. For a perfect circle, the standard deviation of distances should be close to 0 | |
| std_dev = np.std(distances) | |
| mean_dist = np.mean(distances) | |
| # 4. Normalize the standard deviation as a percentage of the mean radius | |
| if mean_dist > 0: | |
| # Lower std_dev means more circular | |
| similarity = max(0, 100 - (std_dev / mean_dist * 100)) | |
| return min(100, similarity) # Cap at 100% | |
| return 0 | |
| def process_frame(frame): | |
| """Process a single frame from the webcam""" | |
| global finger_path, is_drawing, target_shape | |
| if frame is None: | |
| # Return a blank frame if input is None | |
| return np.zeros((480, 640, 3), dtype=np.uint8) | |
| # Set target shape if not already set | |
| if target_shape is None: | |
| height, width = frame.shape[:2] | |
| center = (width // 2, height // 2) | |
| radius = min(width, height) // 4 | |
| target_shape = create_perfect_circle(center, radius) | |
| # Convert the BGR image to RGB | |
| rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| # Process the frame with MediaPipe Hands | |
| results = hands.process(rgb_frame) | |
| # Draw target shape (circle for now) | |
| for point in target_shape: | |
| cv2.circle(frame, point, 1, (0, 255, 0), -1) | |
| # If hands are detected | |
| if results.multi_hand_landmarks: | |
| for hand_landmarks in results.multi_hand_landmarks: | |
| # Draw hand landmarks | |
| mp_drawing.draw_landmarks( | |
| frame, hand_landmarks, mp_hands.HAND_CONNECTIONS) | |
| # Get index finger tip coordinates | |
| index_finger_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP] | |
| h, w, c = frame.shape | |
| x, y = int(index_finger_tip.x * w), int(index_finger_tip.y * h) | |
| # Check if index finger is raised (simplified) | |
| index_finger_mcp = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP] | |
| if index_finger_tip.y < index_finger_mcp.y: # Index finger is raised | |
| is_drawing = True | |
| finger_path.append((x, y)) | |
| # Draw a filled circle at the finger tip | |
| cv2.circle(frame, (x, y), 10, (255, 0, 0), -1) | |
| else: | |
| is_drawing = False | |
| # Draw the finger path | |
| if len(finger_path) > 1: | |
| for i in range(1, len(finger_path)): | |
| cv2.line(frame, finger_path[i-1], finger_path[i], (0, 0, 255), 2) | |
| # Calculate and display similarity score | |
| similarity = calculate_similarity(finger_path, target_shape) | |
| cv2.putText( | |
| frame, f"Score: {similarity:.1f}%", (10, 30), | |
| cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2 | |
| ) | |
| # Display instructions | |
| cv2.putText( | |
| frame, "Draw a circle with your index finger", (10, 70), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2 | |
| ) | |
| return frame | |
| def clear_path(): | |
| """Clear the current drawing path""" | |
| global finger_path | |
| finger_path.clear() | |
| return "Path cleared!" | |
| def webcam_feed(): | |
| """For Gradio webcam input""" | |
| return None | |
| # Create Gradio interface | |
| with gr.Blocks(title="Shape Drawing Game") as demo: | |
| gr.Markdown("# ✏️ Shape Drawing Game") | |
| gr.Markdown("Draw shapes in the air using your index finger and see how close you get to the target shape!") | |
| # Using Webcam component for compatibility with older Gradio versions | |
| with gr.Row(): | |
| webcam = gr.Webcam(label="Webcam Feed") | |
| processed = gr.Image(label="Game View") | |
| with gr.Row(): | |
| clear_button = gr.Button("Clear Drawing") | |
| status = gr.Textbox(label="Status") | |
| # Connect components | |
| webcam.change(process_frame, inputs=webcam, outputs=processed) | |
| clear_button.click(fn=clear_path, outputs=status) | |
| gr.Markdown(""" | |
| ## How to Play: | |
| 1. Position your hand in front of the camera | |
| 2. Click the webcam button to capture frames | |
| 3. Raise your index finger to start drawing | |
| 4. Try to trace the green circle as closely as possible | |
| 5. See your score update in real-time | |
| 6. Click 'Clear Drawing' to start over | |
| """) | |
| # Requirements for Hugging Face Spaces | |
| requirements = ["opencv-python", "mediapipe", "numpy", "gradio"] | |
| # Create a README.md file for the project | |
| readme = """ | |
| # Shape Drawing Game | |
| This interactive app transforms your webcam into a real-time shape drawing game. Using your index finger, you draw shapes in the air, and the app: | |
| - Tracks your hand movements live using Mediapipe's hand tracking | |
| - Builds a trail of your fingertip's path | |
| - Compares your drawn path against a target shape (currently a circle) | |
| - Calculates a live accuracy score and gives you immediate feedback | |
| ## How to Play | |
| 1. Position your hand in front of the camera | |
| 2. Click the webcam button to capture frames | |
| 3. Raise your index finger to start drawing | |
| 4. Try to trace the green circle as closely as possible | |
| 5. See your score update in real-time | |
| 6. Click 'Clear Drawing' to start over | |
| ## Dependencies | |
| - opencv-python | |
| - mediapipe | |
| - numpy | |
| - gradio | |
| """ | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo.launch() |