信号处理(Java实现)

4
我有一个传感器正在读取数据,目前的代码是用Java编写的,但我认为问题不是特定于语言,而是更多地涉及方法。
传感器产生高低脉冲信号,类似于心跳。然而,“高”脉冲并不总是相同的电平,也不是“低”。我感兴趣的是相对差异。然而,仅仅这样还不够,因为在单个“会话”中,高低值也可能发生变化(想象一下曲线中点)。
我已经包含了4种“类型”的信号图像,我希望能够处理它们。左上角是“理想”的情况,我相当确定我已经可以处理它,但其他三个情况则更常见且难以处理。
我的当前方法是查找数据的平均值,并查看该点被穿越的次数,这将告诉我有多少个高低脉冲。
我想知道是否有一种简单的方法来检测高低脉冲,而不使用平均方法。

我认为你需要一些类型的平均方法,但你不一定要对整个信号进行平均。你可以尝试使用矩形滤波器。 - S. Miller
你是想知道峰值和随后的低谷之间的平均差异(反之亦然),还是想知道波的频率? - Richard Tingle
我需要的是波的频率。 - Zack Newsham
4个回答

9
当您提到要提取波的频率时,我首先想到了傅里叶变换;它将信号从时间域转换为频率域。给定以下样本波形:

enter image description here

这是一条正弦波,我已经添加了噪声和趋势。底层正弦波的频率为1.5Hz。
你会得到这个傅里叶变换。

enter image description here

在这里你可以看到0Hz处的大幅响应,这是线性趋势,在这种情况下我们可以忽略它。之后你可以看到1.5Hz处响应中的一个峰值,即我们输入信号的频率。换句话说,一旦你进行傅里叶变换,你的结果就是数据点中最大值(当你移除了极低频的结果后)。

Java代码

Apachi commons有一个快速傅里叶变换类,我用它来创建这个变换。它以采样波形数据为输入,并输出一个复数,复数的模数(实部平方加虚部平方的平方根)等于该频率处的能量。输出数组中的每个条目i都对应于频率i*samplingFrequency/noOfSamples。

然而,下面的Java代码基本上为您处理了这些问题。唯一的问题是快速傅里叶变换的输入条目数量必须是2的幂。

import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;

public class FourierTest {

    public static void main(String[] args) {
      
        double samplingFrequency=10; //hz, You will know this from your data and need to set it here
        
        

        double[] frequencyDomain = new double[input.length];

        FastFourierTransformer transformer = new FastFourierTransformer(DftNormalization.STANDARD);
        try {           
            Complex[] complex = transformer.transform(input, TransformType.FORWARD);
            
            for (int i = 0; i < complex.length; i++) {               
                double real = (complex[i].getReal());
                double imaginary = (complex[i].getImaginary());

                frequencyDomain[i] = Math.sqrt((real * real) + (imaginary * imaginary));
            }

        } catch (IllegalArgumentException e) {
            System.out.println(e);
        }

        //only to frequencyDomain.length/2 since second half is mirror image or first half
        for(int i=0;i<frequencyDomain.length/2;i++){
            double frequency=samplingFrequency*i/frequencyDomain.length;
            System.out.println("Frequency: " + frequency + "\t\tEnergyComponent: " + frequencyDomain[i]);
        }
    }
    
    static double[]  input = new double[]{
            0.017077407 , //sample at 0 seconds
            1.611895528 , //sample at 0.1 seconds
            2.063967663 , //sample at 0.2 seconds
            1.598492541 , //etc
            0.184678933 ,
            0.02654732  ,
            0.165869218 ,
            1.026139745 ,
            1.914179294 ,
            2.523684208 ,
            1.71795312  ,
            0.932131202 ,
            1.097366772 ,
            1.107912105 ,
            2.843777623 ,
            2.503608192 ,
            2.540595787 ,
            2.048111122 ,
            1.515498608 ,
            1.828077941 ,
            2.400006658 ,
            3.562953532 ,
            3.34333491  ,
            2.620231348 ,
            2.769874641 ,
            2.423059324 ,
            2.11147835  ,
            3.473525478 ,
            4.504105599 ,
            4.325642774 ,
            3.963498242 ,
            2.842688545 ,
            2.573038184 ,
            3.434226007 ,
            4.924115479 ,
            4.876122332 ,
            4.553580015 ,
            3.92554604  ,
            3.804585546 ,
            3.476610932 ,
            4.535171252 ,
            5.398007229 ,
            5.729933758 ,
            5.573444511 ,
            4.487695977 ,
            4.133046459 ,
            4.796637209 ,
            5.091399617 ,
            6.420441446 ,
            6.473462022 ,
            5.663322311 ,
            4.866446009 ,
            4.840966187 ,
            5.329697081 ,
            6.746910181 ,
            6.580067494 ,
            7.140083322 ,
            6.243532245 ,
            4.960520462 ,
            5.100901901 ,
            6.794495306 ,
            6.959324497 ,
            7.194674358 ,
            7.035874424 

        };
}

这非常有趣,我现在没有时间深入回答,但我看到可能会引起问题的一些事情:如果波的频率小于1会发生什么?是否可能区分真实结果和“非常低频率结果”? - Zack Newsham
@ZackNewsham Fourier的好处在于它的单位是任意的。重要的是采样频率比你所关心的频率高,你至少在采样时间内具备波的几个周期,且线性部分不会抹去波形(例如,波形从0到1需要20秒,但是在此期间线性运动已经达到1000,这是行不通的)。因此,如果看起来像我的第一个图,无论是以秒、毫秒还是千年为单位,你都会得到一个看起来像第二个图的图形;但它的单位将会是KHz、Hz或nHz。 - Richard Tingle

2

找到变化方向改变的点。在微积分术语中,这是二阶导数。

基本上你要找的是 sign(f(x)-f(x-1))sign(f(x+1)-f(x)) 相反的点。

根据您的数据采集方式,有几种方法可以实现此目的。如果您提供有关特定问题的更多信息,我将编辑答案以提供更多帮助。


如果信号不对称,这仍然有效吗?即向上的斜率比向下的陡峭,或者反之? - Zack Newsham
是的,你所做的就是找到拐点。请注意,这种方法假定你得到的数据点沿着曲线而不是高点、低点、高点的模式。 - Adam Yost
2
仅仅改变方向是不够的(由于信号噪音),我最终寻找了一个明显的方向改变,这证明相对准确。 - Zack Newsham

1

看起来我需要知道这个波的频率才能使用它。 - Zack Newsham
1
是的,但您可以从低截止频率开始,并逐渐增加它,直到只有非常高的频率被通过。 - adjan

0
据我理解,这里需要的是相对于相邻低谷的峰值高度。我创建了一个库,可以使用我称之为“尖峰检测”的方法处理这种情况。该库名为jDSP,提供了一个名为Spike Detection的实用程序。
可以在此处找到尖峰检测的演示,可以识别相对于相邻低谷的峰值高度,然后使用特定值进行过滤。

Image

橙色的叉号代表检测到的高峰。


如果有任何负评的原因,请告知,谢谢。我理解这个问题是检测局部峰值的问题,可以使用这个特定库的实用工具来解决。 - Sambit Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接