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()