frequency_estimator.py 3.45 KB
Newer Older
1
2
3
4
5
from __future__ import division
from numpy.fft import rfft
from numpy import argmax, mean, diff, log, nonzero
from scipy.signal import blackmanharris, correlate
from time import time
6

7

8
from parabolic import parabolic
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79


def freq_from_crossings(sig, fs):
    """
    Estimate frequency by counting zero crossings
    """
    # Find all indices right before a rising-edge zero crossing
    indices = nonzero((sig[1:] >= 0) & (sig[:-1] < 0))[0]

    # Naive (Measures 1000.185 Hz for 1000 Hz, for instance)
    # crossings = indices

    # More accurate, using linear interpolation to find intersample
    # zero-crossings (Measures 1000.000129 Hz for 1000 Hz, for instance)
    crossings = [i - sig[i] / (sig[i+1] - sig[i]) for i in indices]

    # Some other interpolation based on neighboring points might be better.
    # Spline, cubic, whatever

    return fs / mean(diff(crossings))


def freq_from_fft(sig, fs):
    """
    Estimate frequency from peak of FFT
    """
    # Compute Fourier transform of windowed signal
    windowed = sig * blackmanharris(len(sig))
    f = rfft(windowed)

    # Find the peak and interpolate to get a more accurate peak
    i = argmax(abs(f))  # Just use this for less-accurate, naive version
    true_i = parabolic(log(abs(f)), i)[0]

    # Convert to equivalent frequency
    return fs * true_i / len(windowed)


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


def freq_from_HPS(sig, fs):
    """
    Estimate frequency using harmonic product spectrum (HPS)
    """
    windowed = sig * blackmanharris(len(sig))

    from pylab import subplot, plot, log, copy, show
    import matplotlib.pyplot as plt

    # harmonic product spectrum:
    c = abs(rfft(windowed))
LucasDANIELE's avatar
LucasDANIELE committed
80
    maxharms = 6
81
    resarray = []
LucasDANIELE's avatar
LucasDANIELE committed
82
    for x in range(2, maxharms):
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
        a = copy(c[::x])  # Should average or maximum instead of decimating
        c = c[:len(a)]
        i = argmax(abs(c))
        true_i = parabolic(abs(c), i)[0]
        res = fs * true_i / len(windowed)
        resarray.append(res)
        c *= a
    show()
    return mean(resarray[1:3])


def hello(signal, fs):
    print('Calculating frequency from FFT:', end=' ')
    start_time = time()
    print('%f Hz' % freq_from_fft(signal, fs))
    print('Time elapsed: %.3f s\n' % (time() - start_time))
    
    print('Calculating frequency from zero crossings:', end=' ')
    start_time = time()
    print('%f Hz' % freq_from_crossings(signal, fs))
    print('Time elapsed: %.3f s\n' % (time() - start_time))
    
    print('Calculating frequency from autocorrelation:', end=' ')
    start_time = time()
    print('%f Hz' % freq_from_autocorr(signal, fs))
    print('Time elapsed: %.3f s\n' % (time() - start_time))
    
    # print('Calculating frequency from harmonic product spectrum:')
    # start_time = time()
    # freq_from_HPS(signal, fs)
    # print('Time elapsed: %.3f s\n' % (time() - start_time))