10

我正在使用Librosa库进行音高和起始检测。具体来说,我正在使用onset_detectand piptrack

这是我的代码:

def detect_pitch(y, sr, onset_offset=5, fmin=75, fmax=1400):
  y = highpass_filter(y, sr)

  onset_frames = librosa.onset.onset_detect(y=y, sr=sr)
  pitches, magnitudes = librosa.piptrack(y=y, sr=sr, fmin=fmin, fmax=fmax)

  notes = []

  for i in range(0, len(onset_frames)):
    onset = onset_frames[i] + onset_offset
    index = magnitudes[:, onset].argmax()
    pitch = pitches[index, onset]
    if (pitch != 0):
      notes.append(librosa.hz_to_note(pitch))

  return notes

def highpass_filter(y, sr):
  filter_stop_freq = 70  # Hz
  filter_pass_freq = 100  # Hz
  filter_order = 1001

  # High-pass filter
  nyquist_rate = sr / 2.
  desired = (0, 0, 1, 1)
  bands = (0, filter_stop_freq, filter_pass_freq, nyquist_rate)
  filter_coefs = signal.firls(filter_order, bands, desired, nyq=nyquist_rate)

  # Apply high-pass filter
  filtered_audio = signal.filtfilt(filter_coefs, [1], y)
  return filtered_audio

在录音棚中录制的吉他音频样本上运行此程序时,因此样本没有噪音(像这样),我在这两个功能中都得到了非常好的结果。开始时间是正确的,频率几乎总是正确的(有时会出现一些八度音阶错误)。

然而,当我尝试用我便宜的麦克风录制自己的吉他声音时,出现了一个大问题。我得到带有噪音的音频文件,例如this。该onset_detect算法感到困惑,并认为噪声包含起始时间。因此,我得到了非常糟糕的结果。即使我的音频文件由一个音符组成,我也会有很多次发作。

这是两个波形。第一个是在录音室录制的 B3 音符的吉他样本,而第二个是我录制的 E2 音符。

工作室 记录

第一个的结果是正确的 B3(检测到一个起始时间)。第二个的结果是一个包含7 个元素的数组,这意味着检测到 7 个起始时间,而不是 1 个!其中一个元素是正确的开始时间,其他元素只是噪声部分的随机峰值。

另一个例子是这个包含音符 B3、C4、D4、E4 的音频文件:

b3-e4

如您所见,噪声很清晰,我的高通滤波器没有帮助(这是应用滤波器后的波形)。

我认为这是一个噪音问题,因为这些文件之间的区别就在那里。如果是,我能做些什么来减少它?我尝试过使用高通滤波器,但没有任何变化。

4

2 回答 2

5

我有三个观察要分享。

首先,经过一番尝试,我得出结论,开始检测算法看起来好像它可能被设计为自动重新调整自己的操作,以便在任何给定时刻考虑本地背景噪声。这可能是为了使其能够以与在强音部分相同的可能性检测到弱音部分的开始时间。不幸的是,算法往往会触发来自廉价麦克风的背景噪声——起始检测算法老实说,它认为它只是在听弱音音乐。

