2010-12-20 56 views
16

我在numpy數組中加載了一些音頻數據,我希望通過查找無聲部分來分段數據,即音頻幅度低於aa某個閾值的部分時間段。在numpy數組中查找滿足條件的大量連續值

極其簡單的方法來做到這一點是這樣的:

values = ''.join(("1" if (abs(x) < SILENCE_THRESHOLD) else "0" for x in samples)) 
pattern = re.compile('1{%d,}'%int(MIN_SILENCE))                   
for match in pattern.finditer(values): 
    # code goes here 

上述代碼發現其中存在比SILENCE_THRESHOLD至少MIN_SILENCE連續元素更小的部分。

現在,很明顯,上面的代碼是非常低效率和可怕的濫用正則表達式。還有其他一些更高效的方法,但是仍然會導致同樣簡單和短的代碼?

回答

26

這是一個基於numpy的解決方案。

我認爲(?)它應該比其他選項更快。希望這很清楚。

但是,它確實需要兩倍於各種基於生成器的解決方案的內存。只要你可以在內存中保存一份臨時數據(用於比較),以及一個與數據長度相同的布爾數組(每個元素爲1位),它應該非常高效......

import numpy as np 

def main(): 
    # Generate some random data 
    x = np.cumsum(np.random.random(1000) - 0.5) 
    condition = np.abs(x) < 1 

    # Print the start and stop indicies of each region where the absolute 
    # values of x are below 1, and the min and max of each of these regions 
    for start, stop in contiguous_regions(condition): 
     segment = x[start:stop] 
     print start, stop 
     print segment.min(), segment.max() 

def contiguous_regions(condition): 
    """Finds contiguous True regions of the boolean array "condition". Returns 
    a 2D array where the first column is the start index of the region and the 
    second column is the end index.""" 

    # Find the indicies of changes in "condition" 
    d = np.diff(condition) 
    idx, = d.nonzero() 

    # We need to start things after the change in "condition". Therefore, 
    # we'll shift the index by 1 to the right. 
    idx += 1 

    if condition[0]: 
     # If the start of condition is True prepend a 0 
     idx = np.r_[0, idx] 

    if condition[-1]: 
     # If the end of condition is True, append the length of the array 
     idx = np.r_[idx, condition.size] # Edit 

    # Reshape the result into two columns 
    idx.shape = (-1,2) 
    return idx 

main() 
+0

這導致令人印象深刻的20倍加速!它沒有考慮到最小長度,但這很容易添加。唯一的問題是增加的內存使用情況,使得在某些情況下使用它是不可行的,所以我想我會默認使用這個選項,並且在內存不足時添加一個選項來使用另一種算法。 – pafcu 2010-12-21 05:46:56

+1

隨着numpy 1.9,我得到一個'DeprecationWarning:numpy布爾subtract(二進制運算符)已棄用'使用np.diff在布爾條件。我用'd = np.subtract(condition [1:],condition [: - 1],dtype = np.float)'替換了這一行,以避免這個問題。 – daryl 2014-09-29 15:30:43

+2

@daryl - 感謝您注意到變化!可以更清楚地做'd = np.diff(condition.astype(int))',儘管這主要是個人偏好的問題。 – 2014-09-29 19:10:38

3

我還沒有測試過,但你應該接近你要找的東西。略多行代碼,但應該更高效,可讀的,它不濫用正則表達式:-)

def find_silent(samples): 
    num_silent = 0 
    start = 0 
    for index in range(0, len(samples)): 
     if abs(samples[index]) < SILENCE_THRESHOLD: 
      if num_silent == 0: 
       start = index 
      num_silent += 1 
     else: 
      if num_silent > MIN_SILENCE: 
       yield samples[start:index] 
      num_silent = 0 
    if num_silent > MIN_SILENCE: 
     yield samples[start:] 

for match in find_silent(samples): 
    # code goes here 
+1

你的代碼看起來不錯,只是如果沉默片斷在樣本的末尾,那麼它將不會被發現。你需要在for循環之後檢查它。 – 2010-12-20 22:48:24

+0

@Justin:謝謝,在編輯中補充說。 – 2010-12-20 23:45:45

2

這應返回的(start,length)雙列表:

def silent_segs(samples,threshold,min_dur): 
    start = -1 
    silent_segments = [] 
    for idx,x in enumerate(samples): 
    if start < 0 and abs(x) < threshold: 
     start = idx 
    elif start >= 0 and abs(x) >= threshold: 
     dur = idx-start 
     if dur >= min_dur: 
     silent_segments.append((start,dur)) 
     start = -1 
    return silent_segments 

和簡單測試:

>>> s = [-1,0,0,0,-1,10,-10,1,2,1,0,0,0,-1,-10] 
>>> silent_segs(s,2,2) 
[(0, 5), (9, 5)] 
+0

這似乎比基於正則表達式的解決方案快大約25%。尼斯。現在只需要9分鐘:-) – pafcu 2010-12-20 23:23:46

5

稍有馬虎,但簡單快速十歲上下,如果你不介意使用SciPy的:

from scipy.ndimage import gaussian_filter 
sigma = 3 
threshold = 1 
above_threshold = gaussian_filter(data, sigma=sigma) > threshold 

這個想法是,數據的安靜部分將平滑到低振幅,而響亮的區域則不會。調整'西格瑪'影響'安靜'區域必須持續多久;調整「門檻」來影響它必須是多麼安靜。這對於大西格瑪來說會變慢,此時使用基於FFT的平滑可能會更快。

這還有另一個好處,即單個「熱像素」不會中斷你的沉默發現,所以你對某些類型的噪音不那麼敏感。

2

另一種方式來快速而簡潔地做到這一點:

import pylab as pl 

v=[0,0,1,1,0,0,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0] 
vd = pl.diff(v) 
#vd[i]==1 for 0->1 crossing; vd[i]==-1 for 1->0 crossing 
#need to add +1 to indexes as pl.diff shifts to left by 1 

i1=pl.array([i for i in xrange(len(vd)) if vd[i]==1])+1 
i2=pl.array([i for i in xrange(len(vd)) if vd[i]==-1])+1 

#corner cases for the first and the last element 
if v[0]==1: 
    i1=pl.hstack((0,i1)) 
if v[-1]==1: 
    i2=pl.hstack((i2,len(v))) 

現在I1包含起始索引和i2的1月底指數,...,1個區

3

有一個非常方便的解決方案,使用scipy.ndimage。對於數組:

a = array([1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0]) 

其可施加到另一陣列的條件的結果,發現該連續的區域是簡單的:

regions = scipy.ndimage.find_objects(scipy.ndimage.label(a)[0]) 

然後,在施加任何功能,這些區域可以是完成例如像:

[np.sum(a[r]) for r in regions] 
1

@喬金通,我用argmax代替了約20%-25%的速度提高了np.diff/np.nonzero溶液(見下面的代碼,condition是布爾)

def contiguous_regions(condition): 
    idx = [] 
    i = 0 
    while i < len(condition): 
     x1 = i + condition[i:].argmax() 
     try: 
      x2 = x1 + condition[x1:].argmin() 
     except: 
      x2 = x1 + 1 
     if x1 == x2: 
      if condition[x1] == True: 
       x2 = len(condition) 
      else: 
       break 
     idx.append([x1,x2]) 
     i = x2 
    return idx 

當然,您的里程可能會因您的數據而異。

此外,我不完全確定,但我猜numpy可能會優化argmin/argmax布爾數組停止搜索第一True/False發生。這可能可以解釋它。