Ir al contenido

Inteligibilidad objetiva (STOI y ESTOI)

Referencias: Taal et al. 2011Taal et al. 2010Jensen y Taal 2016

STOI y ESTOI son medidas de inteligibilidad objetiva basadas en correlación. Cada una compara una referencia limpia con una versión degradada o procesada de la misma habla y devuelve un escalar con relación monótona con la fracción de palabras que un oyente entendería: 1 cuando la señal degradada coincide con la limpia y cerca de 0 para ruido incorrelado. Trabajan directamente sobre las dos formas de onda, lo que las convierte en la vara de medir habitual para el habla con ponderación tiempo-frecuencia: reducción de ruido, separación de fuentes y procesado por máscara binaria, donde separar el habla limpia de su distorsión no es inmediato.

Ambas medidas ejecutan el mismo procesado antes de divergir (Taal et al. 2011, sección II): remuestreo a 10 kHz; una transformada de corto plazo de 256 muestras (25,6 ms) con ventana de Hann y solape del 50 %, rellenada con ceros hasta 512 puntos; eliminación de las tramas cuya energía limpia está más de 40 dB por debajo de la trama limpia más intensa; una agrupación por 15 bandas de tercio de octava de las magnitudes DFT desde un centro mínimo de 150 Hz; y segmentos de análisis de 384 ms (30 tramas) como unidad de comparación. La frecuencia de muestreo de las entradas es libre, la biblioteca remuestrea internamente.

from phonometry import stoi
d = stoi(clean, degraded, fs) # STOI, remuestreado a 10 kHz por dentro
print(round(d.value, 3)) # un escalar en torno a [0, 1]
print(stoi(clean, clean, fs).value) # 1.0 (una señal contra sí misma)

2. STOI: correlación de envolventes con recorte

Sección titulada «2. STOI: correlación de envolventes con recorte»

Para cada banda y segmento STOI normaliza la envolvente degradada respecto a la limpia, la recorta en un límite inferior de relación señal-distorsión ( dB) para que una unidad completamente degradada no pueda arrastrar la puntuación por debajo de su suelo, y toma la correlación muestral de ambas envolventes (Taal et al. 2011, Ecs. 3-6):

El índice es el promedio de esas correlaciones intermedias sobre todas las bandas y segmentos (Ec. 6). Como la normalización divide una ganancia por segmento, STOI es invariante al nivel de reproducción de la señal degradada, y a mayor SNR corresponde una puntuación monótonamente mayor.

import numpy as np
from phonometry import stoi
fs = 10000
rng = np.random.default_rng(1)
clean = rng.standard_normal(3 * fs)
for snr_db in (-10, 0, 10, 20):
g = 10.0 ** (-snr_db / 20.0)
noisy = clean + g * rng.standard_normal(clean.size)
print(snr_db, round(stoi(clean, noisy, fs).value, 3))

El STOIResult incluye la correlación media por banda (band_scores) y las puntuaciones por segmento (segment_scores) que promedian a value, y su .plot() dibuja la correlación intermedia por banda. Merece la pena mirar esa vista por bandas antes de citar el índice: muestra dónde muerde la degradación, algo que el número único no puede indicar.

Correlación intermedia media por banda de tercio de octava de 150 Hz a 3810 Hz para material tipo habla en un enmascarador plano con relación señal-ruido de 0 dB: la banda más grave conserva solo unos 0,27 de la correlación de envolvente y el valor sube de forma sostenida hasta unos 0,9 en las bandas consonánticas por encima de 1,9 kHz, promediando un STOI de 0,727Correlación intermedia media por banda de tercio de octava de 150 Hz a 3810 Hz para material tipo habla en un enmascarador plano con relación señal-ruido de 0 dB: la banda más grave conserva solo unos 0,27 de la correlación de envolvente y el valor sube de forma sostenida hasta unos 0,9 en las bandas consonánticas por encima de 1,9 kHz, promediando un STOI de 0,727
Mostrar el código de esta figura
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, lfilter
from phonometry import stoi
# Material tipo habla: ruido limitado en banda con envolvente silábica de
# 3,5 Hz, en un enmascarador plano con SNR de 0 dB.
fs = 10000
rng = np.random.default_rng(11)
t = np.arange(4 * fs) / fs
b, a = butter(2, [200 / (fs / 2), 4000 / (fs / 2)], btype="band")
carrier = lfilter(b, a, rng.standard_normal(t.size))
clean = carrier * (0.15 + 0.85 * np.abs(np.sin(2 * np.pi * 3.5 * t)) ** 2)
masker = rng.standard_normal(clean.size)
gain = np.sqrt(np.mean(clean ** 2)) / np.sqrt(np.mean(masker ** 2))
res = stoi(clean, clean + gain * masker, fs)
print(round(res.value, 3)) # 0.727
# En una línea: la correlación intermedia por banda que hay tras el índice.
res.plot(language="es")
plt.show()
# A mano, reproduciendo lo que dibuja STOIResult.plot():
pos = np.arange(res.band_scores.size)
fig, ax = plt.subplots()
ax.bar(pos, res.band_scores)
ax.set_xticks(pos)
ax.set_xticklabels([f"{f:.0f}" for f in res.band_frequencies], rotation=45, ha="right")
ax.set_xlabel("Banda de tercio de octava [Hz]")
ax.set_ylabel("Correlación intermedia media")
ax.set_title(f"STOI = {res.value:.3f}")
plt.show()

