簡體   English   中英

Python中的自相關代碼會產生錯誤(吉他音高檢測)

[英]Autocorrelation code in Python produces errors (guitar pitch detection)

該鏈接提供了基於自相關的基音檢測算法的代碼。 我用它來檢測簡單吉他旋律的音高。

通常,它會產生很好的結果。 例如,對於旋律C4,C#4,D4,D#4,E4,它輸出:

262.743653536
272.144441273
290.826273006
310.431336809
327.094621169

與正確的音符相關。

但是,在某些情況下,例如音頻文件(E4,F4,F#4,G4,G#4,A4,A#4,B4),它會產生錯誤:

325.861452246
13381.6439242
367.518651703
391.479384923
414.604661221
218.345286173
466.503751322
244.994090035

更具體地說,這里存在三個錯誤:錯誤地檢測到13381Hz而不是F4(〜350Hz)(奇怪的錯誤),還有218Hz而不是A4(440Hz)和244Hz而不是B4(〜493Hz),它們是八度音階錯誤。

我假設這兩個錯誤是由不同的原因引起的? 這是代碼:

slices = segment_signal(y, sr)
for segment in slices:
  pitch = freq_from_autocorr(segment, sr)
  print pitch

def segment_signal(y, sr, onset_frames=None, offset=0.1):
  if (onset_frames == None):
    onset_frames = remove_dense_onsets(librosa.onset.onset_detect(y=y, sr=sr))

  offset_samples = int(librosa.time_to_samples(offset, sr))

  print onset_frames

  slices = np.array([y[i : i + offset_samples] for i
    in librosa.frames_to_samples(onset_frames)])

  return slices

您可以在上面的第一個鏈接中看到freq_from_autocorr函數。

我唯一更改的想法是此行:

corr = corr[len(corr)/2:]

我已替換為:

corr = corr[int(len(corr)/2):]

更新

我注意到我使用的offset最小(用於檢測每個音高的信號段最小),我得到的高頻(10000+ Hz)錯誤也更多。

具體來說,我注意到在這些情況下(10000+ Hz)不同的部分是i_peak值的計算。 如果沒有錯誤,則在50-150范圍內;如果出現錯誤,則為3-5。

您鏈接的代碼段中的自相關函數不是特別可靠。 為了獲得正確的結果,它需要將第一個峰定位在自相關曲線的左側。 其他開發人員使用的方法(調用numpy.argmax()函數)並不總是找到正確的值。

我已經使用peakutils包實現了一個稍微健壯的版本。 我也不保證它的魯棒性,但是無論如何,它都比以前使用的freq_from_autocorr()函數的版本更好。

下面列出了我的示例解決方案:

import librosa
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import fftconvolve
from pprint import pprint
import peakutils

def freq_from_autocorr(signal, fs):
    # Calculate autocorrelation (same thing as convolution, but with one input
    # reversed in time), and throw away the negative lags
    signal -= np.mean(signal)  # Remove DC offset
    corr = fftconvolve(signal, signal[::-1], mode='full')
    corr = corr[len(corr)//2:]

    # Find the first peak on the left
    i_peak = peakutils.indexes(corr, thres=0.8, min_dist=5)[0]
    i_interp = parabolic(corr, i_peak)[0]

    return fs / i_interp, corr, i_interp

def parabolic(f, x):
    """
    Quadratic interpolation for estimating the true position of an
    inter-sample maximum when nearby samples are known.

    f is a vector and x is an index for that vector.

    Returns (vx, vy), the coordinates of the vertex of a parabola that goes
    through point x and its two neighbors.

    Example:
    Defining a vector f with a local maximum at index 3 (= 6), find local
    maximum if points 2, 3, and 4 actually defined a parabola.

    In [3]: f = [2, 3, 1, 6, 4, 2, 3, 1]

    In [4]: parabolic(f, argmax(f))
    Out[4]: (3.2142857142857144, 6.1607142857142856)
    """
    xv = 1/2. * (f[x-1] - f[x+1]) / (f[x-1] - 2 * f[x] + f[x+1]) + x
    yv = f[x] - 1/4. * (f[x-1] - f[x+1]) * (xv - x)
    return (xv, yv)

# Time window after initial onset (in units of seconds)
window = 0.1

# Open the file and obtain the sampling rate
y, sr = librosa.core.load("./Vocaroo_s1A26VqpKgT0.mp3")
idx = np.arange(len(y))

# Set the window size in terms of number of samples
winsamp = int(window * sr)

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

fqlist = [] # List of estimated frequencies, one per note
crlist = [] # List of autocorrelation arrays, one array per note
iplist = [] # List of peak interpolated peak indices, one per note
for tm in onstm:
    startidx = int(tm * sr)
    freq, corr, ip = freq_from_autocorr(y[startidx:startidx+winsamp], sr)
    fqlist.append(freq)
    crlist.append(corr)
    iplist.append(ip)    

pprint(fqlist)

# Choose which notes to plot (it's set to show all 8 notes in this case)
plidx = [0, 1, 2, 3, 4, 5, 6, 7]

# Plot amplitude curves of all notes in the plidx list 
fgwin = plt.figure(figsize=[8, 10])
fgwin.subplots_adjust(bottom=0.0, top=0.98, hspace=0.3)
axwin = []
ii = 1
for tm in onstm[plidx]:
    axwin.append(fgwin.add_subplot(len(plidx)+1, 1, ii))
    startidx = int(tm * sr)
    axwin[-1].plot(np.arange(startidx, startidx+winsamp), y[startidx:startidx+winsamp])
    ii += 1
axwin[-1].set_xlabel('Sample ID Number', fontsize=18)
fgwin.show()

# Plot autocorrelation function of all notes in the plidx list
fgcorr = plt.figure(figsize=[8,10])
fgcorr.subplots_adjust(bottom=0.0, top=0.98, hspace=0.3)
axcorr = []
ii = 1
for cr, ip in zip([crlist[ii] for ii in plidx], [iplist[ij] for ij in plidx]):
    if ii == 1:
        shax = None
    else:
        shax = axcorr[0]
    axcorr.append(fgcorr.add_subplot(len(plidx)+1, 1, ii, sharex=shax))
    axcorr[-1].plot(np.arange(500), cr[0:500])
    # Plot the location of the leftmost peak
    axcorr[-1].axvline(ip, color='r')
    ii += 1
axcorr[-1].set_xlabel('Time Lag Index (Zoomed)', fontsize=18)
fgcorr.show()

打印輸出如下:

In [1]: %run autocorr.py
[325.81996740236065,
 346.43374761017725,
 367.12435233192753,
 390.17291696559079,
 412.9358117076161,
 436.04054933498134,
 465.38986619237039,
 490.34120132405866]

我的代碼示例產生的第一幅圖描繪了每個檢測到的開始時間之后的0.1秒的幅度曲線:

吉他音符振幅

該代碼產生的第二個圖顯示了自相關曲線,該自相關曲線是在freq_from_autocorr()函數內部計算的。 垂直的紅線描繪了每條曲線左邊第一個峰的位置,由peakutils軟件包估算。 其他開發人員使用的方法對於其中一些紅線獲得了不正確的結果; 這就是為什么他的函數版本偶爾返回錯誤的頻率的原因。

吉他音符自相關曲線

我的建議是在其他唱片上測試freq_from_autocorr()函數的修訂版,看看是否可以找到更具挑戰性的示例,即使改進后的版本仍會給出不正確的結果,然后發揮創造力並嘗試開發出更可靠的峰值尋找永遠不會錯火的算法。

自相關方法並不總是正確的。 您可能想要實現一種更復雜的方法,例如YIN:

http://audition.ens.fr/adc/pdf/2002_JASA_YIN.pdf

或MPM:

http://www.cs.otago.ac.nz/tartini/papers/A_Smarter_Way_to_Find_Pitch.pdf

以上兩篇論文都是不錯的讀物。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM