java频谱计算的工具类

直接上代码,逻辑还算比较清楚

public class SpectrumHelper {

    /**
     * 计算频谱
     * @param data 采样数据
     * @param samplingInterval 采样间隔,以秒为单位
     * @param needFillZero 是否需要补零
     * @param flyLen 参与傅里叶变换的长度
     */
    public static SpectrumBean caculateSpectrum(double[] data, double samplingInterval, boolean needFillZero, int flyLen){
        if(data == null || data.length == 0){
            return null;
        }
        int dataLen = data.length;
        double[] partakeFLYArr = null;//参与傅里叶变换的数组
        if(needFillZero){ //需要补零
            if(flyLen < dataLen){
                return null;
            }
            partakeFLYArr = new double[flyLen];
            System.arraycopy(data, 0, partakeFLYArr, 0, dataLen);
            for(int i = dataLen; i < flyLen; i++){
                partakeFLYArr[i] = 0;
            }
        }else{
            partakeFLYArr = data;
            flyLen = dataLen;
        }
        //进行傅里叶变换,使用的是commons-math包里面的快速傅里叶算法
        FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
        Complex[] complexArr = fft.transform(partakeFLYArr, TransformType.FORWARD);

        SpectrumBean spectrumBean = new SpectrumBean();
        double[] amplitude = new double[flyLen / 2];

        //计算振幅,除以的都是采样点的长度,而不是傅里叶变化的长度
        Complex complex = complexArr[0];
        amplitude[0] = Math.sqrt(Math.abs(complex.getReal()) * Math.abs(complex.getReal())
                + Math.abs(complex.getImaginary()) * Math.abs(complex.getImaginary())) * 1.0 / dataLen;
        for(int i = 1; i < flyLen / 2; i++){
            complex = complexArr[i];
            amplitude[i] = Math.sqrt(Math.abs(complex.getReal()) * Math.abs(complex.getReal())
                    + Math.abs(complex.getImaginary()) * Math.abs(complex.getImaginary())) * 2.0 / dataLen;
        }
        spectrumBean.setAmplitude(amplitude);//设置振幅

        //计算频率分辨率,除以的是傅里叶变换的长度,而不是采样点的长度
        double Fs = 1.0 / samplingInterval;//采样频率
        spectrumBean.setFrequencyResolution(Fs / flyLen);//设置频率分辨率

        return spectrumBean;
    }

}

SpectrumBean类表示的是频谱计算的结果,类定义如下:

/**
 * 用于保存频谱计算的结果
 */
public class SpectrumBean {

    /**
     * 频率分辨率
     */
    private double frequencyResolution;

    /**
     * 保存振幅信息,长度是参与傅里叶变换的长度,不是采样点的长度
     */
    private double[] amplitude;

    public double getFrequencyResolution() {
        return frequencyResolution;
    }

    public void setFrequencyResolution(double frequencyResolution) {
        this.frequencyResolution = frequencyResolution;
    }

    public double[] getAmplitude() {
        return amplitude;
    }

    public void setAmplitude(double[] amplitude) {
        this.amplitude = amplitude;
    }
}

 