El enmascarador plano castiga sobre todo a las bandas graves, porque ahí el espectro del habla cae y el del enmascarador no, y la correlación se recupera en las bandas consonánticas por encima de 1 kHz. Un perfil por bandas plano, o que se hunde solo en una banda, apunta a algo distinto del ruido de banda ancha: un filtro de rechazo, una resonancia o un artefacto de procesado del realce que se está evaluando.

3. ESTOI: correlación espectral para enmascaradores modulados

Sección titulada «3. ESTOI: correlación espectral para enmascaradores modulados»

ESTOI (extended=True) sustituye la correlación por banda independiente por una conjunta espectro-temporal. Dentro de cada segmento de 384 ms normaliza en media y varianza las filas del espectrograma (las envolventes de banda) y luego sus columnas (los espectros por trama), y promedia la correlación de las columnas normalizadas (Jensen y Taal 2016, Ecs. 4-8). Hacer competir las columnas significa que un enmascarador que deja silencios, donde el habla limpia es audible por instantes, recibe crédito por el habla vislumbrada ahí, algo que el promedio por banda de STOI apenas capta.

Dos paneles del índice de inteligibilidad frente al SNR de -15 a 20 dB. Izquierda (STOI): las curvas del enmascarador estacionario y del modulado casi se superponen, de modo que STOI apenas separa los dos enmascaradores. Derecha (ESTOI): la curva del enmascarador modulado queda claramente por encima de la del estacionario en todo el rango de SNR, de modo que ESTOI acredita el habla vislumbrada en los silencios del enmascaradorDos paneles del índice de inteligibilidad frente al SNR de -15 a 20 dB. Izquierda (STOI): las curvas del enmascarador estacionario y del modulado casi se superponen, de modo que STOI apenas separa los dos enmascaradores. Derecha (ESTOI): la curva del enmascarador modulado queda claramente por encima de la del estacionario en todo el rango de SNR, de modo que ESTOI acredita el habla vislumbrada en los silencios del enmascarador
Mostrar el código de esta figura
import numpy as np
import matplotlib.pyplot as plt
from phonometry import stoi
fs = 10000
rng = np.random.default_rng(20)
t = np.arange(3 * fs) / fs
# Una señal limpia tipo habla: tonos con modulación de amplitud.
clean = np.zeros_like(t)
for f0 in (200.0, 400.0, 700.0, 1100.0, 1800.0, 2600.0):
depth = 0.5 * (1.0 + np.sin(2 * np.pi * rng.uniform(2.0, 6.0) * t + rng.uniform(0.0, 2 * np.pi)))
clean += depth * np.sin(2 * np.pi * f0 * t + rng.uniform(0.0, 2 * np.pi))
p_clean = np.sqrt(np.mean(clean ** 2))
base = rng.standard_normal(clean.size)
gate = 0.5 * (1.0 + np.sign(np.sin(2 * np.pi * 5.0 * t))) # puerta on/off a 5 Hz
modulated = base * (0.05 + 0.95 * gate)
snrs = np.arange(-15.0, 20.1, 5.0)
def curve(masker, extended):
p_m = np.sqrt(np.mean(masker ** 2))
return [stoi(clean, clean + (p_clean / (p_m * 10.0 ** (s / 20.0))) * masker,
fs, extended=extended).value for s in snrs]
fig, (a, b) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)
for ax, extended, title in ((a, False, "STOI"), (b, True, "ESTOI")):
ax.plot(snrs, curve(base, extended), "o-", label="Enmascarador estacionario")
ax.plot(snrs, curve(modulated, extended), "s--", label="Enmascarador modulado")
ax.set_title(title); ax.set_xlabel("SNR [dB]"); ax.set_ylim(0, 1); ax.legend()
a.set_ylabel("Índice de inteligibilidad")
plt.show()
STOIESTOI
Magnitud intermediaCorrelación de envolventes por banda, con recorteCorrelación espectral normalizada por filas y por columnas
Invariancia al nivelSí (normalización por segmento)Sí (normalización por fila y por columna)
Enmascaradores estacionariosBien validadaBien validada
Enmascaradores modulados, hablantes competidoresInfravalora el beneficio de los vistazosLo sigue
CosteMenorAlgo mayor

