Spaces:
Sleeping
Sleeping
File size: 6,573 Bytes
dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 dc79c99 c072565 236535c c072565 dc79c99 c072565 ac04435 236535c c072565 dc79c99 c072565 dc79c99 c072565 236535c c072565 236535c ac04435 c072565 75ada83 c072565 236535c c072565 236535c c072565 ac04435 c072565 ac04435 c072565 236535c 75ada83 ea2e3bd c072565 236535c c072565 236535c c072565 ac04435 236535c c072565 236535c c072565 ac04435 c072565 ac04435 ea2e3bd c072565 ac04435 c072565 ea2e3bd c072565 ac04435 c072565 ea2e3bd c072565 236535c c072565 236535c c072565 75ada83 236535c c072565 75ada83 c072565 ea2e3bd 75ada83 236535c dc79c99 | 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 | import cv2
import mediapipe as mp
import numpy as np
import torch
import torch.nn as nn
import gradio as gr
# ----------------------------
# Labels
# ----------------------------
GESTURE_LABELS = {
0: "A", 1: "B", 2: "L", 3: "U", 4: "V", 5: "W",
6: "Z", 7: "F", 8: "five", 9: "one", 10: "three",
11: "two", 12: "six", 13: "seven", 14: "eight",
15: "nine", 16: "ten", 17: "E", 18: "four",
19: "i", 20: "k", 21: "r", 22: "zero",
23: "m", 24: "s"
}
CONF_THRESHOLD = 0.6
BLOCK_SIZE = 10 # frames per block
# ----------------------------
# Model
# ----------------------------
class GestureNet(nn.Module):
def __init__(self, input_size=126, num_classes=len(GESTURE_LABELS)):
super().__init__()
self.fc1 = nn.Linear(input_size, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, num_classes)
self.relu = nn.ReLU()
self.dropout = nn.Dropout(0.3)
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.relu(self.fc2(x))
x = self.dropout(x)
return self.fc3(x)
model = GestureNet()
model.load_state_dict(torch.load("gesture_model1.pth", map_location="cpu"))
model.eval()
# ----------------------------
# MediaPipe
# ----------------------------
mp_hands = mp.solutions.hands
# ----------------------------
# Landmark extraction + prediction
# ----------------------------
def extract_coords(results):
coords = []
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
hand_coords = np.array([[lm.x, lm.y, lm.z] for lm in hand_landmarks.landmark])
hand_coords -= hand_coords[0]
max_val = np.max(np.linalg.norm(hand_coords, axis=1))
if max_val > 0:
hand_coords /= max_val
coords.extend(hand_coords.flatten())
return coords
def run_model(coords):
"""Return label string (either gesture or 'Unknown')"""
if len(coords) < 126:
coords = coords + [0.0] * (126 - len(coords))
elif len(coords) > 126:
coords = coords[:126]
input_tensor = torch.tensor(coords, dtype=torch.float32).unsqueeze(0)
with torch.no_grad():
outputs = model(input_tensor)
probs = torch.softmax(outputs, dim=1)
pred_class = torch.argmax(probs, dim=1).item()
confidence = probs[0][pred_class].item()
if confidence >= CONF_THRESHOLD:
return GESTURE_LABELS[pred_class]
return "Unknown"
# ----------------------------
# Image prediction (unchanged format)
# ----------------------------
def predict_image_from_rgb(image_rgb):
with mp_hands.Hands(static_image_mode=True, max_num_hands=2) as hands:
results = hands.process(image_rgb)
coords = extract_coords(results)
if not coords:
return "No hand detected"
label = run_model(coords)
# run_model returns label or "Unknown" – we can keep the simple label
return label
# ----------------------------
# 🆕 Video prediction → build word from 10-frame blocks
# ----------------------------
def predict_video_word(video_path):
"""
Divides video into non-overlapping blocks of BLOCK_SIZE frames.
If every frame in a block yields the *same* valid gesture,
that gesture is added to the output word.
Returns the concatenated word, or 'Unknown' if no block is valid.
"""
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return "Cannot open video file"
frame_labels = [] # list of strings (gesture, "Unknown", or "No hand detected")
with mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
) as hands:
while True:
ret, frame = cap.read()
if not ret:
break
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(frame_rgb)
coords = extract_coords(results)
if coords:
label = run_model(coords) # gesture name or "Unknown"
frame_labels.append(label)
else:
frame_labels.append("No hand detected")
cap.release()
# Build word from non-overlapping blocks
word = []
for i in range(0, len(frame_labels) - BLOCK_SIZE + 1, BLOCK_SIZE):
block = frame_labels[i : i + BLOCK_SIZE]
# Check: all labels identical AND the label is not invalid
if (len(set(block)) == 1 and
block[0] not in ("Unknown", "No hand detected")):
word.append(block[0])
if word:
return "".join(word)
return "Unknown"
# ----------------------------
# Universal predictor
# ----------------------------
def predict_file(file_path, is_video=False):
if file_path is None:
return "No file uploaded"
# Direct numpy array (image)
if isinstance(file_path, np.ndarray):
if len(file_path.shape) == 3 and file_path.shape[2] == 3:
image_rgb = cv2.cvtColor(file_path, cv2.COLOR_BGR2RGB)
return predict_image_from_rgb(image_rgb)
else:
return "Invalid image format"
file_ext = str(file_path).lower()
is_video_file = is_video or file_ext.endswith(('.mp4', '.avi', '.mov', '.mkv', '.webm'))
if is_video_file:
return predict_video_word(file_path) # returns the word
else:
image = cv2.imread(file_path)
if image is None:
return "Could not read image file"
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return predict_image_from_rgb(image_rgb)
# ----------------------------
# Gradio UI
# ----------------------------
with gr.Blocks(title="Hand Gesture Recognition") as app:
gr.Markdown("# ✋ Hand Gesture Recognition (Word‑Builder)")
gr.Markdown(
"Upload an image or video. For videos, non‑overlapping blocks of **10 frames** "
"are checked. If all 10 frames in a block show the same gesture, that letter is "
"added to the output word. Blocks with mixed or invalid gestures are ignored."
)
with gr.Row():
input_file = gr.File(
label="Upload Image or Video",
file_types=["image", "video"],
type="filepath"
)
output_text = gr.Textbox(label="Prediction", lines=3)
predict_btn = gr.Button("Predict", variant="primary")
predict_btn.click(
predict_file,
inputs=input_file,
outputs=output_text
)
app.launch() |