File size: 6,923 Bytes
c43fe87
 
14e24fa
c43fe87
14e24fa
 
 
c43fe87
14e24fa
c43fe87
14e24fa
 
 
 
 
 
 
c43fe87
14e24fa
 
 
c43fe87
14e24fa
 
c43fe87
14e24fa
 
 
 
 
 
 
 
 
 
c43fe87
14e24fa
 
 
c43fe87
14e24fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c43fe87
14e24fa
 
 
 
fe74d40
 
 
 
14e24fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c43fe87
14e24fa
 
 
 
 
c43fe87
fe74d40
 
 
c43fe87
14e24fa
 
 
 
 
fe74d40
14e24fa
fe74d40
14e24fa
 
 
 
fe74d40
14e24fa
fe74d40
 
 
14e24fa
 
 
 
fe74d40
 
 
 
 
14e24fa
c43fe87
fe74d40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14e24fa
 
fe74d40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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()