Добавить отчёт (tex+pdf) и ipynb с анализом восстановления аудио DeepFilterNet3

This commit is contained in:
2026-06-27 07:11:43 +03:00
commit 7a7e9205ac
3 changed files with 371 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Сравнение эффективности восстановления звука: DeepFilterNet3\n",
"\n",
"Сравнение оригинального, зашумлённого и восстановленного (DeepFilterNet3) аудиосигналов."
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"import numpy as np\n",
"from scipy.io import wavfile\n",
"from scipy import signal\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
"import librosa\n",
"import librosa.display\n",
"from IPython.display import Audio, display\n",
"\n",
"plt.rcParams['figure.dpi'] = 120\n",
"plt.rcParams['figure.figsize'] = (14, 4)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"sr, orig = wavfile.read('VoiceA.wav')\n",
"_, noisy = wavfile.read('VoiceA_s1-3_Mix.wav')\n",
"_, rest = wavfile.read('VoiceA_s1-3_Mix_Restored.wav')\n",
"\n",
"def to_mono(x):\n",
" return x.mean(axis=1).astype(np.float64) if x.ndim > 1 else x.astype(np.float64)\n",
"\n",
"orig_m = to_mono(orig)\n",
"noisy_m = to_mono(noisy)\n",
"rest_m = to_mono(rest)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Прослушивание"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"print('Оригинал:')\n",
"display(Audio(orig_m, rate=sr))\n",
"print('Зашумлённый:')\n",
"display(Audio(noisy_m, rate=sr))\n",
"print('Восстановленный:')\n",
"display(Audio(rest_m, rate=sr))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Метрики качества"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"def snr(clean, proc):\n",
" return 10*np.log10(np.sum(clean**2)/(np.sum((clean-proc)**2)+1e-10))\n",
"\n",
"def seg_snr(clean, proc, sr, seg_ms=30):\n",
" seg_len = int(sr*seg_ms/1000)\n",
" n = len(clean)//seg_len\n",
" vals = []\n",
" for i in range(n):\n",
" s = clean[i*seg_len:(i+1)*seg_len]\n",
" p = proc[i*seg_len:(i+1)*seg_len]\n",
" v = 10*np.log10((np.sum(s**2)+1e-10)/(np.sum((s-p)**2)+1e-10))\n",
" vals.append(np.clip(v, -10, 35))\n",
" return np.mean(vals)\n",
"\n",
"def lsd(clean, proc, sr, n_fft=512):\n",
" _, _, Sx_c = signal.spectrogram(clean, sr, nperseg=n_fft)\n",
" _, _, Sx_p = signal.spectrogram(proc, sr, nperseg=n_fft)\n",
" Sx_c = np.abs(Sx_c)/np.max(np.abs(Sx_c))+1e-10\n",
" Sx_p = np.abs(Sx_p)/np.max(np.abs(Sx_p))+1e-10\n",
" return np.sqrt(np.mean((10*np.log10(Sx_c**2/Sx_p**2))**2))\n",
"\n",
"def stoi_score(clean, proc, sr):\n",
" try:\n",
" import pystoi\n",
" return pystoi.stoi(clean/np.max(np.abs(clean)), proc/np.max(np.abs(proc)), sr)\n",
" except ImportError:\n",
" return None\n",
"\n",
"metrics = {\n",
" 'SNR (дБ)': [snr(orig_m, noisy_m), snr(orig_m, rest_m)],\n",
" 'Segmental SNR (дБ)': [seg_snr(orig_m, noisy_m, sr), seg_snr(orig_m, rest_m, sr)],\n",
" 'LSD (дБ)': [lsd(orig_m, noisy_m, sr), lsd(orig_m, rest_m, sr)],\n",
" 'Корреляция': [np.corrcoef(orig_m, noisy_m)[0,1], np.corrcoef(orig_m, rest_m)[0,1]],\n",
" 'RMSE': [np.sqrt(np.mean((orig_m-noisy_m)**2)), np.sqrt(np.mean((orig_m-rest_m)**2))],\n",
"}\n",
"\n",
"df = pd.DataFrame(metrics, index=['Noisy', 'Restored']).T\n",
"df.columns = ['Шумный', 'Восстановленный']\n",
"df['Улучшение'] = df['Восстановленный'] - df['Шумный']\n",
"df"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## STOI (при наличии pystoi)"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"stoi_n = stoi_score(orig_m, noisy_m, sr)\n",
"stoi_r = stoi_score(orig_m, rest_m, sr)\n",
"if stoi_n is not None:\n",
" print(f'STOI Noisy: {stoi_n:.4f}')\n",
" print(f'STOI Restored: {stoi_r:.4f}')\n",
" print(f'Улучшение: {stoi_r-stoi_n:+.4f}')\n",
"else:\n",
" print('pystoi не установлен — пропускаем')"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Спектрограммы"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"fig, axes = plt.subplots(1, 3, figsize=(16, 4))\n",
"for ax, data, title in zip(axes, [orig_m, noisy_m, rest_m], ['Original', 'Noisy', 'Restored']):\n",
" D = librosa.amplitude_to_db(np.abs(librosa.stft(data)), ref=np.max)\n",
" librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='hz', ax=ax, cmap='magma')\n",
" ax.set_title(title)\n",
" ax.set_ylim([0, 8000])\n",
"plt.tight_layout()\n",
"plt.show()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Спектральное сравнение (усреднённый спектр)"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"fig, ax = plt.subplots(figsize=(12, 4))\n",
"for data, label, color in zip([orig_m, noisy_m, rest_m], ['Original', 'Noisy', 'Restored'], ['C0', 'C1', 'C2']):\n",
" spec = np.abs(librosa.stft(data))\n",
" mean_spec = np.mean(spec, axis=1)\n",
" freqs = librosa.fft_frequencies(sr=sr)\n",
" ax.semilogy(freqs, mean_spec, color=color, label=label, alpha=0.8)\n",
"ax.set_xlim([0, 8000])\n",
"ax.set_xlabel('Frequency (Hz)')\n",
"ax.set_ylabel('Magnitude')\n",
"ax.legend()\n",
"ax.grid(alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LSD(t): Логарифмическое спектральное расстояние во времени"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"def lsd_t(clean, proc, sr, n_fft=512):\n",
" _, t, Sx_c = signal.spectrogram(clean, sr, nperseg=n_fft)\n",
" _, _, Sx_p = signal.spectrogram(proc, sr, nperseg=n_fft)\n",
" Sx_c = np.abs(Sx_c)/np.max(np.abs(Sx_c))+1e-10\n",
" Sx_p = np.abs(Sx_p)/np.max(np.abs(Sx_p))+1e-10\n",
" lsd_f = np.sqrt(np.mean((10*np.log10(Sx_c**2/Sx_p**2))**2, axis=0))\n",
" return t, lsd_f\n",
"\n",
"t, lsd_n = lsd_t(orig_m, noisy_m, sr)\n",
"_, lsd_r = lsd_t(orig_m, rest_m, sr)\n",
"\n",
"fig, ax = plt.subplots(figsize=(12, 3))\n",
"ax.plot(t, lsd_n, label=f'Noisy (mean={np.mean(lsd_n):.1f} dB)', alpha=0.7, lw=0.8)\n",
"ax.plot(t, lsd_r, label=f'Restored (mean={np.mean(lsd_r):.1f} dB)', alpha=0.7, lw=0.8)\n",
"ax.set_xlabel('Time (s)')\n",
"ax.set_ylabel('LSD (dB)')\n",
"ax.legend()\n",
"ax.grid(alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Выводы\n",
"\n",
"- **SNR**: $0.92 \\rightarrow 2.36$ дБ (+1.44)\n",
"- **Segmental SNR**: $-1.45 \\rightarrow 1.26$ дБ (+2.71)\n",
"- **LSD**: $73.78 \\rightarrow 31.34$ дБ (снижение в 2.4 раза)\n",
"- **RMSE**: снижение на 15%\n",
"\n",
"DeepFilterNet3 эффективно подавляет широкополосный шум, особенно в паузах. При исходном SNR~1 дБ полное восстановление невозможно, но качество сигнала заметно улучшается."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
BIN
View File
Binary file not shown.
+109
View File
@@ -0,0 +1,109 @@
\documentclass[12pt,a4paper]{article}
\usepackage{fontspec}
\usepackage{polyglossia}
\setmainlanguage[spelling=modern]{russian}
\setotherlanguage{english}
\setmainfont{Times New Roman}
\setmonfont{Courier New}
\usepackage{graphicx}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{booktabs}
\usepackage{multirow}
\usepackage{geometry}
\geometry{top=2cm, bottom=2cm, left=2.5cm, right=2cm}
\usepackage{setspace}
\onehalfspacing
\usepackage{hyperref}
\usepackage{float}
\begin{document}
\section{Введение}
Цель работы~--- оценка эффективности метода восстановления звуковых сигналов с использованием нейросетевой модели DeepFilterNet3, интегрированной в среду Audacity через OpenVINO. В качестве исходного материала использована запись голоса (VoiceA), к которой был добавлен шум (SNR~1~dB). Восстановление выполнялось алгоритмом DeepFilterNet3.
\section{Метрики оценки качества}
Для количественной оценки использовались следующие метрики:
\begin{itemize}
\item \textbf{SNR (Signal-to-Noise Ratio)}~--- отношение мощности сигнала к мощности шума;
\item \textbf{Segmental SNR}~--- усреднённый SNR по сегментам 30~ms с клиппированием {[}-10, 35{]}~dB;
\item \textbf{LSD (Log-Spectral Distance)}~--- среднеквадратичное логарифмическое спектральное расстояние;
\item \textbf{Коэффициент корреляции Пирсона} между сигналами;
\item \textbf{RMSE (Root Mean Square Error)}~--- среднеквадратичная ошибка;
\item \textbf{Фактор шумоподавления}~--- отношение дисперсии шума до и после обработки.
\end{itemize}
\section{Результаты}
\subsection{Сводная таблица метрик}
\begin{table}[H]
\centering
\caption{Сравнение метрик для зашумлённого и восстановленного сигналов}
\label{tab:metrics}
\begin{tabular}{lrrr}
\toprule
Метрика & Шумный & Восстановленный & Улучшение \\
\midrule
SNR (dB) & 0.92 & 2.36 & +1.44 \\
Segmental SNR (dB)& $-$1.45 & 1.26 & +2.71 \\
LSD (dB) & 73.78 & 31.34 & $-$42.44 \\
Корреляция & 0.6664 & 0.6633 & $-$0.0031 \\
RMSE & 9202 & 7801 & 15.2\% \\
Шумоподавление (dB)& --- & --- & +1.44 \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Анализ результатов}
DeepFilterNet3 демонстрирует следующие результаты:
\begin{itemize}
\item \textbf{SNR +1.44~dB}: прирост отношения сигнал/шум положительный, однако скромный. Исходный SNR~0.9~dB был крайне низким~--- модель работает в условиях сильного шума.
\item \textbf{Segmental SNR +2.71~dB}: посегментный анализ показывает более существенное улучшение на коротких интервалах, что говорит о качественном подавлении шума в паузах.
\item \textbf{LSD $-$42.44~dB}: резкое снижение логарифмического спектрального расстояния~--- спектральная форма восстановленного сигнала значительно ближе к оригиналу, чем у шумного.
\item \textbf{Корреляция}: практически не изменилась (0.666 против 0.663). Это связано с тем, что шум аддитивный и не разрушает линейную структуру сигнала, а слабое подавление не восстанавливает корреляцию.
\item \textbf{RMSE}: снизилась на 15\%~--- амплитудная ошибка уменьшилась.
\end{itemize}
Ключевое наблюдение: модель эффективно подавляет шум в \textit{спектральной} области (LSD падает в 2.4~раза), но вклад шума в амплитудную ошибку остаётся значительным. Это характерно для DeepFilterNet, которая работает в частотной области.
\subsection{Визуализация}
На рис.~\ref{fig:analysis} представлен комплексный анализ сигналов.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth]{analysis_plot.png}
\caption{Сравнение исходного, зашумлённого и восстановленного сигналов. Строки: (1)~осциллограммы, (2)~спектрограммы, (3)~усреднённые спектры, (4)~спектрограмма шума, ошибки восстановления и LSD(t).}
\label{fig:analysis}
\end{figure}
\subsection{Спектральный анализ}
По спектрограммам (рис.~\ref{fig:analysis}, строка~2) видно:
\begin{itemize}
\item Шум занимает широкий частотный диапазон (0--8~kHz) и маскирует формантные структуры голоса.
\item Восстановленный сигнал имеет более чистую спектральную картину; шумовой фон в паузах подавлен.
\item Высокочастотные компоненты (>4~kHz) восстанавливаются хуже~--- часть гармоник теряется.
\end{itemize}
Спектрограммы остаточной ошибки (строка~4, центр) показывают, что наибольшая ошибка восстановления сосредоточена в области 1--3~kHz, где энергия голоса максимальна~--- модель осторожничает и не подавляет шум в этой области, чтобы не исказить речь.
\section{Заключение}
DeepFilterNet3 в Audacity (OpenVINO) показал следующие результаты:
\begin{itemize}
\item Улучшение SNR на +1.44~dB и Segmental SNR на +2.71~dB;
\item Значительное снижение спектрального искажения (LSD с 73.8 до 31.3~dB);
\item Снижение RMSE на 15\%;
\item Корреляция с оригиналом практически не изменилась.
\end{itemize}
Метод эффективен для подавления широкополосного шума, особенно заметен эффект в паузах между речевыми сегментами. При очень низком исходном SNR ($\sim$1~dB) полное восстановление невозможно, однако модель демонстрирует разумный компромисс между подавлением шума и сохранением речевого сигнала. Для лучших результатов рекомендовано использовать более высокое качество входного сигнала (SNR~5--10~dB) или комбинировать DeepFilterNet с предварительной фильтрацией.
\end{document}