Python 如何在时域中计算基频f(0)?
我是DSP新手,尝试为音频文件的每个分段帧计算基频(Python 如何在时域中计算基频f(0)?,python,numpy,audio,scipy,signal-processing,Python,Numpy,Audio,Scipy,Signal Processing,我是DSP新手,尝试为音频文件的每个分段帧计算基频(f(0))。F0估算方法可分为三类: 基于时域信号的时间动力学 基于频域的频率结构,以及 混合方法 大多数示例都是基于频域的频率结构估计基频,我正在寻找基于时域信号的时间动力学 提供了一些信息,但我仍然不清楚如何在时域中计算它 我发现,这是迄今为止使用的代码: def freq_from_autocorr(sig, fs): """ Estimate frequency using autocorrelation "
f(0)
)。F0估算方法可分为三类:
- 基于时域信号的时间动力学李>
- 基于频域的频率结构,以及
- 混合方法
def freq_from_autocorr(sig, fs):
"""
Estimate frequency using autocorrelation
"""
# Calculate autocorrelation and throw away the negative lags
corr = correlate(sig, sig, mode='full')
corr = corr[len(corr)//2:]
# Find the first low point
d = diff(corr)
start = nonzero(d > 0)[0][0]
# Find the next peak after the low point (other than 0 lag). This bit is
# not reliable for long signals, due to the desired peak occurring between
# samples, and other peaks appearing higher.
# Should use a weighting function to de-emphasize the peaks at longer lags.
peak = argmax(corr[start:]) + start
px, py = parabolic(corr, peak)
return fs / px
如何在时域中进行估计
提前谢谢 这是一个正确的实现。不是很健壮,但确实有效。为了验证这一点,我们可以生成一个已知频率的信号,看看我们将得到什么结果:
import numpy as np
from scipy.io import wavfile
from scipy.signal import correlate, fftconvolve
from scipy.interpolate import interp1d
fs = 44100
frequency = 440
length = 0.01 # in seconds
t = np.linspace(0, length, int(fs * length))
y = np.sin(frequency * 2 * np.pi * t)
def parabolic(f, x):
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)
def freq_from_autocorr(sig, fs):
"""
Estimate frequency using autocorrelation
"""
corr = correlate(sig, sig, mode='full')
corr = corr[len(corr)//2:]
d = np.diff(corr)
start = np.nonzero(d > 0)[0][0]
peak = np.argmax(corr[start:]) + start
px, py = parabolic(corr, peak)
return fs / px
结果
从自动相关(y,fs)运行freq\u
得到~442.014 Hz
,大约0.45%的误差
奖金-我们可以提高
我们可以通过稍微增加编码使其更加精确和健壮:
def indexes(y, thres=0.3, min_dist=1, thres_abs=False):
"""Peak detection routine borrowed from
https://bitbucket.org/lucashnegri/peakutils/src/master/peakutils/peak.py
"""
if isinstance(y, np.ndarray) and np.issubdtype(y.dtype, np.unsignedinteger):
raise ValueError("y must be signed")
if not thres_abs:
thres = thres * (np.max(y) - np.min(y)) + np.min(y)
min_dist = int(min_dist)
# compute first order difference
dy = np.diff(y)
# propagate left and right values successively to fill all plateau pixels (0-value)
zeros, = np.where(dy == 0)
# check if the signal is totally flat
if len(zeros) == len(y) - 1:
return np.array([])
if len(zeros):
# compute first order difference of zero indexes
zeros_diff = np.diff(zeros)
# check when zeros are not chained together
zeros_diff_not_one, = np.add(np.where(zeros_diff != 1), 1)
# make an array of the chained zero indexes
zero_plateaus = np.split(zeros, zeros_diff_not_one)
# fix if leftmost value in dy is zero
if zero_plateaus[0][0] == 0:
dy[zero_plateaus[0]] = dy[zero_plateaus[0][-1] + 1]
zero_plateaus.pop(0)
# fix if rightmost value of dy is zero
if len(zero_plateaus) and zero_plateaus[-1][-1] == len(dy) - 1:
dy[zero_plateaus[-1]] = dy[zero_plateaus[-1][0] - 1]
zero_plateaus.pop(-1)
# for each chain of zero indexes
for plateau in zero_plateaus:
median = np.median(plateau)
# set leftmost values to leftmost non zero values
dy[plateau[plateau < median]] = dy[plateau[0] - 1]
# set rightmost and middle values to rightmost non zero values
dy[plateau[plateau >= median]] = dy[plateau[-1] + 1]
# find the peaks by using the first order difference
peaks = np.where(
(np.hstack([dy, 0.0]) < 0.0)
& (np.hstack([0.0, dy]) > 0.0)
& (np.greater(y, thres))
)[0]
# handle multiple peaks, respecting the minimum distance
if peaks.size > 1 and min_dist > 1:
highest = peaks[np.argsort(y[peaks])][::-1]
rem = np.ones(y.size, dtype=bool)
rem[peaks] = False
for peak in highest:
if not rem[peak]:
sl = slice(max(0, peak - min_dist), peak + min_dist + 1)
rem[sl] = True
rem[peak] = False
peaks = np.arange(y.size)[~rem]
return peaks
def freq_from_autocorr_improved(signal, fs):
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 = indexes(corr, thres=0.8, min_dist=5)[0]
i_interp = parabolic(corr, i_peak)[0]
return fs / i_interp, corr, i_interp
def索引(y,thres=0.3,min\u dist=1,thres\u abs=False):
“”“从中借用的峰值检测例程”
https://bitbucket.org/lucashnegri/peakutils/src/master/peakutils/peak.py
"""
如果isinstance(y,np.ndarray)和np.issubdtype(y.dtype,np.unsignedinteger):
提升值错误(“y必须签名”)
如果没有,则:
thres=thres*(np.max(y)-np.min(y))+np.min(y)
最小距离=整数(最小距离)
#计算一阶差分
dy=np.diff(y)
#依次传播左右值以填充所有平台像素(0值)
零,=np,其中(dy==0)
#检查信号是否完全平坦
如果len(零)=len(y)-1:
返回np.array([])
如果len(零):
#计算零索引的一阶差
零之差=np.diff(零)
#检查零是否未链接在一起
零与非零之差=np.add(np.where(零与零之差!=1),1)
#创建一个链式零索引数组
零高原=np.分裂(零、零差异非一)
#修复dy中最左边的值是否为零
如果零高原[0][0]==0:
dy[零高原[0]]=dy[零高原[0][1]+1]
零高原波普(0)
#修复dy最右边的值是否为零
如果len(零高原)和零高原[-1][-1]==len(dy)-1:
dy[zero_plateaus[-1]]=dy[zero_plateaus[-1][0]-1]
零高原波普(-1)
#对于每个零索引链
对于零度高原中的高原:
中位数=np.中位数(高原)
#将最左边的值设置为最左边的非零值
dy[平台[平台<中间值]]=dy[平台[0]-1]
#将最右边和中间的值设置为最右边的非零值
dy[平台[平台>=中间值]]=dy[平台[-1]+1]
#用一阶差分法求峰值
峰值=np.where(
(np.hstack([dy,0.0])<0.0)
&(np.hstack([0.0,dy])>0.0)
&(np.更大(y,thres))
)[0]
#根据最小距离处理多个峰值
如果峰值大小>1且最小距离>1:
最高=峰值[np.argsort(y[peaks])][::-1]
rem=np.ones(y.size,dtype=bool)
rem[峰值]=假
对于最高峰值:
如果不是rem[峰值]:
sl=切片(最大值(0,峰值-最小距离),峰值+最小距离+1)
rem[sl]=真
rem[峰值]=假
峰值=np.arange(y.size)[~rem]
返回峰值
来自自动校准的def freq改善(信号,fs):
信号-=np.平均值(信号)#去除直流偏移
corr=fftconvolve(信号,信号[::-1],模式='full')
corr=corr[len(corr)//2:]
#找到左边的第一个峰
i_peak=指数(corr,thres=0.8,min_dist=5)[0]
i_interp=抛物线(corr,i_peak)[0]
返回fs/i_interp、corr、i_interp
从“自动校准”运行freq\u改进的(y,fs)
产生~441.825 Hz
,大约0.41%的误差。这种方法在更复杂的情况下会表现得更好,并且计算时间会延长两倍
通过更长的采样时间(即将
长度设置为例如0.1s),我们将获得更准确的结果。您使用自相关实现的方法运行良好,可算作时域方法。你有没有运行它来看看自相关曲线是什么样子,以及峰值是什么意思?在这里,策划有很大的帮助。结果fs/px有意义吗?另外,试着用已知的f_0
生成的信号测试函数,有无加性噪声。嗨,@Lukasz,代码运行得非常好。有人应该如何可视化wav文件的f0?@AadityaUra线条图是一个不错的选择,因为它说明了基本频率是如何随时间变化的。