commit 580e6c67551aaf7e71c723a4fae83a54e15ef6c6 parent 91a40618b27f6e6eac8f71e0bfd632ad293851e0 Author: Martin Kloeckner <mjkloeckner@gmail.com> Date: Fri, 28 Nov 2025 17:09:18 -0300 updated `tp/*` Diffstat:
39 files changed, 245 insertions(+), 67 deletions(-) diff --git a/tp/data/canciones_prueba/000002_wpp.mp3 b/tp/data/canciones_prueba/000002_wpp.mp3 Binary files differ. diff --git a/tp/img/diagrama_de_bloques_general.png b/tp/img/diagrama_de_bloques_general.png Binary files differ. diff --git a/tp/main.pdf b/tp/main.pdf Binary files differ. diff --git a/tp/plot/a4_clarinete.png b/tp/plot/a4_clarinete.png Binary files differ. diff --git a/tp/plot/a4_clarinete_comparison.png b/tp/plot/a4_clarinete_comparison.png Binary files differ. diff --git a/tp/plot/a4_clarinete_cutoff_3000Hz.png b/tp/plot/a4_clarinete_cutoff_3000Hz.png Binary files differ. diff --git a/tp/plot/a4_clarinete_cutoff_3000Hz_fft.png b/tp/plot/a4_clarinete_cutoff_3000Hz_fft.png Binary files differ. diff --git a/tp/plot/a4_clarinete_cutoff_time_comparison.png b/tp/plot/a4_clarinete_cutoff_time_comparison.png Binary files differ. diff --git a/tp/plot/a4_flauta.png b/tp/plot/a4_flauta.png Binary files differ. diff --git a/tp/plot/a4_flauta_comparison.png b/tp/plot/a4_flauta_comparison.png Binary files differ. diff --git a/tp/plot/a4_flauta_cutoff_1000Hz.png b/tp/plot/a4_flauta_cutoff_1000Hz.png Binary files differ. diff --git a/tp/plot/a4_flauta_cutoff_1000Hz_fft.png b/tp/plot/a4_flauta_cutoff_1000Hz_fft.png Binary files differ. diff --git a/tp/plot/a4_flauta_cutoff_time_comparison.png b/tp/plot/a4_flauta_cutoff_time_comparison.png Binary files differ. diff --git a/tp/plot/a4_violin.png b/tp/plot/a4_violin.png Binary files differ. diff --git a/tp/plot/a4_violin_comparison.png b/tp/plot/a4_violin_comparison.png Binary files differ. diff --git a/tp/plot/a4_violin_cutoff_4000Hz.png b/tp/plot/a4_violin_cutoff_4000Hz.png Binary files differ. diff --git a/tp/plot/a4_violin_cutoff_4000Hz_fft.png b/tp/plot/a4_violin_cutoff_4000Hz_fft.png Binary files differ. diff --git a/tp/plot/a4_violin_cutoff_time_comparison.png b/tp/plot/a4_violin_cutoff_time_comparison.png Binary files differ. diff --git a/tp/plot/cancion1.png b/tp/plot/cancion1.png Binary files differ. diff --git a/tp/plot/cancion1_0_248s_a_0_256s.png b/tp/plot/cancion1_0_248s_a_0_256s.png Binary files differ. diff --git a/tp/plot/cancion1_0_520s_a_0_528s.png b/tp/plot/cancion1_0_520s_a_0_528s.png Binary files differ. diff --git a/tp/plot/cancion1_filter1_output_compare.png b/tp/plot/cancion1_filter1_output_compare.png Binary files differ. diff --git a/tp/plot/cancion1_filter1_output_compare_0_248_a_0_256.png b/tp/plot/cancion1_filter1_output_compare_0_248_a_0_256.png Binary files differ. diff --git a/tp/plot/cancion1_filter2_output_compare.png b/tp/plot/cancion1_filter2_output_compare.png Binary files differ. diff --git a/tp/plot/cancion1_filter2_output_compare_0_248_a_0_256.png b/tp/plot/cancion1_filter2_output_compare_0_248_a_0_256.png Binary files differ. diff --git a/tp/plot/cancion2.png b/tp/plot/cancion2.png Binary files differ. diff --git a/tp/plot/cancion2_14_72s_a_14_73s.png b/tp/plot/cancion2_14_72s_a_14_73s.png Binary files differ. diff --git a/tp/plot/cancion2_26_57s_a_26_58s.png b/tp/plot/cancion2_26_57s_a_26_58s.png Binary files differ. diff --git a/tp/plot/cancion2_6s_filter1_output_compare.png b/tp/plot/cancion2_6s_filter1_output_compare.png Binary files differ. diff --git a/tp/plot/cancion2_6s_filter1_output_compare_26_57_a_26_58.png b/tp/plot/cancion2_6s_filter1_output_compare_26_57_a_26_58.png Binary files differ. diff --git a/tp/plot/cancion2_6s_filter2_output_compare.png b/tp/plot/cancion2_6s_filter2_output_compare.png Binary files differ. diff --git a/tp/plot/cancion2_6s_filter2_output_compare_26_57_a_26_58.png b/tp/plot/cancion2_6s_filter2_output_compare_26_57_a_26_58.png Binary files differ. diff --git a/tp/primera_parte.tex b/tp/primera_parte.tex @@ -289,10 +289,10 @@ también tienen armónicos impares como la onda cuadrada, pero estos armónicos tienen mayor amplitud, es por esto que si bien tienen un sonido similar, el sonido del violín es mas agudo. -%\begin{figure}[H] -%\centering -%\includegraphics{plot/a4_violin.png} -%\caption{Sonido de violín} -%\label{a4_violin} -%\end{figure} +\begin{figure}[H] +\centering +\includegraphics{plot/a4_violin.png} +\caption{Sonido de violín} +\label{a4_violin} +\end{figure} diff --git a/tp/scripts/data.py b/tp/scripts/data.py @@ -39,21 +39,48 @@ canciones_dataset_dir = data_dir + 'canciones/' canciones_dataset = [] canciones_dataset_common_fs = 44100 -i = 0 -for i, file_name in enumerate(sorted(os.listdir(canciones_dataset_dir))): - # if i > 3: - # break - basename, ext = os.path.splitext(file_name) - - file_path = canciones_dataset_dir + file_name - print("[LOG] Cargando '" + file_path + "'") - - file_data, file_fs = librosa.load(file_path, sr=None, mono=True) - file_fs = int(file_fs) - canciones_dataset.append(SimpleNamespace( - name=basename, - path=file_path, - data=file_data, - fs=file_fs)) - -print(f'[LOG] Se cargaron {i} archivos') +def cargar_canciones_dataset(count=0): + for i, file_name in enumerate(sorted(os.listdir(canciones_dataset_dir))): + if count != 0: + if i == count: + break + + basename, ext = os.path.splitext(file_name) + + file_path = canciones_dataset_dir + file_name + print("[LOG] Cargando '" + file_path + "'") + + file_data, file_fs = librosa.load(file_path, sr=None, mono=True) + file_fs = int(file_fs) + canciones_dataset.append(SimpleNamespace( + name=basename, + path=file_path, + data=file_data, + fs=file_fs)) + + print(f'[LOG] Se cargaron {i+1} archivos') + +canciones_prueba_dir = data_dir + 'canciones_prueba/' +canciones_prueba = [] + +def cargar_canciones_prueba(count=0): + for i, file_name in enumerate(sorted(os.listdir(canciones_prueba_dir))): + if count != 0: + if i == count: + break + + basename, ext = os.path.splitext(file_name) + + file_path = canciones_prueba_dir + file_name + print("[LOG] Cargando '" + file_path + "'") + + file_data, file_fs = librosa.load(file_path, sr=None, mono=True) + file_fs = int(file_fs) + canciones_prueba.append(SimpleNamespace( + name=basename, + path=file_path, + data=file_data, + fs=file_fs)) + + print(f'[LOG] Se cargaron {i+1} archivos') + diff --git a/tp/scripts/main.py b/tp/scripts/main.py @@ -16,7 +16,6 @@ def primera_parte(): time_domain_music_instruments() def segunda_parte(): - freq_domain freq_domain_cancion1() freq_domain_cancion2() freq_domain_spectograms() @@ -35,15 +34,15 @@ def segunda_parte(): def tercera_parte(): # h, fs = filtro_fir_deducido() - # filtro_fir_analisis(h, fs) - # analisis_freq_ventanas() - # comparacion_de_espectrogramas_filtrado_vs_original(h) + DB = generar_base_de_datos_si_no_existe() + # evaluar_aciertos(DB) + evaluar_cancion(DB, canciones_dataset[0]) - DB = generar_base_de_datos() - evaluar_aciertos(DB) +cargar_canciones_dataset() +cargar_canciones_prueba() # primera_parte() # segunda_parte() diff --git a/tp/scripts/segunda_parte.py b/tp/scripts/segunda_parte.py @@ -27,11 +27,11 @@ def freq_domain_spectograms(): for i in [512, 1024, 2048]: for window in ['boxcar', 'bartlett', 'hann', 'hamming']: spectogram_plot(file1_fs, file1_data, - f"cancion1_espectograma_{window}_{i:04d}", N=i, + f"cancion1_espectrograma_{window}_{i:04d}", N=i, win=window, ylim=[0, 17500]) spectogram_plot(file2_fs, file2_data, - f"cancion2_espectograma_{window}_{i:04d}", t=6, N=i, + f"cancion2_espectrograma_{window}_{i:04d}", t=6, N=i, win=window, ylim=[0, 8000]) def a4_flauta_cutoff(): diff --git a/tp/scripts/tercera_parte.py b/tp/scripts/tercera_parte.py @@ -149,16 +149,16 @@ def analisis_freq_ventanas(): def comparacion_de_espectrogramas_filtrado_vs_original(h): for i, cancion in enumerate(canciones_dataset): - spectogram_plot(cancion.fs, cancion.data, - f'{cancion.name}_espectograma_original_44100Hz', + spectrogram_plot(cancion.fs, cancion.data, + f'{cancion.name}_espectrograma_original_44100Hz', N=1024, ylim=[0, 20000]) filter_output = np.convolve(cancion.data, h, mode='same') - spectogram_plot(cancion.fs, filter_output, - f'{cancion.name}_espectograma_filtrado_{cutoff}Hz', + spectrogram_plot(cancion.fs, filter_output, + f'{cancion.name}_espectrograma_filtrado_{cutoff}Hz', N=1024, ylim=[0, 20000]) - spectogram_plot(cancion.fs, filter_output, + spectrogram_plot(cancion.fs, filter_output, f'{cancion.name}_espectrograma_filtrado_{cutoff}Hz_ylim_3000Hz_inferno', cmap='inferno', ylim=[0, 3000], N=4096, win='hamming') @@ -188,7 +188,11 @@ def generar_base_de_datos(): # E = obtener_caracteristicas(E) H = generar_huella(E, fs) - # graficar_huella(H, f'huella_acustica_{cancion.name}', xlim=[0, 100]) + + # if i == 0: + # graficar_huella(H, f'{cancion.name}_huella_acustica_completa') + + # graficar_huella(H, f'{cancion.name}_huella_acustica', xlim=[0, 100]) DB = guardar_huella(i, H, DB) with open(f'{database_name}', 'wb') as f: @@ -197,6 +201,26 @@ def generar_base_de_datos(): print(f'[LOG] Base de datos guardada en `./{database_name}`') return DB +def generar_base_de_datos_si_no_existe(): + if os.path.exists(database_name): + print(f'[LOG] Base de datos ya existe en `./{database_name}`, pasando...') + try: + with open(database_name, 'rb') as db_file: + DB = pickle.load(db_file) + + print(f"[LOG] Se cargaron los archivos de `{database_name}`") + + except FileNotFoundError: + print(f"[ERROR] No se encontro el archivo `{database_name}`") + except EOFError: + print(f"[ERROR] El archivo `{database_name}` puede estar incompleto o corrupto") + except Exception as e: + print(f"[ERROR] Ocurrio un error inesperado: {e}") + else: + DB = generar_base_de_datos() + + return DB + # calcula la matriz de características dado un espectrograma `E` def obtener_caracteristicas(E): bandas_totales = 21 # Número de bandas de frecuencia (M) @@ -255,25 +279,18 @@ def dividir_en_bandas_logaritmicas(Sxx, fs, n_bandas, fmin, fmax): def generar_huella(E, fs): E_db = dividir_en_bandas_logaritmicas(E, fs, 21, 300, 2000) - # E_db tiene dimensiones (m, n) -> (N_BANDAS, N_FRAMES) + # E_db tiene dimensiones (m, n) -> (n_bandas, n_frames) num_bandas, num_frames = E_db.shape - # La huella H(m, n) tendrá dimensiones (N_BANDAS - 1, N_FRAMES - 1) + # La huella H(m, n) tendrá dimensiones (n_bandas - 1, n_frames - 1) H = np.zeros((num_bandas - 1, num_frames - 1), dtype=int) - # Bucle para aplicar la lógica F(m, n) # n: tiempo (frame), m: banda for n in range(1, num_frames): # n: empieza en 1 (segundo frame) for m in range(num_bandas - 1): # m: va de la primera hasta la penúltima banda - - # TÉRMINO IZQUIERDO (Frame actual 'n'): E(m,n) - E(m+1, n) diff_actual = E_db[m, n] - E_db[m + 1, n] - - # TÉRMINO DERECHO (Frame anterior 'n-1'): E(m, n-1) - E(m+1, n-1) diff_anterior = E_db[m, n - 1] - E_db[m + 1, n - 1] - # Lógica F(m, n) - # F(m,n) = 1 si la diferencia de energía en el tiempo actual es mayor que la anterior. if diff_actual > diff_anterior: H[m, n - 1] = 1 # Se mapea n-1 al índice de la matriz H else: @@ -281,7 +298,7 @@ def generar_huella(E, fs): return H -def graficar_huella(H, save_name="", xlim=[], ylim=[]): +def graficar_huella(H, save_name="", xlim=[], ylim=[], save_dir=""): fig, axis = plt.subplots(figsize=(8, 4)) plt.pcolormesh(H, shading='auto', cmap='binary') @@ -301,7 +318,7 @@ def graficar_huella(H, save_name="", xlim=[], ylim=[]): if save_name == "": plt.show() else: - save_plot(fig, save_name) + save_plot(fig, save_name, save_dir=save_dir) def guardar_huella(ID, huella, DB): """ @@ -446,6 +463,28 @@ def query_DB(DB, huella): return ID.astype(np.uint32), MATCHES.astype(np.uint32) +def evaluar_cancion(DB, cancion): + h, fs = filtro_fir_deducido() + + nro_pruebas = 0 + data = np.convolve(cancion.data, h, mode='same') + print(f'[LOG] Evaluando cancion `{cancion.path}`') + duracion_cancion = len(data) / cancion.fs + print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s') + for r in range(0, 10): + for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos + t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T) + t_inicial = 0 if t_inicial < 0 else t_inicial + print(f'[LOG] Intervalo {t_inicial:05.02f}:{t_inicial+T:05.02f}s', end='') + f, t, E = generate_spectrogram(cancion.fs, data, t=t_inicial, dt=T) + H = generar_huella(E, cancion.fs) + + nro_pruebas += 1 + id, matches = query_DB(DB, H) + + print(f' mejor coincidencia: `{canciones_dataset[id[0]].name}`') + print("") + def evaluar_aciertos(DB): h, fs = filtro_fir_deducido() @@ -456,12 +495,12 @@ def evaluar_aciertos(DB): print(f'[LOG] Evaluando cancion `{cancion.path}`') duracion_cancion = len(data) / cancion.fs print(f'[LOG] Duracion cancion: {duracion_cancion:05.02f}s') - for r in range(0, 50): + for r in range(0, 10): for T in [5, 10, 20]: # segmentso de 5, 10 y 20 segundos t_inicial = random.uniform(0.0, 1.0) * (duracion_cancion - 1.5*T) t_inicial = 0 if t_inicial < 0 else t_inicial print(f'[LOG] Intervalo {t_inicial:05.02f}:{t_inicial+T:05.02f}s', end='') - f, t, E = generate_spectogram(cancion.fs, data, t=t_inicial, dt=T) + f, t, E = generate_spectrogram(cancion.fs, data, t=t_inicial, dt=T) H = generar_huella(E, cancion.fs) nro_pruebas += 1 diff --git a/tp/scripts/utils.py b/tp/scripts/utils.py @@ -128,10 +128,17 @@ def time_plot(fs, data, save_name="", t=0, dt=0, a=0, da=0): return fig, ax -def save_plot(fig, name, overwrite=True): +def save_plot(fig, name, overwrite=True, save_dir=""): base_name = os.path.basename(name) file_name, ext = os.path.splitext(base_name) - file_path_no_ext = f'{plot_dir}{file_name}' + + if save_dir == "": + save_dir = plot_dir + + if save_dir != "" and not save_dir.endswith('/'): + save_dir += "/" + + file_path_no_ext = f'{save_dir}{file_name}' save_name = f'{file_path_no_ext}.png' @@ -149,7 +156,7 @@ def save_plot(fig, name, overwrite=True): # crea carpeta para plots os.makedirs(plot_dir, exist_ok=True) - fig.savefig(save_name, dpi=250, bbox_inches="tight") + fig.savefig(save_name, dpi=100, bbox_inches="tight") plt.close(fig) # liberar memoria def save_to_wav(fs, data, save_name): @@ -163,7 +170,7 @@ def save_to_wav(fs, data, save_name): os.makedirs(out_dir, exist_ok=True) file_path = f'{out_dir}{save_name}' - print(file_path) + print(f'[LOG] Guardando audio: `{file_path}') wavfile.write(file_path, fs, data_as_int16) # frecuencia @@ -368,7 +375,7 @@ def freq_plot_multiple(fs, data_arr, leg_arr, save_name="", if save_name != "": save_plot(fig, save_name) -def generate_spectogram(fs, data, t=0, dt=0, N=1024, overlp=16, win='hamm'): +def generate_spectrogram(fs, data, t=0, dt=0, N=1024, overlp=16, win='hamm'): if dt == 0: dt = (len(data)/fs)-t @@ -381,16 +388,16 @@ def generate_spectogram(fs, data, t=0, dt=0, N=1024, overlp=16, win='hamm'): f, time, Sxx = spectrogram(interval_data, fs=fs, nperseg=N, noverlap=overlp, window=win) - return f, t, Sxx + return f, time, Sxx -def spectogram_plot(fs, data, save_name="", t=0, dt=0, N=1024, +def spectrogram_plot(fs, data, save_name="", t=0, dt=0, N=1024, overlp=16, win='hamm', xlim=[], ylim=[], shading='gouraud', cmap='viridis', show=False): - f, time, Sxx = generate_spectogram(fs, data, t, dt, N, overlp, win) + f, time, Sxx = generate_spectrogram(fs, data, t, dt, N, overlp, win) fig, axis = plt.subplots(figsize=(8, 4)) - # plt.pcolormesh(time, f, Sxx**0.10, shading='gouraud') + # plt.pcolormesh(time, f, Sxx**0.10) plt.pcolormesh(time, f, 10*np.log10(Sxx + 1e-12), shading=shading, cmap=cmap) plt.ylabel('Frecuencia [Hz]') diff --git a/tp/tercera_parte.tex b/tp/tercera_parte.tex @@ -8,6 +8,25 @@ secciones previas. El objetivo es implementar una herramienta que analice un son musical, particularmente una canción en formato digital, e identifique la canción basándose en una lista de canciones analizadas previamente. +El procedimiento básico consiste en analizar el segmento de audio, +extraer características particulares en el espacio tiempo-frecuencia (es +decir, en su espectrograma) que sirvan para identificarlo luego. Al conjunto de estas características se las denomina huella digital acústica, ya que es análogo a como una huella dactilar se identifica por las convergencias, desviaciones, interrupciones y demás particularidades de las crestas papilares. Este procedimiento de extracción de huellas se utiliza tanto para confeccionar la base de datos de canciones conocidas como para posteriormente reconocer segmentos de audio sin identificar, consultando la base de datos. + +El sistema de reconocimiento consta de dos bloques principales: +\begin{itemize} + \item El algoritmo para extraer las huellas digitales acústicas. + \item La base de datos que almacena las huellas acústicas de las canciones conocidas. +\end{itemize} + +El algoritmo para extraer las huellas acústicas toma como entrada el archivo con el audio y, luego de un pre-procesamiento de la señal, extrae las características y entrega como resultado la huella acústica, tal como se muestra en la figura \ref{fig-diagrama-bloques-general}. + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{img/diagrama_de_bloques_general.png} + \caption{Diagrama de bloques sistema de reconocimiento} + \label{fig-diagrama-bloques-general} +\end{figure} + \hypertarget{reduccion-de-frecuencia}{% \subsection{Reducción de frecuencia}\label{reduccion-de-frecuencia}} @@ -26,9 +45,9 @@ Para reducir la frecuencia de muestreo de la señal discreta de $44100$Hz a $551 \label{eq-señal-submuestreada} \end{align} -El diagrama debloques del sistema a utilizar se compone de un filtro pasa-bajos y luego un muestreador o compresor de la señal en dominio temporal como se muestra en la figura \ref{fig-diagrama-de-bloques-decimador}, en la cual $x(n)$ representa la señal de entrada y $x_{d}(n)$ la señal decimada. El filtro pasa-bajos se utiliza para evitar aliasing si la señal de entrada $x_{n}$ no permite decimación en $8$ veces, es decir, tiene ancho de banda mayor a $2\pi/8$ o en frecuencia siendo $5512.5$Hz +El diagrama de bloques del sistema a utilizar se compone de un filtro pasa-bajos y luego un muestreador o compresor de la señal en dominio temporal como se muestra en la figura \ref{fig-diagrama-de-bloques-decimador}, en la cual $x(n)$ representa la señal de entrada y $x_{d}(n)$ la señal decimada. El filtro pasa-bajos se utiliza para evitar aliasing si la señal de entrada $x_{n}$ no permite decimación en $8$ veces, es decir, tiene ancho de banda mayor a $2\pi/8$ o en frecuencia siendo $5512.5$Hz -\begin{figure}[!ht] +\begin{figure}[!h] \centering \includegraphics[width=0.75\linewidth]{img/diagrama_de_bloques-decimador.png} \caption{Diagrama de bloques reducción de frecuencia} @@ -89,7 +108,7 @@ Para la ventana se utiliza la ventana de Hamming, ya que es muy simple la implem Teniendo la expresión de la ventana $w(n)$ y la respuesta al impulso del filtro ideal $h_{ideal}(n)$ se obtiene la respuesta al impulso del filtro FIR $h(n)$ de la ecuación \ref{eq-ventaneo}, en este caso tomando 700 coeficientes, es decir, se tiene un filtro de grado 700. La respuesta al impulso se muestra graficada en la figura \ref{fig-respuesta-al-impulso-fir} en la cual se ve claramente la forma de la función sinc. -\begin{figure}[!ht] +\begin{figure}[!h] \centering \includegraphics[width=\linewidth]{plot/respuesta_al_impulso_filtro_fir.png} \caption{Respuesta al impulso filtro pasa-bajos FIR de grado $700$} @@ -98,14 +117,14 @@ Teniendo la expresión de la ventana $w(n)$ y la respuesta al impulso del filtro La respuesta en frecuencia del filtro se obtiene tomando $z=j\omega$ en la función de transferencia del sistema $H(z)$ mencionada previamente en la ecuación \ref{fig-respuesta-en-freq-fir}. En la figura \ref{fig-respuesta-en-freq-fir} se grafica la respuesta en frecuencia del filtro pasa-bajos elegido, la magnitud se muestra en color azul y la fase en color rojo. Se nota que a partir de la frecuencia de corte ($2627.1$ Hz, correspondiente al punto de $-3$dB) se atenúan drásticamente las señales y en particular para las frecuencias mayores a la de Nyquist ($2756.2$ Hz) ya se puede decir prácticamente que se atenúan por completo ya que se tiene una ganancia de $-60dB$, esto es $0.001$ veces, lo cual era el objetivo para evitar aliasing al decimar. Además se observa la fase lineal hasta la frecuencia de corte, esta es una de las principales características de los filtros FIR por sobre los IIR, que es que no distorsionan la fase. -\begin{figure}[!ht] +\begin{figure}[!h] \centering \includegraphics[width=\linewidth]{plot/respuesta_en_frecuencia_pasa-bajos_fir.png} \caption{Respuesta en frecuencia filtro pasa-bajos FIR de grado $700$} \label{fig-respuesta-en-freq-fir} \end{figure} -Los ceros $z_{i}$ y polos $p_{i}$ se definen como aquellos puntos del plano complejo en donde la función de transferencia del sistema $H(jw)$ se anula, en el caso de los ceros y diverge a infinito, en el caso de los polos. La particularidad de los filtros FIR es que no tienen polos distintos de cero, esto sale de analizar la ecuación \ref{eq-transferencia-filtro-fir}, se tiene una sumatoria de coeficientes no nulos constantes multiplicados por la variable $z^{-1}$, la única forma de que la transferencia tienda a infinito es que la variable z tienda a $0$ de manera de que $z^{-1}$ diverja. +Los ceros $z_{i}$ y polos $p_{i}$ se definen como aquellos puntos del plano complejo en donde la función de transferencia del sistema $H(j\omega)$ se anula, en el caso de los ceros y diverge a infinito, en el caso de los polos. La particularidad de los filtros FIR es que no tienen polos distintos de cero, esto sale de analizar la ecuación \ref{eq-transferencia-filtro-fir}, se tiene una sumatoria de coeficientes no nulos constantes multiplicados por la variable $z^{-1}$, la única forma de que la transferencia tienda a infinito es que la variable z tienda a $0$ de manera de que $z^{-1}$ diverja. %ve factorizando la expresión de la ecuación \ref{eq-transferencia-filtro-fir} como se muestra en la ecuación \ref{eq-transferencia-filtro-fir-factorizada}. @@ -114,9 +133,96 @@ Los ceros $z_{i}$ y polos $p_{i}$ se definen como aquellos puntos del plano comp % \label{eq-transferencia-filtro-fir-factorizada} %\end{align} -\begin{figure}[!ht] +\begin{figure}[!h] \centering \includegraphics[width=\linewidth]{plot/polos_y_ceros_pasa-bajos_fir.png} \caption{Polos y ceros filtro pasa-bajos FIR de grado $700$} - \label{fig-respuesta-en-freq-fir} + \label{fig-polos_y_ceros_pasa-bajos_fir} +\end{figure} + +La principal ventaja de utilizar la ventana de Hamming en lugar de la rectangular al realizar un espectrograma es la reducción drástica de la fuga espectral, lo que resulta en una representación de frecuencia más limpia y precisa de las señales en el tiempo. + +Cuando se calcula un espectrograma, el proceso implica dividir la señal continua en pequeños segmentos (ventanas) para calcular la Transformada Rápida de Fourier (FFT) de cada segmento. + +La ventana rectangular es un corte brusco de la señal. Cuando esta ventana se multiplica con la señal en el dominio del tiempo, crea una discontinuidad en los puntos de inicio y fin de la ventana. + +En el dominio de la frecuencia, esta discontinuidad se traduce en un espectro con lóbulos laterales muy altos (el primer lóbulo está a solo 13 dB por debajo del pico principal). + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/potencia_db_espectro_ventana_comparacion_hamming_rect.png} + \caption{Comparación potencia de ventana rectangular y Hamming} + \label{fig-potencia_db_espectro_ventana_comparacion_hamming_rect} +\end{figure} + +La ventana de Hamming tiene un lóbulo principal ligeramente más ancho que la rectangular, lo que resulta en una peor resolución de frecuencia (es más difícil distinguir dos frecuencias muy, muy cercanas) + +\iffalse +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/espectograma_fs_original_44100Hz.png} + \caption{Espectrograma muestra `000002.mp3` original} + \label{fig-espectograma_fs_original_44100Hz} +\end{figure} + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/espectograma_fs_2650Hz.png} + \caption{Espectrograma muestra `000002.mp3` filtrado} + \label{fig-espectograma_fs_2650Hz} +\end{figure} +\fi + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/000002_espectrograma_filtrado_2650Hz_ylim_3000Hz_inferno.png} + \caption{Espectrograma muestra `000002.mp3` filtrado} + \label{fig-espectrograma_000002} +\end{figure} + +\hypertarget{generacion-de-huella}{% +\subsection{Generación de huellas acústicas}\label{generacion-de-huella}} + +El algoritmo de extracción de huellas digitales acústicas extrae características a partir del espectrograma del +audio. El conjunto de estas características conforman la huella. + +Las características se obtienen mediante una función signo $F(m,n)$ que opera sobre la energía de las bandas $E(m,n)$ según la ecuación \ref{eq-energia-de-banda}, donde $n$ es el tiempo y $m$ la banda de frecuencia. + +\begin{align} + F (m, n) = \left\{ + \begin{array}{rl} + 1 & E(m, n) - E(n, m+1) > E(n-1, m) - E(n-1, m+1)\\ + 0 & \mbox{en otro caso} + \end{array} + \right. + \label{eq-energia-de-banda} +\end{align} + +En la figura \ref{fig-huella_acustica_000002_completa} se muestra un gráfico de la huella generada para una canción de muestra `000002.mp3` utilizando el algoritmo mencionado previamente. Luego en la figura \ref{fig-huella_acustica_000002_segmento} se toma el segmento de $0$ a $100$ de manera que se aprecie aun mejor el patrón de unos y ceros. En la figura \ref{fig-huella_acustica_000141} se grafica la huella de otra muestra para el mismo segmento anterior, de $0$ a $100$, nótese la diferencia entre el patrón generado. + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/000002_huella_acustica_completa.png} + \caption{Huella acústica muestra `000002.mp3` completa} + \label{fig-huella_acustica_000002_completa} +\end{figure} + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/000002_huella_acustica.png} + \caption{Huella acústica muestra `000002.mp3` segmento} + \label{fig-huella_acustica_000002_segmento} \end{figure} + +\begin{figure}[!h] + \centering + \includegraphics[width=\linewidth]{plot/000141_huella_acustica.png} + \caption{Huella acústica muestra `000141.mp3`} + \label{fig-huella_acustica_000141} +\end{figure} + +\pagebreak +\hypertarget{resultados}{% +\subsection{Resultados}\label{resultados}} + +Utilizando 16 canciones de muestra, se generaron las huellas como se menciono previamente y se cargaron estas huellas a la base de datos. Para verificar cual es el error al identificar canciones se generaron pruebas procedurales, en particular se genero un total de $2550$ identificaciones de las cuales $29$ fueron erróneas, esto resulta en un error porcentual de $1.14$\%. En una primera instancia el error fue mayor, pero se corrigió mediante el ajuste del ancho de ventana y del ancho de solapamiento, a la hora de calcular el espectrograma para generar la huella de las canciones de muestra (las que se guardan en la base de datos).
