如何在没有缓冲区的情况下在 C 中实现移动平均线?

信息处理 过滤器 C 移动平均线
2022-01-02 17:59:55

是否可以在不需要样本窗口的情况下在 C 中实现移动平均?

我发现我可以优化一点,通过选择一个两倍的窗口大小来允许位移而不是分割,但不需要缓冲区会很好。有没有办法仅将新的移动平均结果表示为旧结果和新样本的函数?

定义一个示例移动平均线,跨越 4 个样本的窗口为:

ma <= (a + b + c + d) / 4

添加新样本 e:

ma_new <= (a + b + c + d) / 4 - (a / 4) + (e / 4)
ma_new(ma, oldest_sample, new_sample) <= ma - (a / 4) + (e / 4)
4个回答

移动平均值可以递归实现,但要精确计算移动平均值,您必须记住总和中最旧的输入样本(即a在您的示例中)。对于长度为 $N$ 的移动平均值,您计算:

$$y[n]=\frac{1}{N}\sum_{k=n-N+1}^nx[k]\tag{1}$$

其中 $y[n]$ 是输出信号,$x[n]$ 是输入信号。方程。(1) 可以递归地写成

$$y[n]=y[n-1]+\frac{1}{N}(x[n]-x[nN])\tag{2}$$

因此,您总是需要记住样本 $x[nN]$ 才能计算 (2)。

正如 Conrad Turner 所指出的,您可以改用(无限长)指数窗口,它允许您仅从过去的输出和当前输入计算输出:

$$y[n]=\alpha x[n]+(1-\alpha)y[n-1]\tag{3}$$

但这不是标准(未加权)移动平均线,而是指数加权移动平均线,过去的样本权重更小,但是(至少在理论上)您永远不会忘记任何事情(样本的权重越来越小远在过去)。

衰退记忆(指数)移动平均线有什么问题:

ma_new = alpha * new_sample + (1-alpha) * ma_old

上面 (3) 的代码,简单递归过滤器$y(n)=αx(n) + (1-α)y(n-1)$其中$α<1$$α$越小,过滤器越平滑。我用它来平滑没有缓冲区的数据流。

uint32 filter(uint32 x, uint32 y)
{
    return (x + 9*y) / 10;  //( α=1/10 )
}

main()
{
  angle = filter(new_angle,angle);   // y(n) = filter( x(n), y(n-1) )
}

此方法通过基本上假设样本window_size样本之前的值等于先前的移动平均值来为您提供移动平均值的近似值,该移动平均值在每个window_size样本中更新。

如果您的值是随机分布的,它会很好地工作,但异常值会使它比精确的移动平均值更偏斜。

previous_average = 0
total = 0
for count, sample in enumerate(samples):
    if count % window_size == 0: # Update previous_average every window_size samples
        previous_average = total/window_size
    total += sample
    if count > window_size:
        total -= previous_average
    current_average = total/window_size

通过 4 点示例,我们可以估计误差:

$\begin{align} y_n = \frac{(a + b + c + d)}{4} &, y_{n+1} = \frac{(a+b+c+d)}{4} + \frac{e}{4} - \frac{a}{4} \\ y*_{n+1} &= \frac{(a + b + c + d) - y_{n} + e}{ 4} \\ &= \frac{(a+b+c+d)}{4} - \frac{(a+b+c+d)}{16} + \frac{e}{4} \\ &= \frac{3(a+b+c+d)}{16} + \frac{e}{4} \\ \end{align}$

$\begin{align} E &= y*_{n+1} - y_{n+1} \\ &= \frac{3a}{16} - \frac{a+b+c+d}{16 } \end{对齐}$

我懒惰地假设任意窗口大小 $W$

$\begin{align} E &= \frac{(W-1)a}{W^2} - \frac{y_n}{W} \\ \end{align}$

如果 $W$ 足够大,则:

$\begin{align} E &\approx \frac{a - y_n}{W} \end{align}$

这是$a$点的残差除以残差总数...

这只是第一次count % window_size == 0迭代的误差的近似值,我已经在尝试超越我的数学能力,但是由于我们可以从任何 $x_n = a$ 开始,这表明只要 $W$ 足够大或每个 $x_{n} - y_{n}$ 足够小,则此平均值将产生与精确公式拟合相同的平均残差。

我从我的测试脚本中附上了一张图片,显示了与此方法相比的精确 100 点移动平均值:

精确移动平均线与此方法的比较