采样信号和傅里叶变换
香农采样定理:采样频率只要大于信号中最高频率的2倍,采样值就可以包含原始信号所有信息
假设如下采样信号
int sampleCount = 10000;
var time = new double[sampleCount];
var xs = new double[sampleCount];
var ys_real = new double[sampleCount];
var ys_img = new double[sampleCount];
//10KHz采样,生成实部数据 和 X轴数据
for(int i=0;i< sampleCount; i++)
{
time[i] = i;
var x= ((double)i) / ((double)sampleCount);
xs[i] = x;
ys_real[i] =
0.5 * Math.Sin(2 * Math.PI * 10 * x ) //叠加10Hz信号
+ 0.2 * Math.Sin(2 * Math.PI * 80 * x + Math.PI / 4) //叠加80Hz信号
+ 0.05 * Math.Sin(2 * Math.PI * 200 * x + Math.PI / 6)//叠加200Hz信号
+ 0.07 * Math.Sin(2 * Math.PI * 400 * x + Math.PI / 8)//叠加400Hz信号
+ 10;////叠加直流分量
}
//绘制10K个点
_timePlot.Plot.Clear();
_timePlot.Plot.Add.ScatterLine(xs.ToArray(), ys_real.ToArray());
_timePlot.Plot.Axes.AutoScale();
_timePlot.Refresh();
//傅里叶向前变换FFT default选型需要导入2的N次方数据 这里选Matlab不需要2的幂数
Fourier.Forward(ys_real, ys_img, MathNet.Numerics.IntegralTransforms.FourierOptions.Matlab);
var result = new double[ys_real.Length];
for (int i = 0; i < result.Count(); i++)
{
var temp = new Complex32((float)ys_real[i], (float)ys_img[i]);
result[i] = temp.Magnitude;//复数的模
//归一化
//0HZ的直流分量 =振幅/采样点数N
if (i == 0) { result[i] = result[i] / sampleCount; }
else
{
//其他==振幅/采样点数N/2
result[i] = result[i] / (sampleCount / 2);
}
}
//Scottplot绘制FFT波形
_fftPlot.Plot.Clear();
_fftPlot.Plot.Add.ScatterLine(time.Take(sampleCount/2).ToArray(), result.Take(sampleCount/2).ToArray());
_fftPlot.Plot.Axes.AutoScale();
_fftPlot.Refresh();
频率和整幅都可以匹配
//去除高频部分大于70的频率信号
for (int i = 0; i < ys_real.Length; i++)
{
if (i > 70)
{
ys_real[i] = 0;
ys_img[i] = 0;
}
}
//反傅里叶变化
Fourier.Inverse(ys_real, ys_img, FourierOptions.Matlab);
//绘制过滤过的波形 图上黄色波形
_timePlot.Plot.Add.ScatterLine(xs.ToArray(), ys_real.ToArray());
_timePlot.Plot.Axes.AutoScale();
_timePlot.Refresh();
只剩下0.5 * Math.Sin(2 * Math.PI * 10 * x )+ 10;的时域信号
//TIME->FFT-- > FILTER(noise) - IFFT - TIME
用途:
- 计算信号的频域信号分析特征
- 对信号进行预处理,剔除高频毛刺部分
以上的步骤模拟的频率正好是整数的频次10 80之类 单位是1HZ,现在我们扩展到0.1HZ
采样10K,采样率1K/S
int N = 10000;//采样10K个
int fs = 1000;//采样频率
var n = new double[N];
var timex = new double[N];
var ys_real = new double[N];
var ys_img = new double[N];
var oneSampleTime = 1d / fs;
var fftwindow = MathNet.Numerics.Window.Hann(N);
//生成实部数据 和 X轴数据
for (int i = 0; i < N; i++)
{
n[i] = i;
timex[i] = oneSampleTime * i;
ys_real[i] =
0//叠加直流分量
+ 0.5 * Math.Sin(2 * Math.PI * 10.5 * timex[i]) //叠加10Hz信号
+ 0.2 * Math.Sin(2 * Math.PI * 40.777 * timex[i]) //叠加10Hz信号
+ 0.1 * Math.Sin(2 * Math.PI * 80 * timex[i] + Math.PI / 4) //叠加80Hz信号
+ 0.03 * Math.Sin(2 * Math.PI * 200 * timex[i] + Math.PI / 6)//叠加200Hz信号
+ 0.01 * Math.Sin(2 * Math.PI * 400 * timex[i] + Math.PI / 8)//叠加400Hz信号
;
}
//绘制10K个点
_timePlot.Plot.Clear();
_timePlot.Plot.Add.ScatterLine(timex.ToArray(), ys_real.ToArray());
_timePlot.Plot.Axes.AutoScale();
_timePlot.Refresh();
//傅里叶向前变换FFT default选型需要导入2的N次方数据 这里选Matlab不需要2的幂数
Fourier.Forward(ys_real, ys_img, MathNet.Numerics.IntegralTransforms.FourierOptions.Matlab);
var result = new double[ys_real.Length];
for (int i = 0; i < result.Count(); i++)
{
var temp = new Complex32((float)ys_real[i], (float)ys_img[i]);
result[i] = temp.Magnitude;//复数的模
//归一化
//0HZ的直流分量 =振幅/采样点数N
if (i == 0) { result[i] = result[i] / N; }
else
{
//其他==振幅/采样点数N/2
result[i] = result[i] / (N / 2);
}
}
var frequenceArray = new double[result.Length];
for (int i = 0; i < frequenceArray.Length; i++)
{
frequenceArray[i] = i * (double)fs / N;
}
//Scottplot绘制FFT波形
_fftPlot.Plot.Clear();
_fftPlot.Plot.Add.ScatterLine(frequenceArray.ToArray(), result.Take(N / 2).ToArray());
_fftPlot.Plot.Axes.AutoScale();
_fftPlot.Refresh();
左图是时域波形
右图是频域波形 注意这次我们插入了两个非整数频率的信号10.5 和 40.777HZ 对40.777的区域放大后可以看到明显的频率泄露,边上的频率也有了振幅
0.5 * Math.Sin(2 * Math.PI * 10.5 * timex[i])
0.2 * Math.Sin(2 * Math.PI * 40.777 * timex[i])
[参考文档]因为离散傅里叶变换DFT只能输出在Fs/N(fs=1000/N=10000)的频率点上的功率,所以当输入频率不在Fs/N的整数倍时,在dft的输出上就没有与输入频率相对应得点(dft输出是离散的),那么输入频率就会泄漏到所有的输出点上,具体的泄漏分布取决于所采用的窗的连续域复利叶变换
这里带入矩形窗概念,如果我们对生成ys_real[i]数组每个元素都1那就是给时域信号加矩形窗,因为都是1,所以原始的时域信号默认就加了矩形窗
那矩形窗的默认定义就是N的数组,每个元素都是1,ys_real[i]=ys_real[i]fftwindow[i];
现在我对信号采样数N进行扩容1倍2N(N-2N区间的时域值补0) 根据上述原理可以得到0.1/2=0.05HZ的信号
ys_real[i] =
0//叠加直流分量
+ 0.5 * Math.Sin(2 * Math.PI * 10.5 * timex[i]) //叠加10Hz信号
+ 0.2 * Math.Sin(2 * Math.PI * 40.55 * timex[i]) //叠加10Hz信号
+ 0.1 * Math.Sin(2 * Math.PI * 80 * timex[i] + Math.PI / 4) //叠加80Hz信号
+ 0.03 * Math.Sin(2 * Math.PI * 200.5 * timex[i] + Math.PI / 6)//叠加200Hz信号
+ 0.01 * Math.Sin(2 * Math.PI * 400 * timex[i] + Math.PI / 8)//叠加400Hz信号
;
//补零
var zero = new double[N * 2];
Array.Copy(ys_real,zero, ys_real.Length);
ys_real = zero;
ys_img = new double[N * 2];
观测到40.55Hz的频率可以抓到但是两边都有泄露,那我们需要加个hann窗什么的观察一下改善
int N = 10000;//采样10K个
int fs = 1000;//采样频率
var n = new double[N];
var timex = new double[N];
var ys_real = new double[N];
var ys_img = new double[N];
var oneSampleTime = 1d / fs;
var fftwindow = MathNet.Numerics.Window.Hann(N);
//生成实部数据 和 X轴数据
for (int i = 0; i < N; i++)
{
n[i] = i;
timex[i] = oneSampleTime * i;
ys_real[i] =
0//叠加直流分量
+ 0.5 * Math.Sin(2 * Math.PI * 10.5 * timex[i]) //叠加10Hz信号
+ 0.2 * Math.Sin(2 * Math.PI * 40.55 * timex[i]) //叠加10Hz信号
+ 0.1 * Math.Sin(2 * Math.PI * 80 * timex[i] + Math.PI / 4) //叠加80Hz信号
+ 0.03 * Math.Sin(2 * Math.PI * 200.5 * timex[i] + Math.PI / 6)//叠加200Hz信号
+ 0.01 * Math.Sin(2 * Math.PI * 400 * timex[i] + Math.PI / 8)//叠加400Hz信号
;
//先加窗后补0
if (i < N)
{
ys_real[i] = ys_real[i] * fftwindow[i ]; //时域信号的乘积=频域信号的卷积
}
//补零
var zero = new double[N * 2];
Array.Copy(ys_real,zero, ys_real.Length);
ys_real = zero;
ys_img = new double[N * 2];
//if (i > N / 2) { ys_real[i] = 0; }
}
//绘制10K个点
_timePlot.Plot.Clear();
_timePlot.Plot.Add.ScatterLine(timex.ToArray(), ys_real.ToArray());
_timePlot.Plot.Axes.AutoScale();
_timePlot.Refresh();
//傅里叶向前变换FFT default选型需要导入2的N次方数据 这里选Matlab不需要2的幂数
Fourier.Forward(ys_real, ys_img, MathNet.Numerics.IntegralTransforms.FourierOptions.Matlab);
var result = new double[ys_real.Length];
for (int i = 0; i < result.Count(); i++)
{
var temp = new Complex32((float)ys_real[i], (float)ys_img[i]);
result[i] = temp.Magnitude;//复数的模
//归一化
//0HZ的直流分量 =振幅/采样点数N
if (i == 0) { result[i] = result[i] / N; }
else
{
//其他==振幅/采样点数N/2
result[i] = result[i] / (N / 2);
}
}
var frequenceArray = new double[result.Length];
for (int i = 0; i < frequenceArray.Length; i++)
{
frequenceArray[i] = i * (double)fs / (2*N);
}
//Scottplot绘制FFT波形
_fftPlot.Plot.Clear();
_fftPlot.Plot.Add.ScatterLine(frequenceArray.ToArray(), result.Take(N / 2).ToArray());
_fftPlot.Plot.Axes.AutoScale();
_fftPlot.Refresh();
加窗后的时域波形N-2N区间的没有绘制出来就是一根直线;
可以观察到泄露小了很多;
思路就是采集一段时间长度的信号,如果要观察的频率有小数点几位就要适当的进行扩容(补0),扩容前加窗函数抑制泄露
参考文档
[傅里叶变化-Math.Net中Fourier变换使用详解]
https://blog.csdn.net/u011555996/article/details/136403452
[如何 FFT(快速傅里叶变换) 求幅度、频率(超详细 含推导过程)]
https://blog.csdn.net/weixin_39591031/article/details/110392352
[信号处理之快速傅里叶变换(FFT)-通俗易懂]
https://blog.csdn.net/zhang0558/article/details/136840352
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本