Posterior a de una prolongada búsqueda de información pudimos resolver este dilema que presentan algunos de nuestros lectores. Te brindamos la solución y esperamos que te resulte de mucha ayuda.
Solución:
Primero, observe que su código traza hasta 100 espectrogramas (si processBlock
se llama varias veces) una encima de la otra y solo se ve la última. Es posible que desee arreglar eso. Además, supongo que sabe por qué quiere trabajar con grabaciones de audio de 30 ms. Personalmente, no puedo pensar en una aplicación práctica en la que 30 ms grabados con un micrófono de computadora portátil puedan brindar información interesante. Depende de lo que esté grabando y cómo active la grabación, pero este problema es tangencial a la pregunta real.
De lo contrario, el código funciona perfectamente. Con solo unos pequeños cambios en el processBlock
función, aplicando algunos conocimientos previos, puede obtener espectrogramas informativos y estéticos.
Así que hablemos de espectrogramas reales. Tomaré la salida de SoX como referencia. La anotación de la barra de colores dice que es dBFS
1, que es una medida logarítmica (dB es la abreviatura de Decibel). Entonces, primero convierta el espectrograma a dB:
f, t, Sxx = signal.spectrogram(snd_block, RATE)
dBS = 10 * np.log10(Sxx) # convert to dB
plt.pcolormesh(t, f, dBS)
Esto mejoró la escala de colores. Ahora vemos ruido en las bandas de frecuencia más alta que estaba oculto antes. A continuación, abordemos la resolución del tiempo. El espectrograma divide la señal en segmentos (la longitud predeterminada es 256) y calcula el espectro para cada uno. Esto significa que tenemos una resolución de frecuencia excelente pero una resolución de tiempo muy pobre porque solo unos pocos segmentos de este tipo encajan en la ventana de señal (que tiene una longitud de aproximadamente 1300 muestras). Siempre existe una compensación entre la resolución de tiempo y frecuencia. Esto está relacionado con el principio de incertidumbre. Así que cambiemos algo de resolución de frecuencia por resolución de tiempo dividiendo la señal en segmentos más cortos:
f, t, Sxx = signal.spectrogram(snd_block, RATE, nperseg=64)
¡Excelente! Ahora tenemos una resolución relativamente equilibrada en ambos ejes, ¡pero espere! ¡¿Por qué el resultado está tan pixelado ?! En realidad, esta es toda la información que hay en la breve ventana de tiempo de 30 ms. Hay tantas formas en que se pueden distribuir 1300 muestras en dos dimensiones. Sin embargo, podemos hacer un poco de trampa y usar una resolución FFT más alta y segmentos superpuestos. Esto hace que el resultado sea más suave aunque no proporciona información adicional:
f, t, Sxx = signal.spectrogram(snd_block, RATE, nperseg=64, nfft=256, noverlap=60)
Contempla patrones de interferencia espectrales bonitos. (Estos patrones dependen de la función de ventana utilizada, pero no nos detengamos en los detalles aquí. Consulte la window
argumento de la función espectrograma para jugar con estos.) El resultado se ve bien, pero en realidad no contiene más información que la imagen anterior.
Para hacer que el resultado sea más SoX-lixe, observe que el espectrograma SoX está más bien manchado en el eje del tiempo. Obtiene este efecto usando la resolución de tiempo baja original (segmentos largos) pero déjelos superponerse para suavidad:
f, t, Sxx = signal.spectrogram(snd_block, RATE, noverlap=250)
Personalmente prefiero la tercera solución, pero necesitará encontrar su propia compensación de tiempo / frecuencia preferida.
Finalmente, usemos un mapa de colores que se parezca más al de SoX:
plt.pcolormesh(t, f, dBS, cmap='inferno')
Un breve comentario sobre la siguiente línea:
THRESHOLD = 40 # dB
El umbral se compara con el valor eficaz de la señal de entrada, que es no medido en dB pero unidades de amplitud brutas.
1 Aparentemente, FS es la abreviatura de escala completa. dBFS significa que la medida de dB es relativa al rango máximo. 0 dB es la señal más fuerte posible en la representación actual, por lo que los valores reales deben ser <= 0 dB.
ACTUALIZAR para aclarar mi respuesta y, con suerte, complementar la excelente explicación de @kazemakase, encontré tres cosas que espero ayuden:
-
Utilice LogNorm:
plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=Sxx.min(), vmax=Sxx.max()))
-
usar el método fromstring de numpy
Resulta que el cálculo RMS no funcionará con este método ya que los datos tienen un tipo de datos de longitud restringida y los desbordamientos se vuelven negativos: es decir, 507 * 507 = -5095.
-
use la barra de color () ya que todo se vuelve más fácil cuando puede ver la escala
plt.colorbar()
Respuesta original:
Obtuve un resultado decente al reproducir una frecuencia de 10 kHz en su código con solo un par de alteraciones:
-
importar el LogNorm
from matplotlib.colors import LogNorm
-
Utilice LogNorm en la malla
plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=Sxx.min(), vmax=Sxx.max()))
Esto me dio:
Es posible que también deba llamar a plt.close () después de savefig, y creo que la lectura de la transmisión necesita algo de trabajo, ya que las imágenes posteriores dejaron caer el primer cuarto del sonido.
También recomendaría plt.colorbar()
para que puedas ver la escala que termina usando
ACTUALIZACIÓN: viendo que alguien se tomó el tiempo de votar en contra
Aquí está mi código para una versión funcional del espectrograma. Captura cinco segundos de audio y los escribe en un archivo de especificaciones y un archivo de audio para que pueda comparar. Todavía hay mucho que mejorar y está poco optimizado: estoy seguro de que se están cayendo trozos debido al tiempo para escribir archivos de audio y especificaciones. Un mejor enfoque sería usar la devolución de llamada sin bloqueo y podría hacerlo más tarde
La principal diferencia con el código original fue el cambio para obtener los datos en el formato correcto para numpy:
np.fromstring(raw_block,dtype=np.int16)
en lugar de
struct.unpack(format, raw_block)
Esto se volvió obvio como un problema importante tan pronto como intenté escribir el audio en un archivo usando:
scipy.io.wavfile.write('data/audio.wav'.format(self.plot_counter),RATE,snd_block)
Aquí hay una buena música, la batería es obvia:
El código:
import pyaudio
import struct
import math
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
import time
from scipy.io.wavfile import write
THRESHOLD = 0 # dB
RATE = 44100
INPUT_BLOCK_TIME = 1 # 30 ms
INPUT_FRAMES_PER_BLOCK = int(RATE * INPUT_BLOCK_TIME)
INPUT_FRAMES_PER_BLOCK_BUFFER = int(RATE * INPUT_BLOCK_TIME)
def get_rms(block):
return np.sqrt(np.mean(np.square(block)))
class AudioHandler(object):
def __init__(self):
self.pa = pyaudio.PyAudio()
self.stream = self.open_mic_stream()
self.threshold = THRESHOLD
self.plot_counter = 0
def stop(self):
self.stream.close()
def find_input_device(self):
device_index = None
for i in range( self.pa.get_device_count() ):
devinfo = self.pa.get_device_info_by_index(i)
print('Device %: %'.format(i, devinfo['name']))
for keyword in ['mic','input']:
if keyword in devinfo['name'].lower():
print('Found an input: device - '.format(i, devinfo['name']))
device_index = i
return device_index
if device_index == None:
print('No preferred input found; using default input device.')
return device_index
def open_mic_stream( self ):
device_index = self.find_input_device()
stream = self.pa.open( format = self.pa.get_format_from_width(2,False),
channels = 1,
rate = RATE,
input = True,
input_device_index = device_index)
stream.start_stream()
return stream
def processBlock(self, snd_block):
f, t, Sxx = signal.spectrogram(snd_block, RATE)
zmin = Sxx.min()
zmax = Sxx.max()
plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=zmin, vmax=zmax))
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
plt.axis([t.min(), t.max(), f.min(), f.max()])
plt.colorbar()
plt.savefig('data/spec.png'.format(self.plot_counter), bbox_inches='tight')
plt.close()
write('data/audio.wav'.format(self.plot_counter),RATE,snd_block)
self.plot_counter += 1
def listen(self):
try:
print "start", self.stream.is_active(), self.stream.is_stopped()
#raw_block = self.stream.read(INPUT_FRAMES_PER_BLOCK, exception_on_overflow = False)
total = 0
t_snd_block = []
while total < INPUT_FRAMES_PER_BLOCK:
while self.stream.get_read_available() <= 0:
print 'waiting'
time.sleep(0.01)
while self.stream.get_read_available() > 0 and total < INPUT_FRAMES_PER_BLOCK:
raw_block = self.stream.read(self.stream.get_read_available(), exception_on_overflow = False)
count = len(raw_block) / 2
total = total + count
print "done", total,count
format = '%dh' % (count)
t_snd_block.append(np.fromstring(raw_block,dtype=np.int16))
snd_block = np.hstack(t_snd_block)
except Exception as e:
print('Error recording: '.format(e))
return
self.processBlock(snd_block)
if __name__ == '__main__':
audio = AudioHandler()
for i in range(0,5):
audio.listen()
Creo que el problema es que estás intentando hacer el espectrograma de un bloque de audio de 30 ms, que es tan corto que puedes considerar la señal como estática.
El espectrograma es de hecho el STFT, y puede encontrarlo también en la documentación de Scipy:
scipy.signal.spectrogram(x, fs = 1.0, window = ('tukey', 0.25), nperseg = None, noverlap = None, nfft = None, detrend = 'constante', return_onesided = True, escala = 'densidad', eje = -1, modo = 'psd')
Calcule un espectrograma con transformadas de Fourier consecutivas.
Los espectrogramas se pueden utilizar como una forma de visualizar el cambio del contenido de frecuencia de una señal no estacionaria a lo largo del tiempo.
En la primera figura, tiene cuatro cortes que son el resultado de cuatro fft consecutivos en su bloque de señal, con algunas ventanas y superposición. La segunda figura tiene un corte único, pero depende de los parámetros del espectrograma que haya utilizado.
La cuestión es qué quieres hacer con esa señal. ¿Cuál es el propósito del algoritmo?
Reseñas y calificaciones
Si para ti ha resultado provechoso nuestro artículo, nos gustaría que lo compartas con más desarrolladores de esta manera nos ayudas a dar difusión a nuestro contenido.