audio-denoise / dnsmos.py
Azuredraco's picture
Despliegue desde GitHub
fc527e7 verified
Raw History Blame Contribute Delete
3.16 kB
"""
dnsmos.py — Calidad percibida de la voz con DNSMOS P.835 (Microsoft).
Estima en una escala 1–5 lo que puntuarían oyentes humanos:
SIG = calidad de la voz (¿suena natural, sin distorsión?)
BAK = fondo (¿se oye ruido?)
OVRL = valoración global
Trabaja a 16 kHz: evalúa la voz, no los agudos extremos.
Para que sea rápido en CPUs modestas se evalúan como mucho MAX_WINDOWS ventanas de 9 s
repartidas por el audio (la media se estabiliza con pocas).
Basado en DNSMOS/dnsmos_local.py del repositorio microsoft/DNS-Challenge.
"""
from __future__ import annotations
import os
import threading
import numpy as np
from models import dnsmos_paths
SR = 16000
WIN_SEC = 9.01
MAX_WINDOWS = 10
_lock = threading.Lock()
_sessions = None
def _load():
global _sessions
with _lock:
if _sessions is None:
import onnxruntime as ort
paths = dnsmos_paths()
opts = ort.SessionOptions()
opts.log_severity_level = 3
# En contenedores, onnxruntime puede lanzar más hilos que núcleos reales y ralentizarse.
opts.intra_op_num_threads = max(1, min(4, os.cpu_count() or 1))
opts.inter_op_num_threads = 1
_sessions = ort.InferenceSession(str(paths["sig_bak_ovr.onnx"]), opts, providers=["CPUExecutionProvider"])
return _sessions
def available() -> bool:
try:
_load()
return True
except Exception as e:
print(f"[DNSMOS] No disponible: {e}")
return False
def _polyfit(sig, bak, ovr):
p_ovr = np.poly1d([-0.06766283, 1.11546468, 0.04602535])
p_sig = np.poly1d([-0.08397278, 1.22083953, 0.0052439])
p_bak = np.poly1d([-0.13166888, 1.60915514, -0.39604546])
return p_sig(sig), p_bak(bak), p_ovr(ovr)
def score(mono: np.ndarray, sr: int) -> dict[str, float] | None:
"""Devuelve {'SIG','BAK','OVRL'} o None si el modelo no está disponible."""
try:
sess = _load()
except Exception as e:
print(f"[DNSMOS] No disponible: {e}")
return None
import soxr
audio = soxr.resample(np.asarray(mono, dtype=np.float32), sr, SR, quality="HQ") if sr != SR else mono
audio = np.asarray(audio, dtype=np.float32)
if not np.any(audio):
return None
win = int(WIN_SEC * SR)
while len(audio) < win:
audio = np.concatenate([audio, audio])
n_hops = int(np.floor(len(audio) / SR) - WIN_SEC) + 1
starts = np.arange(n_hops) * SR
if len(starts) > MAX_WINDOWS:
starts = starts[np.linspace(0, len(starts) - 1, MAX_WINDOWS).round().astype(int)]
rows = []
for s in starts:
seg = audio[int(s) : int(s) + win]
if len(seg) < win:
continue
sig, bak, ovr = sess.run(None, {"input_1": seg[None, :]})[0][0]
rows.append(_polyfit(sig, bak, ovr))
if not rows:
return None
m = np.mean(rows, axis=0)
return {"SIG": float(m[0]), "BAK": float(m[1]), "OVRL": float(m[2])}
def label(ovrl: float) -> str:
if ovrl >= 3.6:
return "excelente"
if ovrl >= 3.2:
return "buena"
if ovrl >= 2.7:
return "aceptable"
return "mejorable"