web-dev-qa-db-ja.com

python-信号の高低エンベロープを取得する方法?

非常にノイズの多いデータがあり、信号の高低のエンベロープを処理しようとしています。これは、MATLABのこの例のようなものです。

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

「ピークエンベロープの抽出」 Pythonそれを行うことができる同様の関数はありますか?私のプロジェクト全体はPythonで書かれており、最悪の場合のシナリオです。私はmatplotlibの外観を好む...そして実際にcbaは、MATLABとPythonの間でこれらすべてのI/Oを実行しています...

おかげで、

11
user5224720

Pythonで実行できる同様の関数はありますか?

私が知る限り、Numpy/Scipy/Pythonにはそのような機能はありません。ただし、作成するのはそれほど難しくありません。一般的な考え方は次のとおりです。

値のベクトル(s)が与えられた場合:

  1. (s)のピークの場所を見つけます。それらを呼びましょう(u)
  2. Sの谷の場所を見つけます。それらを呼びましょう(l)。
  3. モデルを(u)値のペアに適合させます。呼びましょう(u_p)
  4. モデルを(l)値のペアに適合させます。呼びましょう(l_p)
  5. (s)の領域で(u_p)を評価して、上部エンベロープの補間値を取得します。 (それらを(q_u)と呼びましょう)
  6. (s)の領域で(l_p)を評価して、下のエンベロープの補間値を取得します。 (それらを(q_l)と呼びましょう)。

ご覧のとおり、これは3つのステップ(場所の検索、モデルの適合、モデルの評価)のシーケンスですが、2回適用されます。1回はエンベロープの上部に、1回はエンベロープに適用されます。

(s)の「ピーク」を収集するには、(s)の勾配が正から負に変化するポイントを見つける必要があり、(s)の「谷」を収集するには、(s)の勾配がポイントを見つける必要があります)負から正に変化します。

ピークの例:s = [4,5,4] 5-4は正、4-5は負

谷の例:s = [5,4,5] 4-5は負、5-4は正

たくさんのインラインコメントから始めるためのスクリプト例を以下に示します:

_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()
_

これにより、次の出力が生成されます。

Indicative output

さらなる改善のポイント:

  1. 上記のコードは、いくつかのしきい値「距離」(Tl)(たとえば、時間)より近くに発生している可能性のあるピークまたは谷をフィルターしません。これは、envelopeの2番目のパラメーターに似ています。ただし、_u_x,u_y_の連続する値の違いを調べることで簡単に追加できます。

  2. ただし、前述のポイントをすばやく改善するには、移動平均フィルターでデータをローパスフィルター処理します[〜#〜] before [〜#〜]上限および下限エンベロープ関数の補間。これは、適切な移動平均フィルターで(s)を畳み込むことで簡単に実行できます。ここで詳細な説明を行わずに(必要に応じて実行できます)、連続するN個のサンプルで動作する移動平均フィルターを作成するには、s_filtered = numpy.convolve(s, numpy.ones((1,N))/float(N)のようにします。 (N)が高いほど、データは滑らかになります。ただし、スムージングフィルターの group delay と呼ばれるもののため、これにより(s)値(N/2)サンプルが(_s_filtered_で)右にシフトします。移動平均の詳細については、 このリンク を参照してください。

お役に立てれば。

(元のアプリケーションに関する詳細情報が提供されている場合は、応答を修正して満足しています。おそらく、データをより適切な方法で前処理できます(?))

16
A_A

@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')

関数の増加が予想される場合は、以下を試してください。

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])

下の封筒用。

2
mousomer

回答は補間を使用してMatlab関数の動作を模倣していたため、これは私にとってはあまり役に立ちませんでした。また、非常に長くノイズの多いデータ配列では、少し不安定になり(偽の振動)、時間がかかる傾向がありました。私はここで私のためにかなりうまくいくように見える、よりシンプルでより速いバージョンで貢献します:

import numpy as np
from matplotlib import pyplot as plt


def hl_envelopes_idx(s,dmin=1,dmax=1):
    """
    s : 1d-array, data signal from which to extract high and low envelopes
    dmin, dmax : int, size of chunks, use this if size of data is too big
    """

    # 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 

    """
    # the following might help in some case by cutting the signal in "half"
    s_mid = np.mean(s) (0 if s centered or more generally mean of signal)
    # pre-sort of locals min based on sign 
    lmin = lmin[s[lmin]<s_mid]
    # pre-sort of local max based on sign 
    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

たとえば、次のようになります。

t = np.linspace(0,20*np.pi,5000,endpoint=False)
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')

ex1

または、この騒々しい時間サンプルの場合:

t = np.linspace(0,2*np.pi,5000,endpoint=False)
s = 5*np.cos(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')

ex2

または最後に、形状のはるかに複雑な信号(18867925,)(ここには含めませんでした):

ex3

お役に立てれば。

0
Yacola