neverlib 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neverlib/__init__.py +2 -2
- neverlib/audio_aug/__init__.py +1 -1
- neverlib/audio_aug/audio_aug.py +4 -5
- neverlib/dataAnalyze/README.md +234 -0
- neverlib/dataAnalyze/__init__.py +87 -0
- neverlib/dataAnalyze/dataset_analyzer.py +590 -0
- neverlib/dataAnalyze/quality_metrics.py +364 -0
- neverlib/dataAnalyze/rms_distrubution.py +62 -0
- neverlib/dataAnalyze/spectral_analysis.py +218 -0
- neverlib/dataAnalyze/statistics.py +406 -0
- neverlib/dataAnalyze/temporal_features.py +126 -0
- neverlib/dataAnalyze/visualization.py +468 -0
- neverlib/filter/AudoEQ/README.md +165 -0
- neverlib/filter/AudoEQ/auto_eq_de.py +361 -0
- neverlib/filter/AudoEQ/auto_eq_ga_advanced.py +577 -0
- neverlib/filter/AudoEQ/auto_eq_ga_basic.py +380 -0
- neverlib/filter/AudoEQ/auto_eq_spectral_direct.py +75 -0
- neverlib/filter/README.md +101 -0
- neverlib/filter/__init__.py +7 -0
- neverlib/filter/biquad.py +45 -0
- neverlib/filter/common.py +5 -6
- neverlib/filter/core.py +339 -0
- neverlib/metrics/dnsmos.py +160 -0
- neverlib/metrics/snr.py +177 -0
- neverlib/metrics/spec.py +45 -0
- neverlib/metrics/test_pesq.py +35 -0
- neverlib/metrics/time.py +68 -0
- neverlib/tests/test_vad.py +21 -0
- neverlib/utils/audio_split.py +5 -3
- neverlib/utils/message.py +4 -4
- neverlib/utils/utils.py +32 -15
- neverlib/vad/PreProcess.py +1 -1
- neverlib/vad/README.md +10 -10
- neverlib/vad/VAD_Energy.py +1 -1
- neverlib/vad/VAD_Silero.py +1 -1
- neverlib/vad/VAD_WebRTC.py +1 -1
- neverlib/vad/VAD_funasr.py +1 -1
- neverlib/vad/VAD_statistics.py +3 -3
- neverlib/vad/VAD_vadlib.py +2 -2
- neverlib/vad/VAD_whisper.py +1 -1
- neverlib/vad/__init__.py +1 -1
- neverlib/vad/class_get_speech.py +4 -4
- neverlib/vad/class_vad.py +1 -1
- neverlib/vad/utils.py +47 -5
- {neverlib-0.2.1.dist-info → neverlib-0.2.3.dist-info}/METADATA +120 -120
- neverlib-0.2.3.dist-info/RECORD +53 -0
- {neverlib-0.2.1.dist-info → neverlib-0.2.3.dist-info}/WHEEL +1 -1
- neverlib/Documents/vad/VAD_Energy.ipynb +0 -159
- neverlib/Documents/vad/VAD_Silero.ipynb +0 -305
- neverlib/Documents/vad/VAD_WebRTC.ipynb +0 -183
- neverlib/Documents/vad/VAD_funasr.ipynb +0 -179
- neverlib/Documents/vad/VAD_ppasr.ipynb +0 -175
- neverlib/Documents/vad/VAD_statistics.ipynb +0 -522
- neverlib/Documents/vad/VAD_vadlib.ipynb +0 -184
- neverlib/Documents/vad/VAD_whisper.ipynb +0 -430
- neverlib/utils/waveform_analyzer.py +0 -51
- neverlib/wav_data/000_short.wav +0 -0
- neverlib-0.2.1.dist-info/RECORD +0 -40
- {neverlib-0.2.1.dist-info → neverlib-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {neverlib-0.2.1.dist-info → neverlib-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""
|
|
2
|
+
可视化模块
|
|
3
|
+
Visualization Module
|
|
4
|
+
|
|
5
|
+
提供音频数据可视化功能
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import librosa
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
import matplotlib.patches as patches
|
|
12
|
+
from matplotlib.colors import LinearSegmentedColormap
|
|
13
|
+
import seaborn as sns
|
|
14
|
+
from typing import List, Dict, Tuple, Optional, Union
|
|
15
|
+
import warnings
|
|
16
|
+
from scipy.signal import spectrogram
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AudioVisualizer:
|
|
20
|
+
"""音频可视化器类"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, sr: int = 22050, figsize: Tuple[int, int] = (12, 8)):
|
|
23
|
+
"""
|
|
24
|
+
初始化可视化器
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
sr: 采样率
|
|
28
|
+
figsize: 图形大小
|
|
29
|
+
"""
|
|
30
|
+
self.sr = sr
|
|
31
|
+
self.figsize = figsize
|
|
32
|
+
|
|
33
|
+
# 设置中文字体支持
|
|
34
|
+
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
|
|
35
|
+
plt.rcParams['axes.unicode_minus'] = False
|
|
36
|
+
|
|
37
|
+
# 设置样式
|
|
38
|
+
plt.style.use('default')
|
|
39
|
+
sns.set_palette("husl")
|
|
40
|
+
|
|
41
|
+
def plot_waveform(self, audio: np.ndarray, title: str = "音频波形图",
|
|
42
|
+
show_time: bool = True, ax: Optional[plt.Axes] = None) -> plt.Figure:
|
|
43
|
+
"""
|
|
44
|
+
绘制音频波形图
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
audio: 音频信号
|
|
48
|
+
title: 图标题
|
|
49
|
+
show_time: 是否显示时间轴
|
|
50
|
+
ax: matplotlib轴对象
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
图形对象
|
|
54
|
+
"""
|
|
55
|
+
if ax is None:
|
|
56
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
57
|
+
else:
|
|
58
|
+
fig = ax.figure
|
|
59
|
+
|
|
60
|
+
if show_time:
|
|
61
|
+
time_axis = np.linspace(0, len(audio) / self.sr, len(audio))
|
|
62
|
+
ax.plot(time_axis, audio, linewidth=0.5, alpha=0.8)
|
|
63
|
+
ax.set_xlabel('时间 (s)')
|
|
64
|
+
else:
|
|
65
|
+
ax.plot(audio, linewidth=0.5, alpha=0.8)
|
|
66
|
+
ax.set_xlabel('样本点')
|
|
67
|
+
|
|
68
|
+
ax.set_ylabel('幅度')
|
|
69
|
+
ax.set_title(title)
|
|
70
|
+
ax.grid(True, alpha=0.3)
|
|
71
|
+
|
|
72
|
+
# 添加零线
|
|
73
|
+
ax.axhline(y=0, color='red', linestyle='--', alpha=0.5)
|
|
74
|
+
|
|
75
|
+
plt.tight_layout()
|
|
76
|
+
return fig
|
|
77
|
+
|
|
78
|
+
def plot_spectrogram(self, audio: np.ndarray, title: str = "频谱图",
|
|
79
|
+
n_fft: int = 2048, hop_length: int = 512,
|
|
80
|
+
ax: Optional[plt.Axes] = None) -> plt.Figure:
|
|
81
|
+
"""
|
|
82
|
+
绘制频谱图
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
audio: 音频信号
|
|
86
|
+
title: 图标题
|
|
87
|
+
n_fft: FFT窗口大小
|
|
88
|
+
hop_length: 跳跃长度
|
|
89
|
+
ax: matplotlib轴对象
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
图形对象
|
|
93
|
+
"""
|
|
94
|
+
if ax is None:
|
|
95
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
96
|
+
else:
|
|
97
|
+
fig = ax.figure
|
|
98
|
+
|
|
99
|
+
# 计算频谱图
|
|
100
|
+
D = librosa.stft(audio, n_fft=n_fft, hop_length=hop_length)
|
|
101
|
+
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
|
|
102
|
+
|
|
103
|
+
# 绘制
|
|
104
|
+
img = librosa.display.specshow(S_db, sr=self.sr, hop_length=hop_length,
|
|
105
|
+
x_axis='time', y_axis='hz', ax=ax)
|
|
106
|
+
|
|
107
|
+
ax.set_title(title)
|
|
108
|
+
ax.set_xlabel('时间 (s)')
|
|
109
|
+
ax.set_ylabel('频率 (Hz)')
|
|
110
|
+
|
|
111
|
+
# 添加颜色条
|
|
112
|
+
cbar = plt.colorbar(img, ax=ax, format='%+2.0f dB')
|
|
113
|
+
cbar.set_label('幅度 (dB)')
|
|
114
|
+
|
|
115
|
+
plt.tight_layout()
|
|
116
|
+
return fig
|
|
117
|
+
|
|
118
|
+
def plot_mel_spectrogram(self, audio: np.ndarray, title: str = "梅尔频谱图",
|
|
119
|
+
n_mels: int = 128, ax: Optional[plt.Axes] = None) -> plt.Figure:
|
|
120
|
+
"""
|
|
121
|
+
绘制梅尔频谱图
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
audio: 音频信号
|
|
125
|
+
title: 图标题
|
|
126
|
+
n_mels: 梅尔滤波器数量
|
|
127
|
+
ax: matplotlib轴对象
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
图形对象
|
|
131
|
+
"""
|
|
132
|
+
if ax is None:
|
|
133
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
134
|
+
else:
|
|
135
|
+
fig = ax.figure
|
|
136
|
+
|
|
137
|
+
# 计算梅尔频谱图
|
|
138
|
+
S = librosa.feature.melspectrogram(y=audio, sr=self.sr, n_mels=n_mels)
|
|
139
|
+
S_db = librosa.power_to_db(S, ref=np.max)
|
|
140
|
+
|
|
141
|
+
# 绘制
|
|
142
|
+
img = librosa.display.specshow(S_db, sr=self.sr, x_axis='time',
|
|
143
|
+
y_axis='mel', ax=ax)
|
|
144
|
+
|
|
145
|
+
ax.set_title(title)
|
|
146
|
+
ax.set_xlabel('时间 (s)')
|
|
147
|
+
ax.set_ylabel('梅尔频率')
|
|
148
|
+
|
|
149
|
+
# 添加颜色条
|
|
150
|
+
cbar = plt.colorbar(img, ax=ax, format='%+2.0f dB')
|
|
151
|
+
cbar.set_label('功率 (dB)')
|
|
152
|
+
|
|
153
|
+
plt.tight_layout()
|
|
154
|
+
return fig
|
|
155
|
+
|
|
156
|
+
def plot_spectrum(self, audio: np.ndarray, title: str = "频谱",
|
|
157
|
+
log_scale: bool = True, ax: Optional[plt.Axes] = None) -> plt.Figure:
|
|
158
|
+
"""
|
|
159
|
+
绘制频谱
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
audio: 音频信号
|
|
163
|
+
title: 图标题
|
|
164
|
+
log_scale: 是否使用对数刻度
|
|
165
|
+
ax: matplotlib轴对象
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
图形对象
|
|
169
|
+
"""
|
|
170
|
+
if ax is None:
|
|
171
|
+
fig, ax = plt.subplots(figsize=self.figsize)
|
|
172
|
+
else:
|
|
173
|
+
fig = ax.figure
|
|
174
|
+
|
|
175
|
+
# 计算FFT
|
|
176
|
+
fft_data = np.fft.fft(audio)
|
|
177
|
+
magnitude = np.abs(fft_data)
|
|
178
|
+
freqs = np.fft.fftfreq(len(audio), 1/self.sr)
|
|
179
|
+
|
|
180
|
+
# 只取正频率部分
|
|
181
|
+
positive_idx = freqs >= 0
|
|
182
|
+
freqs = freqs[positive_idx]
|
|
183
|
+
magnitude = magnitude[positive_idx]
|
|
184
|
+
|
|
185
|
+
if log_scale:
|
|
186
|
+
magnitude_db = 20 * np.log10(magnitude + 1e-10)
|
|
187
|
+
ax.plot(freqs, magnitude_db)
|
|
188
|
+
ax.set_ylabel('幅度 (dB)')
|
|
189
|
+
else:
|
|
190
|
+
ax.plot(freqs, magnitude)
|
|
191
|
+
ax.set_ylabel('幅度')
|
|
192
|
+
|
|
193
|
+
ax.set_xlabel('频率 (Hz)')
|
|
194
|
+
ax.set_title(title)
|
|
195
|
+
ax.grid(True, alpha=0.3)
|
|
196
|
+
|
|
197
|
+
plt.tight_layout()
|
|
198
|
+
return fig
|
|
199
|
+
|
|
200
|
+
def plot_features_comparison(self, features_dict: Dict[str, np.ndarray],
|
|
201
|
+
title: str = "特征对比") -> plt.Figure:
|
|
202
|
+
"""
|
|
203
|
+
绘制多个特征的对比图
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
features_dict: 特征字典 {特征名: 特征值数组}
|
|
207
|
+
title: 图标题
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
图形对象
|
|
211
|
+
"""
|
|
212
|
+
n_features = len(features_dict)
|
|
213
|
+
fig, axes = plt.subplots(n_features, 1, figsize=(self.figsize[0], self.figsize[1] * n_features / 2))
|
|
214
|
+
|
|
215
|
+
if n_features == 1:
|
|
216
|
+
axes = [axes]
|
|
217
|
+
|
|
218
|
+
for i, (feature_name, feature_values) in enumerate(features_dict.items()):
|
|
219
|
+
if len(feature_values.shape) == 1:
|
|
220
|
+
# 一维特征
|
|
221
|
+
time_axis = np.linspace(0, len(feature_values) / (self.sr / 512), len(feature_values))
|
|
222
|
+
axes[i].plot(time_axis, feature_values)
|
|
223
|
+
axes[i].set_ylabel(feature_name)
|
|
224
|
+
else:
|
|
225
|
+
# 二维特征(如MFCC)
|
|
226
|
+
img = axes[i].imshow(feature_values, aspect='auto', origin='lower')
|
|
227
|
+
axes[i].set_ylabel(feature_name)
|
|
228
|
+
plt.colorbar(img, ax=axes[i])
|
|
229
|
+
|
|
230
|
+
axes[i].set_title(f'{feature_name} 特征')
|
|
231
|
+
axes[i].grid(True, alpha=0.3)
|
|
232
|
+
|
|
233
|
+
axes[-1].set_xlabel('时间 (s)')
|
|
234
|
+
plt.suptitle(title)
|
|
235
|
+
plt.tight_layout()
|
|
236
|
+
return fig
|
|
237
|
+
|
|
238
|
+
def plot_statistics_distribution(self, stats_dict: Dict[str, List[float]],
|
|
239
|
+
title: str = "统计分布图") -> plt.Figure:
|
|
240
|
+
"""
|
|
241
|
+
绘制统计分布图
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
stats_dict: 统计数据字典
|
|
245
|
+
title: 图标题
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
图形对象
|
|
249
|
+
"""
|
|
250
|
+
n_stats = len(stats_dict)
|
|
251
|
+
fig, axes = plt.subplots(2, (n_stats + 1) // 2, figsize=(self.figsize[0], self.figsize[1]))
|
|
252
|
+
|
|
253
|
+
if n_stats == 1:
|
|
254
|
+
axes = [axes]
|
|
255
|
+
elif n_stats == 2:
|
|
256
|
+
axes = axes.flatten()
|
|
257
|
+
else:
|
|
258
|
+
axes = axes.flatten()
|
|
259
|
+
|
|
260
|
+
for i, (stat_name, values) in enumerate(stats_dict.items()):
|
|
261
|
+
if i >= len(axes):
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
# 绘制直方图和KDE
|
|
265
|
+
axes[i].hist(values, bins=30, alpha=0.7, density=True, color='skyblue')
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
sns.kdeplot(values, ax=axes[i], color='red')
|
|
269
|
+
except:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
axes[i].set_title(f'{stat_name} 分布')
|
|
273
|
+
axes[i].set_xlabel(stat_name)
|
|
274
|
+
axes[i].set_ylabel('密度')
|
|
275
|
+
axes[i].grid(True, alpha=0.3)
|
|
276
|
+
|
|
277
|
+
# 隐藏未使用的子图
|
|
278
|
+
for j in range(i + 1, len(axes)):
|
|
279
|
+
axes[j].set_visible(False)
|
|
280
|
+
|
|
281
|
+
plt.suptitle(title)
|
|
282
|
+
plt.tight_layout()
|
|
283
|
+
return fig
|
|
284
|
+
|
|
285
|
+
def plot_rms_distribution(self, rms_values: List[float],
|
|
286
|
+
title: str = "RMS分布图") -> plt.Figure:
|
|
287
|
+
"""
|
|
288
|
+
绘制RMS分布图
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
rms_values: RMS值列表
|
|
292
|
+
title: 图标题
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
图形对象
|
|
296
|
+
"""
|
|
297
|
+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=self.figsize)
|
|
298
|
+
|
|
299
|
+
# 线性尺度分布
|
|
300
|
+
ax1.hist(rms_values, bins=50, alpha=0.7, color='lightblue', edgecolor='black')
|
|
301
|
+
ax1.set_xlabel('RMS 幅度')
|
|
302
|
+
ax1.set_ylabel('频次')
|
|
303
|
+
ax1.set_title('RMS 线性分布')
|
|
304
|
+
ax1.grid(True, alpha=0.3)
|
|
305
|
+
|
|
306
|
+
# 对数尺度分布
|
|
307
|
+
rms_db = [20 * np.log10(rms + 1e-10) for rms in rms_values]
|
|
308
|
+
ax2.hist(rms_db, bins=50, alpha=0.7, color='lightgreen', edgecolor='black')
|
|
309
|
+
ax2.set_xlabel('RMS (dB)')
|
|
310
|
+
ax2.set_ylabel('频次')
|
|
311
|
+
ax2.set_title('RMS 对数分布')
|
|
312
|
+
ax2.grid(True, alpha=0.3)
|
|
313
|
+
|
|
314
|
+
plt.suptitle(title)
|
|
315
|
+
plt.tight_layout()
|
|
316
|
+
return fig
|
|
317
|
+
|
|
318
|
+
def plot_audio_comparison(self, audio1: np.ndarray, audio2: np.ndarray,
|
|
319
|
+
labels: List[str] = None, title: str = "音频对比") -> plt.Figure:
|
|
320
|
+
"""
|
|
321
|
+
绘制两个音频的对比图
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
audio1: 第一个音频
|
|
325
|
+
audio2: 第二个音频
|
|
326
|
+
labels: 标签列表
|
|
327
|
+
title: 图标题
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
图形对象
|
|
331
|
+
"""
|
|
332
|
+
if labels is None:
|
|
333
|
+
labels = ['音频1', '音频2']
|
|
334
|
+
|
|
335
|
+
fig, axes = plt.subplots(3, 2, figsize=(self.figsize[0], self.figsize[1] * 1.5))
|
|
336
|
+
|
|
337
|
+
# 时域波形对比
|
|
338
|
+
time1 = np.linspace(0, len(audio1) / self.sr, len(audio1))
|
|
339
|
+
time2 = np.linspace(0, len(audio2) / self.sr, len(audio2))
|
|
340
|
+
|
|
341
|
+
axes[0, 0].plot(time1, audio1, alpha=0.8)
|
|
342
|
+
axes[0, 0].set_title(f'{labels[0]} - 波形')
|
|
343
|
+
axes[0, 0].set_xlabel('时间 (s)')
|
|
344
|
+
axes[0, 0].set_ylabel('幅度')
|
|
345
|
+
axes[0, 0].grid(True, alpha=0.3)
|
|
346
|
+
|
|
347
|
+
axes[0, 1].plot(time2, audio2, alpha=0.8, color='orange')
|
|
348
|
+
axes[0, 1].set_title(f'{labels[1]} - 波形')
|
|
349
|
+
axes[0, 1].set_xlabel('时间 (s)')
|
|
350
|
+
axes[0, 1].set_ylabel('幅度')
|
|
351
|
+
axes[0, 1].grid(True, alpha=0.3)
|
|
352
|
+
|
|
353
|
+
# 频谱对比
|
|
354
|
+
self.plot_spectrum(audio1, f'{labels[0]} - 频谱', ax=axes[1, 0])
|
|
355
|
+
self.plot_spectrum(audio2, f'{labels[1]} - 频谱', ax=axes[1, 1])
|
|
356
|
+
|
|
357
|
+
# 频谱图对比
|
|
358
|
+
self.plot_spectrogram(audio1, f'{labels[0]} - 频谱图', ax=axes[2, 0])
|
|
359
|
+
self.plot_spectrogram(audio2, f'{labels[1]} - 频谱图', ax=axes[2, 1])
|
|
360
|
+
|
|
361
|
+
plt.suptitle(title)
|
|
362
|
+
plt.tight_layout()
|
|
363
|
+
return fig
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def plot_dataset_overview(file_paths: List[str], max_files: int = 10,
|
|
367
|
+
sr: int = 22050) -> plt.Figure:
|
|
368
|
+
"""
|
|
369
|
+
绘制数据集概览
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
file_paths: 音频文件路径列表
|
|
373
|
+
max_files: 最大显示文件数
|
|
374
|
+
sr: 采样率
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
图形对象
|
|
378
|
+
"""
|
|
379
|
+
visualizer = AudioVisualizer(sr=sr)
|
|
380
|
+
|
|
381
|
+
# 限制文件数量
|
|
382
|
+
selected_files = file_paths[:max_files]
|
|
383
|
+
|
|
384
|
+
fig, axes = plt.subplots(len(selected_files), 2,
|
|
385
|
+
figsize=(15, 3 * len(selected_files)))
|
|
386
|
+
|
|
387
|
+
if len(selected_files) == 1:
|
|
388
|
+
axes = axes.reshape(1, -1)
|
|
389
|
+
|
|
390
|
+
for i, file_path in enumerate(selected_files):
|
|
391
|
+
try:
|
|
392
|
+
audio, _ = librosa.load(file_path, sr=sr)
|
|
393
|
+
|
|
394
|
+
# 波形图
|
|
395
|
+
visualizer.plot_waveform(audio, f'文件 {i+1}: 波形', ax=axes[i, 0])
|
|
396
|
+
|
|
397
|
+
# 频谱图
|
|
398
|
+
visualizer.plot_spectrogram(audio, f'文件 {i+1}: 频谱图', ax=axes[i, 1])
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
axes[i, 0].text(0.5, 0.5, f'加载失败: {str(e)}',
|
|
402
|
+
ha='center', va='center', transform=axes[i, 0].transAxes)
|
|
403
|
+
axes[i, 1].text(0.5, 0.5, f'加载失败: {str(e)}',
|
|
404
|
+
ha='center', va='center', transform=axes[i, 1].transAxes)
|
|
405
|
+
|
|
406
|
+
plt.suptitle('数据集概览')
|
|
407
|
+
plt.tight_layout()
|
|
408
|
+
return fig
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def create_analysis_dashboard(audio: np.ndarray, sr: int = 22050) -> plt.Figure:
|
|
412
|
+
"""
|
|
413
|
+
创建音频分析仪表板
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
audio: 音频信号
|
|
417
|
+
sr: 采样率
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
仪表板图形对象
|
|
421
|
+
"""
|
|
422
|
+
visualizer = AudioVisualizer(sr=sr)
|
|
423
|
+
|
|
424
|
+
fig = plt.figure(figsize=(16, 12))
|
|
425
|
+
|
|
426
|
+
# 创建网格布局
|
|
427
|
+
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)
|
|
428
|
+
|
|
429
|
+
# 时域波形
|
|
430
|
+
ax1 = fig.add_subplot(gs[0, :])
|
|
431
|
+
visualizer.plot_waveform(audio, "时域波形", ax=ax1)
|
|
432
|
+
|
|
433
|
+
# 频谱图
|
|
434
|
+
ax2 = fig.add_subplot(gs[1, :2])
|
|
435
|
+
visualizer.plot_spectrogram(audio, "频谱图", ax=ax2)
|
|
436
|
+
|
|
437
|
+
# 频谱
|
|
438
|
+
ax3 = fig.add_subplot(gs[1, 2])
|
|
439
|
+
visualizer.plot_spectrum(audio, "频谱", ax=ax3)
|
|
440
|
+
|
|
441
|
+
# 梅尔频谱图
|
|
442
|
+
ax4 = fig.add_subplot(gs[2, :2])
|
|
443
|
+
visualizer.plot_mel_spectrogram(audio, "梅尔频谱图", ax=ax4)
|
|
444
|
+
|
|
445
|
+
# 特征统计
|
|
446
|
+
ax5 = fig.add_subplot(gs[2, 2])
|
|
447
|
+
|
|
448
|
+
# 计算基本统计
|
|
449
|
+
duration = len(audio) / sr
|
|
450
|
+
max_amp = np.max(np.abs(audio))
|
|
451
|
+
rms_amp = np.sqrt(np.mean(audio ** 2))
|
|
452
|
+
|
|
453
|
+
stats_text = f"""音频统计信息:
|
|
454
|
+
时长: {duration:.2f}s
|
|
455
|
+
最大幅度: {max_amp:.4f}
|
|
456
|
+
RMS: {rms_amp:.4f}
|
|
457
|
+
RMS (dB): {20*np.log10(rms_amp):.2f}
|
|
458
|
+
采样率: {sr} Hz
|
|
459
|
+
样本数: {len(audio)}
|
|
460
|
+
"""
|
|
461
|
+
|
|
462
|
+
ax5.text(0.1, 0.5, stats_text, transform=ax5.transAxes,
|
|
463
|
+
fontsize=10, verticalalignment='center')
|
|
464
|
+
ax5.set_title("统计信息")
|
|
465
|
+
ax5.axis('off')
|
|
466
|
+
|
|
467
|
+
plt.suptitle("音频分析仪表板", fontsize=16)
|
|
468
|
+
return fig
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Audio EQ Matching Scripts Collection
|
|
2
|
+
|
|
3
|
+
本文件夹包含多种不同算法的音频EQ匹配脚本, 可以自动分析两个音频文件的频谱差异并生成EQ补偿参数。
|
|
4
|
+
|
|
5
|
+
## 📁 脚本概览
|
|
6
|
+
|
|
7
|
+
### 🧬 遗传算法系列
|
|
8
|
+
|
|
9
|
+
#### 1. `auto_eq_ga_basic.py` - 基础遗传算法
|
|
10
|
+
- **算法**: 遗传算法 (Genetic Algorithm, GA)
|
|
11
|
+
- **特点**:
|
|
12
|
+
- 使用DEAP库实现
|
|
13
|
+
- 代码结构简洁, 易于理解
|
|
14
|
+
- 适合学习和快速原型开发
|
|
15
|
+
- **优势**: 实现简单, 运行稳定
|
|
16
|
+
- **适用场景**: 初学者学习、快速测试
|
|
17
|
+
|
|
18
|
+
#### 2. `auto_eq_ga_advanced.py` - 高级遗传算法
|
|
19
|
+
- **算法**: 增强版遗传算法
|
|
20
|
+
- **特点**:
|
|
21
|
+
- 面向对象设计
|
|
22
|
+
- 包含日志记录系统
|
|
23
|
+
- 支持检查点保存/恢复
|
|
24
|
+
- 早停机制防止过拟合
|
|
25
|
+
- 并行处理支持
|
|
26
|
+
- 详细的统计信息输出
|
|
27
|
+
- **优势**: 功能完整, 适合生产环境
|
|
28
|
+
- **适用场景**: 正式项目、批量处理、研究工作
|
|
29
|
+
|
|
30
|
+
### 🔄 差分进化算法
|
|
31
|
+
|
|
32
|
+
#### 3. `auto_eq_de.py` - 差分进化优化
|
|
33
|
+
- **算法**: 差分进化算法 (Differential Evolution, DE)
|
|
34
|
+
- **特点**:
|
|
35
|
+
- 使用scipy.optimize.differential_evolution
|
|
36
|
+
- 全局优化算法, 收敛性好
|
|
37
|
+
- 参数调整相对简单
|
|
38
|
+
- **优势**: 优化效果稳定, 适合连续参数优化
|
|
39
|
+
- **适用场景**: 需要高精度匹配、对收敛性要求高的场合
|
|
40
|
+
|
|
41
|
+
### 📊 频谱直接补偿
|
|
42
|
+
|
|
43
|
+
#### 4. `auto_eq_spectral_direct.py` - 频谱直接匹配
|
|
44
|
+
- **算法**: 基于STFT的频谱分析
|
|
45
|
+
- **特点**:
|
|
46
|
+
- 直接计算频谱差异
|
|
47
|
+
- 无需优化算法迭代
|
|
48
|
+
- 运行速度最快
|
|
49
|
+
- 使用librosa进行频谱分析
|
|
50
|
+
- **优势**: 计算速度快, 实现直观
|
|
51
|
+
- **适用场景**: 简单频谱匹配、实时处理需求
|
|
52
|
+
|
|
53
|
+
## 🔧 共同依赖
|
|
54
|
+
|
|
55
|
+
所有脚本都需要以下依赖:
|
|
56
|
+
```bash
|
|
57
|
+
pip install numpy scipy soundfile matplotlib
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
额外依赖:
|
|
61
|
+
- `auto_eq_ga_*.py`: `pip install deap`
|
|
62
|
+
- `auto_eq_spectral_direct.py`: `pip install librosa`
|
|
63
|
+
|
|
64
|
+
## 📖 使用方法
|
|
65
|
+
|
|
66
|
+
### 基本用法
|
|
67
|
+
|
|
68
|
+
1. **准备音频文件**:
|
|
69
|
+
- 源音频文件 (reference)
|
|
70
|
+
- 目标音频文件 (target)
|
|
71
|
+
- 确保两个文件采样率一致
|
|
72
|
+
|
|
73
|
+
2. **修改配置参数**:
|
|
74
|
+
```python
|
|
75
|
+
SOURCE_AUDIO_PATH = "path/to/source.wav"
|
|
76
|
+
TARGET_AUDIO_PATH = "path/to/target.wav"
|
|
77
|
+
OUTPUT_MATCHED_AUDIO_PATH = "path/to/output.wav"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
3. **运行脚本**:
|
|
81
|
+
```bash
|
|
82
|
+
python auto_eq_ga_basic.py # 基础遗传算法
|
|
83
|
+
python auto_eq_de.py # 差分进化算法
|
|
84
|
+
python auto_eq_ga_advanced.py # 高级遗传算法
|
|
85
|
+
python auto_eq_spectral_direct.py # 频谱直接匹配
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 输出结果
|
|
89
|
+
|
|
90
|
+
- **EQ参数**: 滤波器类型、频率、Q值、增益等参数
|
|
91
|
+
- **匹配音频**: 应用EQ后的音频文件
|
|
92
|
+
- **对比图表**: 频谱匹配效果可视化
|
|
93
|
+
|
|
94
|
+
## ⚙️ 参数调优指南
|
|
95
|
+
|
|
96
|
+
### 遗传算法参数 (GA系列)
|
|
97
|
+
```python
|
|
98
|
+
POPULATION_SIZE = 200 # 种群大小, 影响搜索广度
|
|
99
|
+
MAX_GENERATIONS = 150 # 最大迭代数, 影响搜索深度
|
|
100
|
+
MAX_FILTERS = 10 # 最大滤波器数量
|
|
101
|
+
COMPLEXITY_PENALTY_FACTOR = 0.01 # 复杂度惩罚因子
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 差分进化参数 (DE)
|
|
105
|
+
```python
|
|
106
|
+
maxiter = 300 # 最大迭代数
|
|
107
|
+
popsize = 15 # 种群大小倍数
|
|
108
|
+
atol = 1e-4 # 收敛阈值
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 频谱分析参数
|
|
112
|
+
```python
|
|
113
|
+
FFT_SIZE = 512 # FFT窗口大小
|
|
114
|
+
SAMPLE_RATE = 16000 # 采样率
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 📊 算法对比
|
|
118
|
+
|
|
119
|
+
| 算法 | 收敛速度 | 精度 | 复杂度 | 稳定性 | 适用场景 |
|
|
120
|
+
|------|---------|------|--------|---------|----------|
|
|
121
|
+
| GA Basic | 中等 | 中等 | 低 | 高 | 学习、原型 |
|
|
122
|
+
| GA Advanced | 中等 | 高 | 高 | 很高 | 生产环境 |
|
|
123
|
+
| DE | 快 | 高 | 中等 | 高 | 精确匹配 |
|
|
124
|
+
| Spectral Direct | 很快 | 中等 | 很低 | 中等 | 快速匹配 |
|
|
125
|
+
|
|
126
|
+
## 🎛️ 支持的滤波器类型
|
|
127
|
+
|
|
128
|
+
- **Peak Filter** (峰值滤波器): 增强或衰减特定频率
|
|
129
|
+
- **Low Shelf** (低频搁架): 影响低频部分
|
|
130
|
+
- **High Shelf** (高频搁架): 影响高频部分
|
|
131
|
+
|
|
132
|
+
## 📋 注意事项
|
|
133
|
+
|
|
134
|
+
1. **音频格式**: 建议使用WAV格式, 确保无损质量
|
|
135
|
+
2. **采样率**: 源音频和目标音频必须采样率一致
|
|
136
|
+
3. **单声道**: 目前脚本主要支持单声道音频
|
|
137
|
+
4. **参数范围**:
|
|
138
|
+
- 频率范围: 20Hz - Nyquist频率
|
|
139
|
+
- Q值范围: 0.3 - 10.0 (Peak), 0.3 - 2.0 (Shelf)
|
|
140
|
+
- 增益范围: -25dB - +25dB
|
|
141
|
+
|
|
142
|
+
## 🔍 故障排除
|
|
143
|
+
|
|
144
|
+
### 常见问题
|
|
145
|
+
|
|
146
|
+
1. **收敛困难**: 增加迭代数或调整惩罚因子
|
|
147
|
+
2. **过度拟合**: 增加复杂度惩罚或减少最大滤波器数
|
|
148
|
+
3. **运行缓慢**: 减少种群大小或使用更快的算法
|
|
149
|
+
4. **匹配精度不够**: 增加滤波器数量或使用高级算法
|
|
150
|
+
|
|
151
|
+
### 性能优化建议
|
|
152
|
+
|
|
153
|
+
- 对于快速测试: 使用 `auto_eq_spectral_direct.py`
|
|
154
|
+
- 对于高质量结果: 使用 `auto_eq_ga_advanced.py`
|
|
155
|
+
- 对于平衡性能: 使用 `auto_eq_de.py`
|
|
156
|
+
|
|
157
|
+
## 📝 作者信息
|
|
158
|
+
|
|
159
|
+
- **作者**: 凌逆战 | Never
|
|
160
|
+
- **更新日期**: 2025-08-05
|
|
161
|
+
- **项目用途**: 音频EQ自动匹配研究
|
|
162
|
+
|
|
163
|
+
## 📄 许可证
|
|
164
|
+
|
|
165
|
+
本项目仅供学习和研究使用。
|