Java实现滤波算法(均值、中值、高、低)

发布于:2025-08-05 ⋅ 阅读:(13) ⋅ 点赞:(0)

滤波算法是信号处理中常用的技术,用于去除噪声或提取特定频率的信号。以下是几种常见滤波算法的Java。

1. 移动平均滤波 (Moving Average Filter)

public class MovingAverageFilter {
    private double[] window;
    private int size;
    private int index;
    private double sum;

    public MovingAverageFilter(int size) {
        this.size = size;
        this.window = new double[size];
        this.index = 0;
        this.sum = 0;
    }

    public double filter(double input) {
        sum = sum - window[index] + input;
        window[index] = input;
        index = (index + 1) % size;
        return sum / size;
    }
}

// 使用示例
MovingAverageFilter maf = new MovingAverageFilter(5); // 窗口大小为5
double filteredValue = maf.filter(rawValue);

2. 中值滤波 (Median Filter)

import java.util.Arrays;

public class MedianFilter {
    private double[] window;
    private int size;
    private int index;

    public MedianFilter(int size) {
        this.size = size;
        this.window = new double[size];
        this.index = 0;
    }

    public double filter(double input) {
        window[index] = input;
        index = (index + 1) % size;
        
        double[] sorted = Arrays.copyOf(window, size);
        Arrays.sort(sorted);
        
        if (size % 2 == 1) {
            return sorted[size / 2];
        } else {
            return (sorted[size / 2 - 1] + sorted[size / 2]) / 2.0;
        }
    }
}

// 使用示例
MedianFilter mf = new MedianFilter(5); // 窗口大小为5
double filteredValue = mf.filter(rawValue);

3. 低通滤波 (Low-pass Filter)

public class LowPassFilter {
    private double alpha; // 平滑因子 (0 < alpha < 1)
    private double lastOutput;

    public LowPassFilter(double alpha) {
        this.alpha = alpha;
        this.lastOutput = 0;
    }

    public double filter(double input) {
        lastOutput = alpha * input + (1 - alpha) * lastOutput;
        return lastOutput;
    }
}

// 使用示例
LowPassFilter lpf = new LowPassFilter(0.1); // alpha值越小,滤波效果越强
double filteredValue = lpf.filter(rawValue);

4. 卡尔曼滤波 (Kalman Filter) 简化版

public class SimpleKalmanFilter {
    private double Q; // 过程噪声协方差
    private double R; // 测量噪声协方差
    private double x; // 估计值
    private double P; // 估计误差协方差
    private double K; // 卡尔曼增益

    public SimpleKalmanFilter(double Q, double R) {
        this.Q = Q;
        this.R = R;
        this.P = 1.0;
        this.x = 0;
    }

    public double filter(double measurement) {
        // 预测步骤
        P = P + Q;
        
        // 更新步骤
        K = P / (P + R);
        x = x + K * (measurement - x);
        P = (1 - K) * P;
        
        return x;
    }
}

// 使用示例
SimpleKalmanFilter kf = new SimpleKalmanFilter(0.01, 0.1);
double filteredValue = kf.filter(rawValue);

5. 高通滤波 (High-pass Filter)

public class HighPassFilter {
    private double alpha;
    private double lastInput;
    private double lastOutput;

    public HighPassFilter(double alpha) {
        this.alpha = alpha;
        this.lastInput = 0;
        this.lastOutput = 0;
    }

    public double filter(double input) {
        double output = alpha * (lastOutput + input - lastInput);
        lastInput = input;
        lastOutput = output;
        return output;
    }
}

// 使用示例
HighPassFilter hpf = new HighPassFilter(0.1);
double filteredValue = hpf.filter(rawValue);

6、标准差滤波

public static double[] removePeaksByStdDev(double[] data, double threshold) {
    // 计算均值和标准差
    double mean = 0;
    for (double d : data) mean += d;
    mean /= data.length;
    
    double variance = 0;
    for (double d : data) variance += Math.pow(d - mean, 2);
    double stdDev = Math.sqrt(variance / data.length);
    
    // 替换超过阈值的点为均值或邻域均值
    double[] filtered = Arrays.copyOf(data, data.length);
    for (int i = 0; i < filtered.length; i++) {
        if (Math.abs(filtered[i] - mean) > threshold * stdDev) {
            // 使用前后点的平均值替换
            double replacement = 0;
            int count = 0;
            if (i > 0) { replacement += filtered[i-1]; count++; }
            if (i < filtered.length-1) { replacement += filtered[i+1]; count++; }
            filtered[i] = count > 0 ? replacement / count : mean;
        }
    }
    return filtered;
}

7、使用Apache Commons Math库

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

public static double[] savitzkyGolayFilter(double[] data, int windowSize) {
    DescriptiveStatistics stats = new DescriptiveStatistics();
    stats.setWindowSize(windowSize);
    double[] filtered = new double[data.length];
    
    for (int i = 0; i < data.length; i++) {
        stats.addValue(data[i]);
        filtered[i] = stats.getMean(); // 或使用其他统计量
    }
    return filtered;
}

选择滤波算法的建议

  1. 移动平均滤波:简单易实现,适用于平滑随机噪声

  2. 中值滤波:对脉冲噪声(突发性大幅值干扰)有很好的效果

  3. 低通滤波:适合去除高频噪声,保留信号趋势

  4. 高通滤波:适合去除低频干扰,保留信号细节

  5. 卡尔曼滤波:适合处理带有高斯噪声的动态系统,计算量相对较大

  6. 基于标准差的方法适合有明显统计特性的数据

  7. 更高级的方法可以考虑Savitzky-Golay滤波器或小波变换