第二个观察结果是,在您记录的示例中,大约前 2200 个样本(大约是前 0.1 秒)有点不稳定,因为在那个短暂的初始间隔内噪声实际上几乎为零。尝试在起点放大波形,你会明白我的意思。不幸的是,吉他演奏的开始在噪音开始之后如此之快(大约在样本 3000 左右),以至于算法无法独立解决这两个问题,而是简单地将两者合并为一个开始事件,该事件也开始于大约 0.1 秒早期的。因此,我大致删除了前 2240 个样本以“规范化”文件(不过我不认为这是作弊;它

我的第三个观察结果是,基于频率的过滤仅在噪声和音乐实际上处于稍微不同的频段时才有效。在这种情况下这可能是真的,但我认为你还没有证明它。因此,我选择尝试不同的方法,而不是基于频率的过滤:阈值。我使用了录音的最后 3 秒,没有吉他演奏,以估计整个录音中的典型背景噪声水平,以 RMS 能量为单位,然后我使用该中值设置最小能量阈值,该阈值被计算为安全地位于中位数之上。只有当 RMS 能量高于阈值时,检测器返回的起始事件才会被接受为“有效”。

示例脚本如下所示:

import librosa
import numpy as np
import matplotlib.pyplot as plt

# I played around with this but ultimately kept the default value
hoplen=512

y, sr = librosa.core.load("./Vocaroo_s07Dx8dWGAR0.mp3")
# Note that the first ~2240 samples (0.1 seconds) are anomalously low noise,
# so cut out this section from processing
start = 2240
y = y[start:]
idx = np.arange(len(y))

# Calcualte the onset frames in the usual way
onset_frames = librosa.onset.onset_detect(y=y, sr=sr, hop_length=hoplen)
onstm = librosa.frames_to_time(onset_frames, sr=sr, hop_length=hoplen)

# Calculate RMS energy per frame.  I shortened the frame length from the
# default value in order to avoid ending up with too much smoothing
rmse = librosa.feature.rmse(y=y, frame_length=512, hop_length=hoplen)[0,]
envtm = librosa.frames_to_time(np.arange(len(rmse)), sr=sr, hop_length=hoplen)
# Use final 3 seconds of recording in order to estimate median noise level
# and typical variation
noiseidx = [envtm > envtm[-1] - 3.0]
noisemedian = np.percentile(rmse[noiseidx], 50)
sigma = np.percentile(rmse[noiseidx], 84.1) - noisemedian
# Set the minimum RMS energy threshold that is needed in order to declare
# an "onset" event to be equal to 5 sigma above the median
threshold = noisemedian + 5*sigma
threshidx = [rmse > threshold]
# Choose the corrected onset times as only those which meet the RMS energy
# minimum threshold requirement
correctedonstm = onstm[[tm in envtm[threshidx] for tm in onstm]]

# Print both in units of actual time (seconds) and sample ID number
print(correctedonstm+start/sr)
print(correctedonstm*sr+start)

fg = plt.figure(figsize=[12, 8])

# Print the waveform together with onset times superimposed in red
ax1 = fg.add_subplot(2,1,1)
ax1.plot(idx+start, y)
for ii in correctedonstm*sr+start:
    ax1.axvline(ii, color='r')
ax1.set_ylabel('Amplitude', fontsize=16)

# Print the RMSE together with onset times superimposed in red
ax2 = fg.add_subplot(2,1,2, sharex=ax1)
ax2.plot(envtm*sr+start, rmse)
for ii in correctedonstm*sr+start:
    ax2.axvline(ii, color='r')
# Plot threshold value superimposed as a black dotted line
ax2.axhline(threshold, linestyle=':', color='k')
ax2.set_ylabel("RMSE", fontsize=16)
ax2.set_xlabel("Sample Number", fontsize=16)

fg.show()

打印输出如下所示:

In [1]: %run rosatest
[ 0.17124717  1.88952381  3.74712018  5.62793651]
[   3776.   41664.   82624.  124096.]

它产生的图如下所示: 具有阈值起始时间的噪声波形

于 2017-05-27T07:32:52.703 回答
0

您是否在治疗前进行了测试以使声音样本正常化?

在阅读onset_detect文档时,我们可以看到有很多可选参数,您是否已经尝试使用一些?

也许这个可选参数之一可以帮助您只保留好的参数(或至少限制开始时间返回数组的大小):

另请参阅您的代码更新以使用预先计算的起始信封:

def detect_pitch(y, sr, onset_offset=5, fmin=75, fmax=1400):
  y = highpass_filter(y, sr)

  o_env = librosa.onset.onset_strength(y, sr=sr)
  times = librosa.frames_to_time(np.arange(len(o_env)), sr=sr)

  onset_frames = librosa.onset.onset_detect(y=o_env, sr=sr)
  pitches, magnitudes = librosa.piptrack(y=y, sr=sr, fmin=fmin, fmax=fmax)

  notes = []

  for i in range(0, len(onset_frames)):
    onset = onset_frames[i] + onset_offset
    index = magnitudes[:, onset].argmax()
    pitch = pitches[index, onset]
    if (pitch != 0):
      notes.append(librosa.hz_to_note(pitch))

  return notes

def highpass_filter(y, sr):
  filter_stop_freq = 70  # Hz
  filter_pass_freq = 100  # Hz
  filter_order = 1001

  # High-pass filter
  nyquist_rate = sr / 2.
  desired = (0, 0, 1, 1)
  bands = (0, filter_stop_freq, filter_pass_freq, nyquist_rate)
  filter_coefs = signal.firls(filter_order, bands, desired, nyq=nyquist_rate)

  # Apply high-pass filter
  filtered_audio = signal.filtfilt(filter_coefs, [1], y)
  return filtered_audio

它工作得更好吗?

于 2017-05-26T13:59:47.497 回答