信号的 Numpy 均方根 (RMS) 平滑
Posted
技术标签:
【中文标题】信号的 Numpy 均方根 (RMS) 平滑【英文标题】:Numpy Root-Mean-Squared (RMS) smoothing of a signal 【发布时间】:2012-01-04 22:50:12 【问题描述】:我有一个肌电数据信号,我应该(科学论文的明确建议)使用 RMS 进行平滑处理。
我有以下工作代码,产生所需的输出,但它比我想象的要慢。
#!/usr/bin/python
import numpy
def rms(interval, halfwindow):
""" performs the moving-window smoothing of a signal using RMS """
n = len(interval)
rms_signal = numpy.zeros(n)
for i in range(n):
small_index = max(0, i - halfwindow) # intended to avoid boundary effect
big_index = min(n, i + halfwindow) # intended to avoid boundary effect
window_samples = interval[small_index:big_index]
# here is the RMS of the window, being attributed to rms_signal 'i'th sample:
rms_signal[i] = sqrt(sum([s**2 for s in window_samples])/len(window_samples))
return rms_signal
我看到了一些关于优化移动窗口循环的 deque
和 itertools
建议,以及来自 numpy 的 convolve
,但我无法弄清楚如何使用它们来完成我想要的。
另外,我不再关心避免边界问题,因为我最终得到了大数组和相对较小的滑动窗口。
感谢阅读
【问题讨论】:
你能链接到论文吗?我从未听说过通过计算移动窗口上的点的 RMS 来平滑信号。一般来说,这看起来不像原始信号的平滑版本。 建议以这种方式平滑,因为它与信号功率(能量)相关,这可用于推断肌肉努力。链接:isek-online.org/standards_emg.html“另一种可接受的提供幅度信息的方法是“均方根”或 RMS。就像移动平均值一样,这个量是为特定时间间隔(移动窗口)定义的,必须指明。”根据Noraxon小册子(封闭源,我公司拥有),它是平滑的首选,时间窗口或多或少在50到100ms之间。 移动窗口的 RMS 也是音频电平表背后的理念。 【参考方案1】:我发现我的机器在 convolve 上遇到了困难,所以我提出了以下解决方案:
快速计算移动 RMS 窗口
假设我们有模拟电压样本 a0 ... a99(一百个样本),我们需要通过它们获取 10 个样本的移动 RMS。
窗口最初会从元素 a0 扫描到 a9(十个样本)以获得 rms0。
# rms = [rms0, rms1, ... rms99-9] (total of 91 elements in list):
(rms0)^2 = (1/10) (a0^2 + ... + a9^2) # --- (note 1)
(rms1)^2 = (1/10) (... a1^2 + ... + a9^2 + a10^2) # window moved a step, a0 falls out, a10 comes in
(rms2)^2 = (1/10) ( a2^2 + ... + a10^2 + a11^2) # window moved another step, a1 falls out, a11 comes in
...
简化它:我们有a = [a0, ... a99]
要创建 10 个样本的移动 RMS,我们可以取 10 个a^2
的平方和乘以 1/10。
换句话说,如果我们有
p = (1/10) * a^2 = 1/10 * [a0^2, ... a99^2]
要获得rms^2
,只需添加一组 10 个 p。
让我们有一个累加器 acu:
acu = p0 + ... p8 # (as in note 1 above)
那么我们可以有
rms0^2 = p0 + ... p8 + p9
= acu + p9
rms1^2 = acu + p9 + p10 - p0
rms2^2 = acu + p9 + p10 + p11 - p0 - p1
...
我们可以创造:
V0 = [acu, 0, 0, ... 0]
V1 = [ p9, p10, p11, .... p99] -- len=91
V2 = [ 0, -p0, -p1, ... -p89] -- len=91
V3 = V0 + V1 + V2
如果我们运行
itertools.accumulate(V3)
我们将得到 rms 数组
代码:
import numpy as np
from itertools import accumulate
a2 = np.power(in_ch, 2) / tm_w # create array of p, in_ch is samples, tm_w is window length
v1 = np.array(a2[tm_w - 1 : ]) # v1 = [p9, p10, ...]
v2 = np.append([0], a2[0 : len(a2) - tm_w]) # v2 = [0, p0, ...]
acu = list(accumulate(a2[0 : tm_w - 1])) # get initial accumulation (acu) of the window - 1
v1[0] = v1[0] + acu[-1] # rms element #1 will be at end of window and contains the accumulation
rmspw2 = list(accumulate(v1 - v2))
rms = np.power(rmspw2, 0.5)
我可以在不到 1 分钟的时间内计算出包含 128 个兆样本的数组。
【讨论】:
【参考方案2】:可以使用卷积来执行您所指的操作。为了处理脑电信号,我也做了几次。
import numpy as np
def window_rms(a, window_size):
a2 = np.power(a,2)
window = np.ones(window_size)/float(window_size)
return np.sqrt(np.convolve(a2, window, 'valid'))
分解后,np.power(a, 2)
部分创建了一个与a
具有相同维度的新数组,但每个值都是平方的。 np.ones(window_size)/float(window_size)
产生一个数组或长度window_size
,其中每个元素都是1/window_size
。所以卷积有效地产生了一个新数组,其中每个元素i
等于
(a[i]^2 + a[i+1]^2 + … + a[i+window_size]^2)/window_size
这是移动窗口内数组元素的 RMS 值。它应该以这种方式表现得非常好。
但请注意,np.power(a, 2)
会生成一个相同维度的 new 数组。如果a
真的很大,我的意思是足够大以至于它不能在内存中容纳两次,你可能需要一个策略来修改每个元素。此外,'valid'
参数指定丢弃边框效果,从而导致np.convolve()
生成的数组更小。您可以通过指定'same'
来保留所有内容(请参阅documentation)。
【讨论】:
太棒了,太棒了,太聪明了! EMG 信号比 EMG 短得多,4 通道录制小于 50mb,因此内存使用不会令人望而却步。我计划使用'same'
,在堆栈中显示原始/平滑/过滤信号。
仅作记录,RMS 现在快了 500 倍(window_size' of 16 samples as measured by
cProfile' 为 0.002 对 1.000 秒),并且对于更大(100+)的窗口并不会变得更慢。我说它很棒吗?
您不应使用您拥有的代码指定“有效”以外的任何内容,因为np.convolve()
的第二个参数包含整个窗口长度的倒数。
只是为了扩展一点,如果需要一些更深奥的行为,可以让window
成为一个总和为1.0
的内核,就像标准化的高斯内核一样。实际上,函数中的三行代码执行一些 DSP 文本通称的“去线性化”、“解调”和“重新线性化”,可以用不同的功率(除了两个)、内核(除了单位平方或高斯)来完成,统计运算符(除了加权平均)和窗口大小。【参考方案3】:
由于这不是线性变换,我不相信可以使用 np.convolve()。
这是一个应该做你想做的事情的函数。请注意,返回数组的第一个元素是第一个完整窗口的 rms;即示例中的数组a
,返回数组是子窗口[1,2],[2,3],[3,4],[4,5]
的有效值,不包括部分窗口[1]
和[5]
。
>>> def window_rms(a, window_size=2):
>>> return np.sqrt(sum([a[window_size-i-1:len(a)-i]**2 for i in range(window_size-1)])/window_size)
>>> a = np.array([1,2,3,4,5])
>>> window_rms(a)
array([ 1.41421356, 2.44948974, 3.46410162, 4.47213595])
【讨论】:
它是可以使用卷积,看我的回答。以上是关于信号的 Numpy 均方根 (RMS) 平滑的主要内容,如果未能解决你的问题,请参考以下文章
R语言rms包生存分析之限制性立方样条(RCS, Restricted cubic spline)分析:拟合连续性自变量和事件风险之间的关系并绘制直方图平滑曲线双Y轴于同一个图像中
MATLAB在NumPy / Python中的平滑实现(n点移动平均)