根据你的具体应用场景和性能要求选择合适的滤波算法。

具体案例:

模拟温度传感器数据(包含一些异常值)
public class MedianFilter {
    private double[] window; // 存储滑动窗口数据的数组
    private int size;        // 窗口大小
    private int index;       // 当前写入位置的索引

    public MedianFilter(int size) {
        this.size = size;
        this.window = new double[size]; // 初始化窗口数组
        this.index = 0;                // 从0位置开始写入
    }

    public double filter(double input) {
        // 1. 将新数据存入窗口
        window[index] = input;
        
        // 2. 更新索引(循环缓冲区)
        index = (index + 1) % size;
        
        // 3. 复制窗口数据并排序
        double[] sorted = Arrays.copyOf(window, size);
        Arrays.sort(sorted);
        
        // 4. 计算并返回中值
        if (size % 2 == 1) {
            return sorted[size / 2]; // 奇数窗口大小,取中间值
        } else {
            return (sorted[size / 2 - 1] + sorted[size / 2]) / 2.0; // 偶数窗口大小,取中间两数的平均值
        }
    }
}
public class MedianFilterExample {
    public static void main(String[] args) {
        // 模拟温度传感器数据(包含一些异常值)
        double[] rawTemperatures = {25.1, 25.2, 25.3, 120.5, 25.2, 25.3, 25.4, -10.0, 25.5, 25.6};
        
        // 创建窗口大小为3的中值滤波器
        MedianFilter mf = new MedianFilter(3);
        
        System.out.println("原始数据\t滤波后数据");
        for (double temp : rawTemperatures) {
            double filtered = mf.filter(temp);
            System.out.printf("%.1f\t\t%.1f\n", temp, filtered);
        }
    }
}

结果:

原始数据    滤波后数据
25.1        25.1    ← 窗口未填满,直接返回
25.2        25.1    ← 窗口[25.1,25.2],取平均(25.1+25.2)/2=25.15(显示为25.1)
25.3        25.2    ← 窗口[25.1,25.2,25.3],排序后中值为25.2
120.5       25.3    ← 窗口[25.2,25.3,120.5],排序后中值为25.3(异常值被滤除)
25.2        25.3    ← 窗口[25.3,120.5,25.2],排序后中值为25.3
25.3        25.3    ← 窗口[120.5,25.2,25.3],排序后中值为25.3
25.4        25.3    ← 窗口[25.2,25.3,25.4],排序后中值为25.3
-10.0       25.4    ← 窗口[25.3,25.4,-10.0],排序后中值为25.3
25.5        25.4    ← 窗口[25.4,-10.0,25.5],排序后中值为25.4
25.6        25.5    ← 窗口[-10.0,25.5,25.6],排序后中值为25.5

关键点说明

  1. 窗口大小选择

    • 窗口大小为3时,可以滤除单个异常值

    • 窗口越大,滤波效果越强,但信号延迟也越大

    • 常用奇数大小(3,5,7)以便有明确的中值

  2. 异常值处理

    • 原始数据中的120.5和-10.0明显是异常值

    • 中值滤波有效地消除了这些异常值的影响

  3. 边界情况

    • 初始阶段窗口未填满时,当窗口大小为偶数时取中间两个数的平均值

    • 随着数据不断输入,窗口会被填满

  4. 性能考虑

    • 每次滤波都需要排序,时间复杂度为O(n log n)

    • 对于大窗口或实时性要求高的场景,可以考虑更高效的实现

更高效的实现(针对大窗口)

对于大窗口,可以使用两个堆(最大堆和最小堆)来优化中值查找:

import java.util.Collections;
import java.util.PriorityQueue;

public class EfficientMedianFilter {
    private PriorityQueue<Double> maxHeap; // 存储较小的一半
    private PriorityQueue<Double> minHeap; // 存储较大的一半
    private double[] window;
    private int size;
    private int index;
    private int count;

    public EfficientMedianFilter(int size) {
        this.size = size;
        this.window = new double[size];
        this.maxHeap = new PriorityQueue<>(size/2 + 1, Collections.reverseOrder());
        this.minHeap = new PriorityQueue<>(size/2 + 1);
        this.index = 0;
        this.count = 0;
    }

    public double filter(double input) {
        if (count < size) {
            count++;
        } else {
            // 移除最旧的值
            double oldest = window[index];
            if (maxHeap.contains(oldest)) {
                maxHeap.remove(oldest);
            } else {
                minHeap.remove(oldest);
            }
        }
        
        // 添加新值
        window[index] = input;
        index = (index + 1) % size;
        
        // 平衡堆
        if (maxHeap.isEmpty() || input <= maxHeap.peek()) {
            maxHeap.offer(input);
        } else {
            minHeap.offer(input);
        }
        
        // 保持堆大小平衡
        while (maxHeap.size() > minHeap.size() + 1) {
            minHeap.offer(maxHeap.poll());
        }
        while (minHeap.size() > maxHeap.size()) {
            maxHeap.offer(minHeap.poll());
        }
        
        // 返回中值
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.peek() + minHeap.peek()) / 2.0;
        } else {
            return maxHeap.peek();
        }
    }
}

这种实现将中值查找的时间复杂度从O(n log n)降低到O(log n),适合大窗口场景。


网站公告

今日签到

点亮在社区的每一天
去签到