测试的代码如下:

   /**
     * 正常的输出波形,正常的傅里叶变换,没有补零的操作
     * 频率分辨率 = 采样频率 / 参与傅里叶变换的点数,注意是参与傅里叶变换的点数而非采样点数,因为可能补零
     * 傅里叶变换结果的频率计算,第n个点的频率 = 频率分辨率 * (n - 1)
     * 所谓频谱,横轴是频率,纵轴是振幅,也就是频率振幅谱
     */
    @Test
    public void test3(){
        //模拟采样
        int Fs = 100;//采样频率
        int N = 256;//采样点数
        double[] wave = generateWaveNormal(Fs, N);
        log(Arrays.toString(wave));

        //对采样结果做频谱分析
        SpectrumBean spectrumBean = SpectrumHelper.caculateSpectrum(wave, 1.0f / Fs, false, 0);
        /*for(int i = 0; i < spectrumBean.getAmplitude().length; i++){
            log(spectrumBean.getAmplitude()[i]);
        }*/
        log(spectrumBean.getFrequencyResolution());

    }


    /**
     * 后面补零
     * 频率分辨率 = 采样频率 / 参与傅里叶变换的点数,注意是参与傅里叶变换的点数而非采样点数,
     * 在补零的情况下,参与傅里叶变换的点数是大于采样点数
     * 傅里叶变换结果的频率计算,第n个点的频率 = 频率分辨率 * (n - 1)
     * 所谓频谱,横轴是频率,纵轴是振幅,也就是频率振幅谱
     */
    @Test
    public void test4(){
        //模拟采样
        int Fs = 100;//采样频率
        int N = 256;//采样点数
        int WANT_N = 1024;//参与傅里叶变换的点数,实际上只采了256个点,后面的都补零了
        double[] wave = generateWaveNormal(Fs, N);
        log(Arrays.toString(wave));

        //对采样结果做频谱分析
        SpectrumBean spectrumBean = SpectrumHelper.caculateSpectrum(wave, 1.0 / Fs, true, WANT_N);
        /*for(int i = 0; i < spectrumBean.getAmplitude().length; i++){
            log(spectrumBean.getAmplitude()[i]);
        }*/
        log(spectrumBean.getFrequencyResolution());

    }

    
    private void log(Object obj){
        System.out.println(obj);
    }

    
    /**
     * 生成一段模拟信号,模拟采样
     * @param fs 采样频率
     * @param n 采样点数
     * 模拟的这段信号包括的频率有10hz, 15hz, 20hz, 26.5hz,这段信号包括直流信号
     * 注意:根据采样定理,采样频率必须大于信号频率的2倍
     * 因此,传递过来的采样频率务必要大于26.5 * 2
     */
    private double[] generateWaveNormal(float fs, int n) {
        double pi = Math.PI;
        double[] res = new double[n];
        float interval = 1 / fs;//采样间隔,单位是s
        for (int i = 0; i < n; i++) {
            double t = interval * i;
            res[i] = 3 + 1 * Math.cos(2 * pi * 10 * t) + 2 * Math.sin(2 * pi * 15 * t + Math.toRadians(30))
                    + 3 * Math.cos(2 * pi * 20 * t + Math.toRadians(-30)) + 4 * Math.sin(2 * pi * 26.5 * t + Math.toRadians(60));
        }
        return res;
    }

 

测试的结果打印到控制台,然后复制到excel种,使用excel画图

不补零的图如下:

补零的图如下:

 

结果分析

横坐标频率计算,比如第n个点的频率 = (n -1) * 频率分辨率,频率分辨率的计算见上面

纵坐标表示的是振幅。

频谱图上的信号和原始信号是能正好对应上的。

要进行频谱计算,可以使用 Python 的 `numpy` 库进行 FFT 转换,然后计算功率谱密度。下面是一个使用 `numpy` 进行频谱计算的示例代码: ```python import numpy as np import matplotlib.pyplot as plt # 生成测试信号 t = np.linspace(0, 1, 1000, endpoint=False) y = np.sin(2 * np.pi * 5 * t) + np.sin(2 * np.pi * 10 * t) # 进行 FFT 转换 fft_y = np.fft.fft(y) # 计算频谱密度 psd = np.abs(fft_y) ** 2 / len(y) # 计算频率数组 freqs = np.fft.fftfreq(len(y), t[1]-t[0]) # 取功率谱密度的前一半(因为对称性,后一半无用) psd_half = 2.0 * psd[:len(y)//2] # 绘制频谱图 plt.plot(freqs[:len(y)//2], 10*np.log10(psd_half)) plt.xlabel('Frequency (Hz)') plt.ylabel('PSD (dB)') plt.show() ``` 在这个示例代码中,首先生成一个测试信号 `y`,其中包含两个正弦波,频率分别为 5 Hz 和 10 Hz。然后,使用 `np.fft.fft()` 方法对 `y` 进行 FFT 转换,得到频域信号 `fft_y`。使用 `np.abs()` 方法计算 `fft_y` 的模,再平方得到功率谱密度 `psd`。然后,使用 `np.fft.fftfreq()` 方法计算频率数组 `freqs`,然后取功率谱密度的前一半,并通过 dB 转换得到 dBFS(dB full scale)单位的频谱幅度。最后,使用 `matplotlib` 库绘制频谱图。 需要注意的是,进行频谱计算时,需要对信号进行窗函数处理,以避免窗口泄漏现象。可以使用 `numpy` 中的 `hamming`、`hanning`、`blackman` 等窗函数进行处理。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值