File size: 6,884 Bytes
bc971c7 | 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 | import gradio as gr
import cv2
import numpy as np
import tempfile
import json
from modules import mediapipe_generator
from modules.mediapipe_generator import extract_to_array
from modules.sentence_guesser import SentenceGuesser
guesser = SentenceGuesser(model="fold1")
EXTRACTION_PATH = "metadata/extraction_order.json"
SIGN_DICT_PATH = "metadata/sign_dict.json"
with open(EXTRACTION_PATH, 'r') as f:
extraction_order = json.load(f)
with open(SIGN_DICT_PATH, 'r') as f:
sign_dict = {int(k): v for k, v in json.load(f).items()}
def retry_tts(cached_words, cached_emotions):
if not cached_words or not cached_emotions:
gr.Warning("No translation available to synthesize yet. Please analyze a sequence first.")
return None
print("🔄 Retrying TTS Generation...")
#return generate_speech(cached_words, cached_emotions)
return None
def get_sign_label(idx):
if idx == 80:
return ""
else:
return sign_dict.get(idx, f"ID_{idx}")
def render_normalized_component(landmarks, canvas_w, canvas_h, padding=0.1, color=(0, 255, 0)):
canvas = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)
if landmarks.shape[0] == 0 or np.isnan(landmarks).any():
return canvas
min_x, max_x = np.min(landmarks[:, 0]), np.max(landmarks[:, 0])
min_y, max_y = np.min(landmarks[:, 1]), np.max(landmarks[:, 1])
data_w = max_x - min_x
data_h = max_y - min_y
if data_w == 0 or data_h == 0:
return canvas
usable_w = canvas_w * (1 - padding * 2)
usable_h = canvas_h * (1 - padding * 2)
scale = min(usable_w / data_w, usable_h / data_h)
cx, cy = (min_x + max_x) / 2, (min_y + max_y) / 2
canvas_cx, canvas_cy = canvas_w / 2, canvas_h / 2
for x, y in landmarks:
pix_x = int((x - cx) * scale + canvas_cx)
pix_y = int((y - cy) * scale + canvas_cy)
cv2.circle(canvas, (pix_x, pix_y), 2, color, -1)
return canvas
def render_diagnostic_matrix(orig_frame, raw_landmarks, norm_pose, norm_lhand, norm_rhand, norm_face, current_label):
cell_w, cell_h = 320, 240
raw_rgb = cv2.resize(orig_frame, (cell_w, cell_h))
raw_overlaid = raw_rgb.copy()
for lm in raw_landmarks:
x_pix, y_pix = int(lm[0] * cell_w), int(lm[1] * cell_h)
cv2.circle(raw_overlaid, (x_pix, y_pix), 2, (0, 0, 255), -1)
canvas_pose = render_normalized_component(norm_pose, cell_w, cell_h, color=(255, 255, 0))
canvas_face = render_normalized_component(norm_face, cell_w, cell_h, color=(0, 255, 255))
canvas_lhand = render_normalized_component(norm_lhand, cell_w, cell_h, color=(255, 0, 255))
canvas_rhand = render_normalized_component(norm_rhand, cell_w, cell_h, color=(0, 255, 0))
def add_title(img, text):
cv2.putText(img, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1)
return img
add_title(raw_rgb, "Raw RGB")
cv2.putText(raw_rgb, f"PRED: {current_label}", (10, cell_h - 15), cv2.FONT_HERSHEY_DUPLEX, 0.7, (0, 255, 0), 2)
add_title(raw_overlaid, "Raw + Landmarks")
add_title(canvas_pose, "Norm: Pose")
add_title(canvas_lhand, "Norm: L-Hand")
add_title(canvas_rhand, "Norm: R-Hand")
add_title(canvas_face, "Norm: Face")
row1 = np.hstack((raw_rgb, raw_overlaid))
row2 = np.hstack((canvas_pose, canvas_face))
row3 = np.hstack((canvas_lhand, canvas_rhand))
return np.vstack((row1, row2, row3))
def render_diagnostic_video(original_path, raw_data, norm_data, frame_indices, fps=30):
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
output_path = temp_file.name
w, h = 640, 720
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, fps, (w, h))
cap = cv2.VideoCapture(original_path)
for t in range(raw_data.shape[0]):
ret, orig_frame = cap.read()
if not ret:
orig_frame = np.zeros((480, 640, 3), dtype=np.uint8)
norm_pose = norm_data[t, :7, :]
norm_lhand = norm_data[t, 7:28, :]
norm_rhand = norm_data[t, 28:49, :]
norm_face = norm_data[t, 49:, :]
current_label = get_sign_label(int(frame_indices[t]))
matrix_frame = render_diagnostic_matrix(
orig_frame,
raw_data[t],
norm_pose,
norm_lhand,
norm_rhand,
norm_face,
current_label
)
out.write(matrix_frame)
cap.release()
out.release()
return output_path
def predict_sign(video_path):
mediapipe_results = mediapipe_generator.generate_mediapipe_gradio(video_path)
pose_seq = np.array([extract_to_array(r.pose_landmarks, 33, 4) for r in mediapipe_results])
face_seq = np.array([extract_to_array(r.face_landmarks, 468, 3) for r in mediapipe_results])
lh_seq = np.array([extract_to_array(r.left_hand_landmarks, 21, 3) for r in mediapipe_results])
rh_seq = np.array([extract_to_array(r.right_hand_landmarks, 21, 3) for r in mediapipe_results])
result = guesser.predict(pose_seq, face_seq, lh_seq, rh_seq)
words = result['prediction_string'].split()
emotions = result['prediction_emotions']
mapped_output = "\n".join([f"{w} -> [{e}]" for w, e in zip(words, emotions)])
display_text = (
f"Raw Window IDs: {result['raw_ids']}\n\n"
f"Translation: {result['prediction_string']}\n\n"
f"Emotion Mapping:\n{mapped_output}"
)
viz_path = render_diagnostic_video(
video_path,
result['raw_data'],
result['norm_data'],
result['frame_indices']
)
# audio_path = generate_speech(words, emotions)
audio_path = None
return display_text, audio_path, viz_path, words, emotions
with gr.Blocks() as demo:
current_words = gr.State([])
current_emotions = gr.State([])
gr.Markdown("# Filipino Sign Language Recognition")
with gr.Row():
with gr.Column():
video_input = gr.Video(label="Input: Upload or Record")
submit_btn = gr.Button("Analyze Sequence", variant="primary")
with gr.Column():
output_text = gr.Textbox(label="Model Predictions")
viz_output = gr.Video(label="Detected and Normalized Landmarks")
with gr.Row():
# audio_output = gr.Audio(label="Synthesized Speech", autoplay=True, scale=3)
retry_audio_btn = gr.Button("🔄 Retry Audio", size="sm", scale=1)
submit_btn.click(
fn=predict_sign,
inputs=video_input,
outputs=[output_text, audio_output, viz_output, current_words, current_emotions]
)
retry_audio_btn.click(
fn=retry_tts,
inputs=[current_words, current_emotions],
outputs=[audio_output]
)
demo.launch()
|