Spaces:
Sleeping
Sleeping
File size: 5,595 Bytes
a32fa0e b2112e2 0a8abea a32fa0e | 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 | import gradio as gr
import matplotlib
matplotlib.use('Agg')
import cv2
import os
import tempfile
import base64
import numpy as np
import torch
import mediapipe as mp
from model import CustomLSTM, STGCNModel, CTRGCNModel, SkateFormerModel
from utils import mediapipe_detection, draw_styled_landmarks, extract_keypoints, get_adjacency_matrix, reshape_for_stgcn
# --- Setup ---
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
gloss = np.load('gloss.npy')
# Load your preferred model (Defaulting to SkateFormer based on your inference.py)
model = SkateFormerModel(num_classes=len(gloss)).to(device)
model.load_state_dict(torch.load('best_model.pth', map_location=device))
model.eval()
mp_holistic = mp.solutions.holistic
def process_video(video_path):
if video_path is None:
return None, "⚠️ Please record a video for prediction."
try:
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS) or 30
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
frames_for_display = []
sequence = []
with mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# Extract keypoints using your existing logic
image, results = mediapipe_detection(frame, holistic)
draw_styled_landmarks(image, results)
keypoints = extract_keypoints(results)
sequence.append(keypoints)
frames_for_display.append(image)
cap.release()
# Padding/Sampling logic to ensure exactly 30 frames
if len(sequence) < 30:
sequence.extend([np.zeros(258)] * (30 - len(sequence)))
else:
# Uniformly sample 30 frames
idx = np.linspace(0, len(sequence) - 1, 30).astype(int)
sequence = [sequence[i] for i in idx]
# Model Inference
input_data = np.expand_dims(sequence, axis=0)
input_processed = reshape_for_stgcn(input_data)
input_tensor = torch.tensor(input_processed, dtype=torch.float32).to(device)
with torch.no_grad():
res = model(input_tensor)
prob = torch.nn.functional.softmax(res, dim=1)
confidence, max_idx = torch.max(prob, dim=1)
label = gloss[max_idx.item()]
result_text = f"{label} ({confidence.item() * 100:.2f}%)"
# Create a temporary file to save the video
fd, output_path = tempfile.mkstemp(suffix='.mp4')
os.close(fd)
# Use MP4V for mp4 files; ensures browser compatibility
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
for frame in frames_for_display:
# Draw background rectangle for readability
cv2.rectangle(frame, (0, 0), (width, 60), (245, 117, 16), -1)
# Put prediction text on frame
cv2.putText(frame, result_text, (10, 45),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
out.write(frame)
out.release()
return output_path, "✅ Analysis Complete"
except Exception as e:
return None, f"❌ Error: {str(e)}"
def get_img_html(img_path):
"""
Reads a local image file and converts it to a Base64 HTML string.
This prevents "broken image" errors in Gradio.
"""
try:
with open(img_path, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read()).decode()
# Return the HTML img tag with the data embedded
return f'<img src="data:image/png;base64,{encoded_string}" width="40" style="display: inline-block; margin-right: 10px; vertical-align: bottom;" />'
except Exception as e:
print(f"Could not load icon: {e}")
return "" # Return empty string if file missing
def reset_state():
return None, None, "Ready"
# --- Gradio UI ---
with gr.Blocks(title="eBIM-Satu (Beta Version)") as demo:
icon_html = get_img_html("favicon.png")
gr.Markdown(f"# {icon_html} eBIM-Satu")
gr.Markdown(f"### Malaysia Isolated Sign Language Recognition")
gr.Markdown("Click 'Record' to start. The system will process the sign after 3 seconds.")
with gr.Row():
with gr.Column(scale=1):
system_status = gr.Textbox(label="System Status", value="Ready", lines=1)
gr.Markdown(
"### Instructions\n1. Open your camera.\n2. Click the record button in the video box.\n3. Perform the sign clearly.\n4. Click 'Start Prediction'.")
with gr.Column(scale=2):
input_video = gr.Video(sources=["webcam"], format="mp4", label="Sign Camera", webcam_options=gr.WebcamOptions(mirror=False))
with gr.Row():
predict_btn = gr.Button("Start Prediction / Analyze", variant="primary", scale=2)
clear_btn = gr.Button("Clear / Reset", variant="secondary", scale=1)
with gr.Column(scale=2):
output_video = gr.Video(autoplay=True, show_label=False)
predict_btn.click(
fn=process_video,
inputs=input_video,
outputs=[output_video, system_status]
)
clear_btn.click(
fn=reset_state,
inputs=None,
outputs=[input_video, output_video, system_status]
)
demo.launch(theme=gr.themes.Soft(), favicon_path="favicon.png")
|