JossephVR
Feat: add app and models working
a42dad9
Raw
History Blame Contribute Delete
6.99 kB
"""
app.py – Clasificador de huellas (EfficientNet / ResNet) + ASR español
"""
import gradio as gr
import numpy as np
import tensorflow as tf
import torch
import torch.nn as nn
import torchvision.transforms as T
import torchvision.models as tv_models
import librosa
import soundfile as sf
import os
# -------------------- Configuración general --------------------
IMG_SIZE = 224
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
FINGERS_EN = ["index", "little", "middle", "ring", "thumb"]
FINGERS_ES = ["índice", "meñique", "medio", "anular", "pulgar"]
FINGER_MAP_ES = dict(zip(FINGERS_EN, FINGERS_ES))
HANDS_ES = ["izquierda", "derecha"]
RESNET_CLASSES = [
"Left_index", "Left_little", "Left_middle", "Left_ring", "Left_thumb",
"Right_index", "Right_little", "Right_middle", "Right_ring", "Right_thumb",
]
TORCH_MEAN = [0.485, 0.456, 0.406]
TORCH_STD = [0.229, 0.224, 0.225]
torch_tfms = T.Compose([
T.Grayscale(num_output_channels=3),
T.Resize((IMG_SIZE, IMG_SIZE)),
T.ToTensor(),
T.Normalize(TORCH_MEAN, TORCH_STD),
])
# ---------- ASR parámetros (idénticos al entrenamiento) ----------
FRAME_LENGTH = 256
FRAME_STEP = 160
FFT_LENGTH = 384
TARGET_SR = 16_000
CHARS = [c for c in "abcdefghijklmnopqrstuvwxyzáéíóúüñ'?! "]
char_to_num = tf.keras.layers.StringLookup(vocabulary=CHARS, oov_token="")
num_to_char = tf.keras.layers.StringLookup(
vocabulary=char_to_num.get_vocabulary(), oov_token="", invert=True
)
# -------------------- Carga diferida de modelos --------------------
_models_cache = {}
def _load_efficientnet():
return tf.keras.models.load_model("models/fingerprint_model_EfficientNet.keras")
def _load_resnet():
model = tv_models.resnet18(weights=tv_models.ResNet18_Weights.IMAGENET1K_V1)
model.fc = nn.Linear(model.fc.in_features, len(RESNET_CLASSES))
model.load_state_dict(torch.load("models/resnet.pt", map_location=DEVICE))
model.eval().to(DEVICE)
return model
def CTCLoss(y_true, y_pred):
b = tf.cast(tf.shape(y_true)[0], dtype="int64")
t = tf.cast(tf.shape(y_pred)[1], dtype="int64")
l = tf.cast(tf.shape(y_true)[1], dtype="int64")
t = t * tf.ones(shape=(b, 1), dtype="int64")
l = l * tf.ones(shape=(b, 1), dtype="int64")
return tf.keras.backend.ctc_batch_cost(y_true, y_pred, t, l)
def _load_asr():
return tf.keras.models.load_model("models/audio.keras",
custom_objects={"CTCLoss": CTCLoss})
def _get_model(name):
if name not in _models_cache:
_models_cache[name] = (
_load_efficientnet() if name == "EfficientNet" else
_load_resnet() if name == "ResNet" else
_load_asr() if name == "ASR" else None
)
return _models_cache[name]
# -------------------- Clasificación de imágenes --------------------
def classify_fingerprint(image, model_name):
if image is None:
return "⚠️ Sube una imagen primero."
# --- EfficientNet ---
if model_name == "EfficientNet":
model = _get_model("EfficientNet")
img = image.convert("RGB").resize((IMG_SIZE, IMG_SIZE))
arr = tf.keras.applications.efficientnet.preprocess_input(
tf.keras.utils.img_to_array(img)
)[None, ...]
preds = model.predict(arr, verbose=0)
finger_probs = np.squeeze(preds[0] if isinstance(preds, (list, tuple)) else preds)
idx = int(finger_probs.argmax())
finger_es = FINGERS_ES[idx]
conf = finger_probs[idx]
hand_es = "N/A"
if isinstance(preds, (list, tuple)) and len(preds) > 1:
hand_es = HANDS_ES[int(np.squeeze(preds[1]).argmax())]
return f"Dedo: {finger_es}\nMano: {hand_es}\nConfianza: {conf:.2%}"
# --- ResNet ---
if model_name == "ResNet":
model = _get_model("ResNet")
tensor = torch_tfms(image).unsqueeze(0).to(DEVICE)
with torch.no_grad():
probs = torch.softmax(model(tensor), 1)[0].cpu().numpy()
idx = int(probs.argmax())
conf = probs[idx]
hand_en, finger_en = RESNET_CLASSES[idx].split('_')
return (
f"Dedo: {FINGER_MAP_ES[finger_en]}\n"
f"Mano: {HANDS_ES[0] if hand_en=='Left' else HANDS_ES[1]}\n"
f"Confianza: {conf:.2%}"
)
return "🔧 Modelo no reconocido"
# -------------------- Transcripción de audio --------------------
def _load_audio_16k(path):
audio, sr = sf.read(path)
if audio.ndim > 1:
audio = audio.mean(axis=1)
if sr != TARGET_SR:
audio = librosa.resample(audio, orig_sr=sr, target_sr=TARGET_SR)
return audio.astype("float32")
def _make_spectrogram(path):
audio = _load_audio_16k(path)
spec = np.abs(librosa.stft(audio,
n_fft=FFT_LENGTH,
hop_length=FRAME_STEP,
win_length=FRAME_LENGTH)) ** 0.5 # (freq, time)
spec = spec.T # (time, freq)
# 🔑 Normalización por FILA (freq-axis) como en entrenamiento
means = spec.mean(axis=1, keepdims=True) # (time, 1)
stds = spec.std(axis=1, keepdims=True) + 1e-10 # (time, 1)
return ((spec - means) / stds).astype("float32") # (time, freq)
def _decode_predictions(pred):
decoded, _ = tf.keras.backend.ctc_decode(
pred, input_length=np.ones(pred.shape[0]) * pred.shape[1], greedy=True
)
seq = decoded[0][0]
return tf.strings.reduce_join(num_to_char(seq)).numpy().decode("utf-8").strip()
def transcribe_audio(audio_path):
if not audio_path or not os.path.exists(audio_path):
return "⚠️ Sube o graba un audio primero."
model = _get_model("ASR")
spec = _make_spectrogram(audio_path)
pred = model.predict(spec[None, ...], verbose=0)
text = _decode_predictions(pred)
return text if text else "(vacío)"
# -------------------- Interfaz Gradio --------------------
with gr.Blocks() as demo:
gr.Markdown("# 🤖 Aplicación Multimodal con Deep Learning")
with gr.Tab("📷 Clasificación de Imágenes"):
image_input = gr.Image(type="pil", label="📤 Imagen de entrada")
image_model = gr.Dropdown(["ResNet", "EfficientNet"], value="ResNet",
label="Selecciona el modelo")
image_output = gr.Textbox(label="📈 Resultado")
gr.Button("Clasificar Imagen").click(
classify_fingerprint,
inputs=[image_input, image_model],
outputs=image_output,
)
with gr.Tab("🎙️ Reconocimiento de Voz"):
audio_input = gr.Audio(type="filepath", label="🎧 Audio de entrada")
audio_output = gr.Textbox(label="📝 Texto transcrito")
gr.Button("Transcribir Audio").click(
transcribe_audio,
inputs=audio_input,
outputs=audio_output,
)
demo.launch()