我有一个在100Hz下测量的信号,我需要对该信号应用Savitzky-Golay平滑滤波器。但是,仔细检查后,我的信号并不是以完全恒定的速率测量的,测量之间的差值介于9.7到10.3 ms之间。
有没有办法对不等距的数据使用Savitzky-Golay滤波器?还有其他可以应用的方法吗?
我有一个在100Hz下测量的信号,我需要对该信号应用Savitzky-Golay平滑滤波器。但是,仔细检查后,我的信号并不是以完全恒定的速率测量的,测量之间的差值介于9.7到10.3 ms之间。
有没有办法对不等距的数据使用Savitzky-Golay滤波器?还有其他可以应用的方法吗?
Answers:
一种方法是对数据进行重新采样,以使其等距分布,然后您可以执行所需的任何处理。使用线性滤波的带限重采样将不是一个好的选择,因为数据不是均匀间隔的,因此您可以使用某种局部多项式插值(例如三次样条)来估计底层信号的值“精确”间隔为10毫秒。
由于Savitzky-Golay滤波器的导出方式(即作为局部最小二乘多项式拟合),非自然采样得到了自然的概括,这在计算上要昂贵得多。
一般的Savitzky-Golay滤波器
对于标准滤波器,其想法是将多项式拟合到局部样本集(使用最小二乘法),然后将中心样本替换为位于中心索引处(即0)的多项式值。这意味着可以通过对样本索引的范德蒙德矩阵求逆来生成标准SG滤波器系数。例如,要在五个样本(具有局部指数-2,-1,0,1,2)上生成局部抛物线拟合,设计等式A c = y的系统如下:
在上面,是最小二乘多项式c 0 + c 1 x + c 2 x 2的未知系数。由于x = 0处的多项式的值仅为c 0,因此计算设计矩阵的伪逆(即c = (A T A )− 1 A T y)将在顶部行产生SG滤波器系数。在这种情况下,
请注意,由于是c 1 + 2 c 2 x,矩阵的第二行(计算c 1)将是平滑的导数滤波器。相同的论点适用于连续的行-它们给出平滑的高阶导数。请注意,我将矩阵缩放了35,因此第一行将与Wikipedia(上)上给出的平滑系数匹配。导数滤波器各自因其他比例因子而不同。
非均匀采样
当样本均匀分布时,滤波器系数是平移不变的,因此结果只是一个FIR滤波器。对于非均匀样本,系数将基于局部样本间距而有所不同,因此将需要构造设计矩阵并在每个样本处对其进行求逆。如果非均匀采样时间为,则构造局部坐标t n,每个中心采样时间固定为0,即
那么每个设计矩阵将具有以下形式:
如果有帮助,我对datageist描述的方法进行了C实现。免费使用,后果自负。
/**
* @brief smooth_nonuniform
* Implements the method described in /signals/1676/savitzky-golay-smoothing-filter-for-not-equally-spaced-data
* free to use at the user's risk
* @param n the half size of the smoothing sample, e.g. n=2 for smoothing over 5 points
* @param the degree of the local polynomial fit, e.g. deg=2 for a parabolic fit
*/
bool smooth_nonuniform(uint deg, uint n, std::vector<double>const &x, std::vector<double> const &y, std::vector<double>&ysm)
{
if(x.size()!=y.size()) return false; // don't even try
if(x.size()<=2*n) return false; // not enough data to start the smoothing process
// if(2*n+1<=deg+1) return false; // need at least deg+1 points to make the polynomial
int m = 2*n+1; // the size of the filter window
int o = deg+1; // the smoothing order
std::vector<double> A(m*o); memset(A.data(), 0, m*o*sizeof(double));
std::vector<double> tA(m*o); memset(tA.data(), 0, m*o*sizeof(double));
std::vector<double> tAA(o*o); memset(tAA.data(), 0, o*o*sizeof(double));
std::vector<double> t(m); memset(t.data(), 0, m* sizeof(double));
std::vector<double> c(o); memset(c.data(), 0, o* sizeof(double));
// do not smooth start and end data
int sz = y.size();
ysm.resize(sz); memset(ysm.data(), 0,sz*sizeof(double));
for(uint i=0; i<n; i++)
{
ysm[i]=y[i];
ysm[sz-i-1] = y[sz-i-1];
}
// start smoothing
for(uint i=n; i<x.size()-n; i++)
{
// make A and tA
for(int j=0; j<m; j++)
{
t[j] = x[i+j-n] - x[i];
}
for(int j=0; j<m; j++)
{
double r = 1.0;
for(int k=0; k<o; k++)
{
A[j*o+k] = r;
tA[k*m+j] = r;
r *= t[j];
}
}
// make tA.A
matMult(tA.data(), A.data(), tAA.data(), o, m, o);
// make (tA.A)-¹ in place
if (o==3)
{
if(!invert33(tAA.data())) return false;
}
else if(o==4)
{
if(!invert44(tAA.data())) return false;
}
else
{
if(!inverseMatrixLapack(o, tAA.data())) return false;
}
// make (tA.A)-¹.tA
matMult(tAA.data(), tA.data(), A.data(), o, o, m); // re-uses memory allocated for matrix A
// compute the polynomial's value at the center of the sample
ysm[i] = 0.0;
for(int j=0; j<m; j++)
{
ysm[i] += A[j]*y[i+j-n];
}
}
std::cout << " x y y_smoothed" << std::endl;
for(uint i=0; i<x.size(); i++) std::cout << " " << x[i] << " " << y[i] << " "<< ysm[i] << std::endl;
return true;
}