Para ruido aditivo estacionario las dos medidas son intercambiables y STOI es la opción ligera por defecto; cuando la interferencia fluctúa en el tiempo, o al comparar procesadores que remodelan el habla en tiempo y frecuencia, prefiere ESTOI.

Ambas responden a una pregunta más estrecha que las dos métricas normalizadas de habla de la biblioteca, y la elección entre las tres familias es en realidad una elección de qué se puede medir. STOI y ESTOI necesitan una referencia limpia y la señal degradada, así que pertenecen al trabajo de procesado: reducción de ruido, separación de fuentes, códecs, algoritmos de audífonos. El STI (IEC 60268-16) no necesita referencia limpia, solo el canal, y es lo que valora una sala o un sistema de megafonía. El SII (ANSI S3.5-1997) no necesita ninguna señal, solo espectros, y es lo que predice la audibilidad para un oyente con un umbral de audición dado. Cuando hay más de una disponible, responden a preguntas distintas en lugar de confirmarse entre sí: un procesador puede subir el STOI mientras la sala mantiene un STI pobre.

Cubierto. Taal et al. 2011 (STOI) y Jensen y Taal 2016 (ESTOI), las dos medidas basadas en correlación que implementa stoi(): el procesado inicial común de remuestreo a 10 kHz, tramas Hann de 256 muestras, 15 bandas de tercio de octava, segmentos de 384 ms y la eliminación de tramas silenciosas a 40 dB (Taal et al. 2011, Ecs. 1-4); la correlación de envolventes por banda y el índice promediado (Ecs. 5-6); y, con extended=True, la normalización por filas y columnas del espectrograma y el índice de correlación espectral (Jensen y Taal 2016, Ecs. 4-8).

No cubierto. Taal et al. 2011 también ajusta una función logística que convierte el índice en un porcentaje de palabras entendidas previsto, calibrada sobre corpus concretos de pruebas de escucha. stoi() devuelve solo , como STOIResult.value, no ese porcentaje: la API no tiene salida de porcentaje ni coeficientes ajustados. El artículo de ICASSP de 2010 se cita solo como la versión corta de congreso del mismo algoritmo, sin añadir nada implementado más allá de las ecuaciones de 2011/2016 anteriores.

  • Jensen, J. y Taal, C. H. (2016). An algorithm for predicting the intelligibility of speech masked by modulated noise maskers. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 24(11), 2009-2022. https://doi.org/10.1109/TASLP.2016.2585878ESTOI: la normalización en media y varianza de las filas y las columnas del espectrograma de corto plazo (Ecs. 4-7) y el índice intermedio de correlación espectral (Ec. 8).
  • Taal, C. H., Hendriks, R. C., Heusdens, R. y Jensen, J. (2010). A short-time objective intelligibility measure for time-frequency weighted noisy speech. 2010 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), 4214-4217. https://doi.org/10.1109/ICASSP.2010.5495701La versión corta de congreso de STOI.
  • Taal, C. H., Hendriks, R. C., Heusdens, R. y Jensen, J. (2011). An algorithm for intelligibility prediction of time-frequency weighted noisy speech. IEEE Transactions on Audio, Speech, and Language Processing, 19(7), 2125-2136. https://doi.org/10.1109/TASL.2011.2114881STOI: el procesado inicial común (10 kHz, tramas Hann de 256 muestras, 15 bandas de tercio de octava, segmentos de 384 ms), la normalización y el recorte por relación señal-distorsión (Ecs. 1-4), la correlación de envolventes por banda (Ec. 5) y el índice promediado (Ec. 6).