滤波算法是信号处理中常用的技术,用于去除噪声或提取特定频率的信号。以下是几种常见滤波算法的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;
}
选择滤波算法的建议
移动平均滤波:简单易实现,适用于平滑随机噪声
中值滤波:对脉冲噪声(突发性大幅值干扰)有很好的效果
低通滤波:适合去除高频噪声,保留信号趋势
高通滤波:适合去除低频干扰,保留信号细节
卡尔曼滤波:适合处理带有高斯噪声的动态系统,计算量相对较大
基于标准差的方法适合有明显统计特性的数据
更高级的方法可以考虑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
关键点说明
窗口大小选择:
窗口大小为3时,可以滤除单个异常值
窗口越大,滤波效果越强,但信号延迟也越大
常用奇数大小(3,5,7)以便有明确的中值
异常值处理:
原始数据中的120.5和-10.0明显是异常值
中值滤波有效地消除了这些异常值的影响
边界情况:
初始阶段窗口未填满时,当窗口大小为偶数时取中间两个数的平均值
随着数据不断输入,窗口会被填满
性能考虑:
每次滤波都需要排序,时间复杂度为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),适合大窗口场景。