简体   繁体   English

如何获得信号的高低包络

[英]How to get high and low envelope of a signal

I have quite a noisy data, and I am trying to work out a high and low envelope to the signal.我有很多嘈杂的数据,我正在尝试计算出信号的高低包络。 It is kind of like this example in MATLAB:它有点像 MATLAB 中的这个例子:

http://uk.mathworks.com/help/signal/examples/signal-smoothing.html http://uk.mathworks.com/help/signal/examples/signal-smoothing.html

in "Extracting Peak Envelope".在“提取峰包络”中。 Is there a similar function in Python that can do that? Python中有没有类似function可以做到的? My entire project has been written in Python, worst case scenario I can extract my numpy array and throw it into MATLAB and use that example.我的整个项目都写在 Python 中,最坏的情况是我可以提取我的 numpy 数组并将其放入 MATLAB 并使用该示例。 But I prefer the look of matplotlib... and really cba doing all of those I/O between MATLAB and Python...但我更喜欢 matplotlib 的外观......并且真的 cba 在 MATLAB 和 Python 之间完成所有这些 I/O......

Thanks,谢谢,

Is there a similar function in Python that can do that? Python中是否有类似的功能可以做到这一点?

As far as I am aware there is no such function in Numpy / Scipy / Python.据我所知,Numpy / Scipy / Python 中没有这样的功能。 However, it is not that difficult to create one.然而,创造一个并不难。 The general idea is as follows:大体思路如下:

Given a vector of values (s):给定一个值向量(s):

  1. Find the location of peaks of (s).找出 (s) 的峰值位置。 Let's call them (u)让我们称他们为 (u)
  2. Find the location of troughs of s.找出 s 波谷的位置。 Let's call them (l).让我们称他们为(l)。
  3. Fit a model to the (u) value pairs.将模型拟合到 (u) 值对。 Let's call it (u_p)让我们称之为(u_p)
  4. Fit a model to the (l) value pairs.将模型拟合到 (l) 值对。 Let's call it (l_p)让我们称之为(l_p)
  5. Evaluate (u_p) over the domain of (s) to get the interpolated values of the upper envelope.在 (s) 的域上评估 (u_p) 以获得上包络的内插值。 (Let's call them (q_u)) (让我们称他们为(q_u))
  6. Evaluate (l_p) over the domain of (s) to get the interpolated values of the lower envelope.在 (s) 的域上评估 (l_p) 以获得下包络的内插值。 (Let's call them (q_l)). (让我们称他们为(q_l))。

As you can see, it is the sequence of three steps (Find location, fit model, evaluate model) but applied twice, once for the upper part of the envelope and one for the lower.如您所见,它是三个步骤(查找位置、拟合模型、评估模型)的顺序,但应用了两次,一次用于信封的上部,一次用于下部。

To collect the "peaks" of (s) you need to locate points where the slope of (s) changes from positive to negative and to collect the "troughs" of (s) you need to locate the points where the slope of (s) changes from negative to positive.要收集 (s) 的“峰值”,您需要找到 (s) 的斜率从正变为负的点,并收集 (s) 的“波谷”,您需要找到 (s) 的斜率变化的点) 由负变为正。

A peak example: s = [4,5,4] 5-4 is positive 4-5 is negative峰值示例:s = [4,5,4] 5-4 为正 4-5 为负

A trough example: s = [5,4,5] 4-5 is negative 5-4 is positive一个低谷的例子:s = [5,4,5] 4-5 是负数 5-4 是正数

Here is an example script to get you started with plenty of inline comments:这是一个示例脚本,可让您开始使用大量内联注释:

from numpy import array, sign, zeros
from scipy.interpolate import interp1d
from matplotlib.pyplot import plot,show,hold,grid

s = array([1,4,3,5,3,2,4,3,4,5,4,3,2,5,6,7,8,7,8]) #This is your noisy vector of values.

q_u = zeros(s.shape)
q_l = zeros(s.shape)

#Prepend the first value of (s) to the interpolating values. This forces the model to use the same starting point for both the upper and lower envelope models.

u_x = [0,]
u_y = [s[0],]

l_x = [0,]
l_y = [s[0],]

#Detect peaks and troughs and mark their location in u_x,u_y,l_x,l_y respectively.

for k in xrange(1,len(s)-1):
    if (sign(s[k]-s[k-1])==1) and (sign(s[k]-s[k+1])==1):
        u_x.append(k)
        u_y.append(s[k])

    if (sign(s[k]-s[k-1])==-1) and ((sign(s[k]-s[k+1]))==-1):
        l_x.append(k)
        l_y.append(s[k])

#Append the last value of (s) to the interpolating values. This forces the model to use the same ending point for both the upper and lower envelope models.

u_x.append(len(s)-1)
u_y.append(s[-1])

l_x.append(len(s)-1)
l_y.append(s[-1])

#Fit suitable models to the data. Here I am using cubic splines, similarly to the MATLAB example given in the question.

u_p = interp1d(u_x,u_y, kind = 'cubic',bounds_error = False, fill_value=0.0)
l_p = interp1d(l_x,l_y,kind = 'cubic',bounds_error = False, fill_value=0.0)

#Evaluate each model over the domain of (s)
for k in xrange(0,len(s)):
    q_u[k] = u_p(k)
    q_l[k] = l_p(k)

#Plot everything
plot(s);hold(True);plot(q_u,'r');plot(q_l,'g');grid(True);show()

This produces this output:这会产生以下输出:

指示性输出

Points for further improvement:进一步改进的要点:

  1. The above code does not filter peaks or troughs that may be occuring closer than some threshold "distance" (Tl) (eg time).上面的代码没有过滤可能比某个阈值“距离”(T1)(例如时间)更近出现的波峰或波谷。 This is similar to the second parameter of envelope .这类似于envelope的第二个参数。 It is easy to add it though by examining the differences between consecutive values of u_x,u_y .通过检查u_x,u_y连续值之间的差异,很容易添加它。

  2. However, a quick improvement over the point mentioned previously is to lowpass filter your data with a moving average filter BEFORE interpolating an upper and lower envelope functions.但是,对前面提到的这一点的快速改进是插入上下包络函数之前使用移动平均滤波器对数据进行低通滤波。 You can do this easily by convolving your (s) with a suitable moving average filter.您可以通过使用合适的移动平均滤波器对您的 (s) 进行卷积来轻松完成此操作。 Without going to a great detail here (can do if required), to produce a moving average filter that operates over N consecutive samples, you would do something like this: s_filtered = numpy.convolve(s, numpy.ones((1,N))/float(N) . The higher the (N) the smoother your data will appear. Please note however that this will shift your (s) values (N/2) samples to the right (in s_filtered ) due to something that is called group delay of the smoothing filter. For more information about the moving average, please see this link .无需在这里详细介绍(如果需要,可以这样做),要生成对 N 个连续样本进行操作的移动平均滤波器,您可以执行以下操作: s_filtered = numpy.convolve(s, numpy.ones((1,N))/float(N) . s_filtered = numpy.convolve(s, numpy.ones((1,N))/float(N)越高,您的数据就越平滑。但是请注意,由于某些原因,这会将您的 (s) 值 (N/2) 样本向右移动(在s_filtered )称为平滑滤波器的群延迟。有关移动平均的更多信息,请参阅此链接

Hope this helps.希望这可以帮助。

(Happy to ammend the response if more information about the original application is provided. Perhaps the data can be pre-processed in a more suitable way (?) ) (如果提供了有关原始应用程序的更多信息,很高兴修改响应。也许可以以更合适的方式对数据进行预处理(?))

First attempt was to make use of scipy Hilbert transform to determine the amplitude envelope but this didn't work as expected in many cases, mainly reason because, citing from this digital signal processing answer :第一次尝试是利用 scipy Hilbert 变换来确定幅度包络,但这在许多情况下没有按预期工作,主要是因为引用了这个数字信号处理答案

Hilbert envelope, also called Energy-Time Curve (ETC), only works well for narrow-band fluctuations.希尔伯特包络,也称为能量-时间曲线 (ETC),仅适用于窄带波动。 Producing an analytic signal, of which you later take the absolute value, is a linear operation, so it treats all frequencies of your signal equally.产生分析信号(稍后取其绝对值)是一种线性运算,因此它平等对待信号的所有频率。 If you give it a pure sine wave, it will indeed return to you a straight line.如果你给它一个纯正弦波,它确实会返回给你一条直线。 When you give it white noise however, you will likely get noise back.然而,当你给它白噪声时,你很可能会得到噪音。

From then, since the other answers were using cubic spline interpolation and did tend to become cumbersome, a bit unstable (spurious oscillations) and time consuming for very long and noisy data arrays, I will contribute here with a simple and numpy efficient version that seems to work pretty fine:从那时起,由于其他答案使用三次样条插值并且确实变得麻烦,有点不稳定(虚假振荡)并且对于非常长且嘈杂的数据数组很耗时,我将在这里贡献一个简单而麻木的高效版本,看起来工作得很好:

import numpy as np
from matplotlib import pyplot as plt

def hl_envelopes_idx(s, dmin=1, dmax=1, split=False):
    """
    Input :
    s: 1d-array, data signal from which to extract high and low envelopes
    dmin, dmax: int, optional, size of chunks, use this if the size of the input signal is too big
    split: bool, optional, if True, split the signal in half along its mean, might help to generate the envelope in some cases
    Output :
    lmin,lmax : high/low envelope idx of input signal s
    """

    # locals min      
    lmin = (np.diff(np.sign(np.diff(s))) > 0).nonzero()[0] + 1 
    # locals max
    lmax = (np.diff(np.sign(np.diff(s))) < 0).nonzero()[0] + 1 
    

    if split:
        # s_mid is zero if s centered around x-axis or more generally mean of signal
        s_mid = np.mean(s) 
        # pre-sorting of locals min based on relative position with respect to s_mid 
        lmin = lmin[s[lmin]<s_mid]
        # pre-sorting of local max based on relative position with respect to s_mid 
        lmax = lmax[s[lmax]>s_mid]


    # global max of dmax-chunks of locals max 
    lmin = lmin[[i+np.argmin(s[lmin[i:i+dmin]]) for i in range(0,len(lmin),dmin)]]
    # global min of dmin-chunks of locals min 
    lmax = lmax[[i+np.argmax(s[lmax[i:i+dmax]]) for i in range(0,len(lmax),dmax)]]
    
    return lmin,lmax

Example 1: quasi-periodic vibration例一:准周期振动

t = np.linspace(0,8*np.pi,5000)
s = 0.8*np.cos(t)**3 + 0.5*np.sin(np.exp(1)*t)
high_idx, low_idx = hl_envelopes_idx(s)

# plot
plt.plot(t,s,label='signal')
plt.plot(t[high_idx], s[high_idx], 'r', label='low')
plt.plot(t[low_idx], s[low_idx], 'g', label='high')

Example 2: noisy decaying signal示例 2:嘈杂的衰减信号

t = np.linspace(0,2*np.pi,5000)
s = 5*np.cos(5*t)*np.exp(-t) + np.random.rand(len(t))

high_idx, low_idx = hl_envelopes_idx(s,dmin=15,dmax=15)

# plot
plt.plot(t,s,label='signal')
plt.plot(t[high_idx], s[high_idx], 'r', label='low')
plt.plot(t[low_idx], s[low_idx], 'g', label='high')

Example 3: nonsymmetric modulated chirp示例 3:非对称调制啁啾

A much more complex signal of 18867925 samples (which isn't included here):一个更复杂的18867925样本信号(此处不包括在内):

Building on @A_A 's answer, replace the sign check with nim/max test to make it more robust.以@A_A 的答案为基础,用 nim/max 测试替换符号检查以使其更健壮。

import numpy as np
import scipy.interpolate
import matplotlib.pyplot as pt
%matplotlib inline

t = np.multiply(list(range(1000)), .1)
s = 10*np.sin(t)*t**.5

u_x = [0]
u_y = [s[0]]

l_x = [0]
l_y = [s[0]]

#Detect peaks and troughs and mark their location in u_x,u_y,l_x,l_y respectively.
for k in range(2,len(s)-1):
    if s[k] >= max(s[:k-1]):
        u_x.append(t[k])
        u_y.append(s[k])

for k in range(2,len(s)-1):
    if s[k] <= min(s[:k-1]):
        l_x.append(t[k])
        l_y.append(s[k])

u_p = scipy.interpolate.interp1d(u_x, u_y, kind = 'cubic', bounds_error = False, fill_value=0.0)
l_p = scipy.interpolate.interp1d(l_x, l_y, kind = 'cubic', bounds_error = False, fill_value=0.0)

q_u = np.zeros(s.shape)
q_l = np.zeros(s.shape)
for k in range(0,len(s)):
    q_u[k] = u_p(t[k])
    q_l[k] = l_p(t[k])

pt.plot(t,s)
pt.plot(t, q_u, 'r')
pt.plot(t, q_l, 'g')

If you expect the function to be increasing, try:如果您希望函数增加,请尝试:

for k in range(1,len(s)-2):
    if s[k] <= min(s[k+1:]):
        l_x.append(t[k])
        l_y.append(s[k])

for the lower envelope.对于较低的信封。

Or you use pandas. Here I only need two lines of code:或者你用pandas,这里我只需要两行代码:

import pandas as pd
import numpy as np


x=np.linspace(0,5*np.pi,1000)
y=np.sin(x)+0.4*np.cos(x/4)*np.sin(x*20)

df=pd.DataFrame(data={"y":y},index=x)

windowsize = 20
df["y_upperEnv"]=df["y"].rolling(window=windowsize).max().shift(int(-windowsize/2))
df["y_lowerEnv"]=df["y"].rolling(window=windowsize).min().shift(int(-windowsize/2))

df.plot(figsize=(20,10))

Output: Output:

图片

You might want to look at the Hilbert transform, which is likely the actual code behind the envelope function in MATLAB.您可能想查看 Hilbert 变换,它可能是 MATLAB 中包络函数背后的实际代码。 The signal sub-module for scipy has a built-in Hilbert transform, and there is a nice example in the documentation, where the envelope of an oscillatory signal is extracted: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.hilbert.html scipy 的信号子模块有一个内置的希尔伯特变换,文档中有一个很好的例子,其中提取了振荡信号的包络: https : //docs.scipy.org/doc/scipy/reference /generated/scipy.signal.hilbert.html

I found using a combination of scipy functions better performing than alternatives我发现使用 scipy 函数的组合比替代方法性能更好

def envelope(sig, distance):
    # split signal into negative and positive parts
    u_x = np.where(sig > 0)[0]
    l_x = np.where(sig < 0)[0]
    u_y = sig.copy()
    u_y[l_x] = 0
    l_y = -sig.copy()
    l_y[u_x] = 0
    
    # find upper and lower peaks
    u_peaks, _ = scipy.signal.find_peaks(u_y, distance=distance)
    l_peaks, _ = scipy.signal.find_peaks(l_y, distance=distance)
    
    # use peaks and peak values to make envelope
    u_x = u_peaks
    u_y = sig[u_peaks]
    l_x = l_peaks
    l_y = sig[l_peaks]
    
    # add start and end of signal to allow proper indexing
    end = len(sig)
    u_x = np.concatenate((u_x, [0, end]))
    u_y = np.concatenate((u_y, [0, 0]))
    l_x = np.concatenate((l_x, [0, end]))
    l_y = np.concatenate((l_y, [0, 0]))
    
    # create envelope functions
    u = scipy.interpolate.interp1d(u_x, u_y)
    l = scipy.interpolate.interp1d(l_x, l_y)
    return u, l

def test():
    x = np.arange(200)
    sig = np.sin(x)
    u, l = envelope(sig, 1)
    
    plt.figure(figsize=(25,5))
    plt.plot(x, u(x))
    plt.plot(x, l(x))
    plt.plot(x, sig*0.9)
    plt.show()
    
test()

结果图像

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM