koulsahil's picture
Update app.py
fe74d40 verified
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()