在numpy数组中查找满足条件的大量连续值

25 投票
8 回答
16599 浏览
提问于 2025-04-16 08:48

我有一些音频数据存储在一个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

上面的代码会找到至少有MIN_SILENCE个连续元素小于SILENCE_THRESHOLD的部分。

不过,显然,上面的代码效率非常低,而且对正则表达式的使用也不太恰当。有没有其他更高效的方法,同时代码又能保持简单和简短呢?

8 个回答

7

这个方法有点随意,但简单又快,如果你不介意用scipy的话:

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

这个想法是,数据中安静的部分会变得很小,而声音大的地方则不会。你可以调整'sigma'来决定一个区域需要多长时间才算“安静”;调整'threshold'来决定这个区域需要多安静。这种方法在'sigma'值很大时会变慢,这时候用基于FFT的平滑处理可能会更快。

这个方法还有一个好处,就是单个的“热像素”不会干扰你找到安静的部分,所以你对某些类型的噪声就不那么敏感了。

9

使用 scipy.ndimage 有一个非常方便的解决方案。对于一个数组:

a = np.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]
42

这里有一个基于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 indices 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()

撰写回答