求助:加速度计数组双峰值检测算法优化(规避小峰干扰)
Hey there, let's fix your peak detection logic to avoid small peak interference and meet your requirements: find the second peak, trace back to a valley below 5, then locate the first peak near position 40. Here's how to refactor your code step by step:
Key Issues in Original Code
Your current logic just tracks the two largest values in the array, which doesn't account for the required sequence (peak1 → valley <5 → peak2) or filter out small, noisy peaks. We need to implement local peak/valley detection with explicit constraints to get reliable results.
Modified Code with Detailed Explanations
First, we'll add helper methods to detect valid peaks/valleys (using a sliding window to filter noise), then rework the detection flow to follow your desired logic:
public class Accelerometer extends AppCompatActivity implements SensorEventListener { private TextView xText, yText, zText, resultText, resultText2; private Sensor mySensor; private SensorManager SM; int pos = 0; double array[] = new double[10000]; // Adjust this window size based on your sampling rate (filters small peaks) private static final int PEAK_WINDOW_SIZE = 3; // Range around position 40 to search for the first peak private static final int PEAK1_TARGET_RANGE = 10; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_accelerometer); // Initialize sensor manager and accelerometer SM = (SensorManager) getSystemService(SENSOR_SERVICE); mySensor = SM.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); SM.registerListener(this, mySensor, SensorManager.SENSOR_DELAY_NORMAL); // Setup stop button Button buttonStop = findViewById(R.id.buttonStop); buttonStop.setOnClickListener(v -> stopSensor()); // Assign text views xText = findViewById(R.id.xText); yText = findViewById(R.id.yText); zText = findViewById(R.id.zText); resultText = findViewById(R.id.resultText); resultText2 = findViewById(R.id.resultText2); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // Not used } @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; // Update UI with raw accelerometer values xText.setText("X: " + String.format("%.2f", x)); yText.setText("Y: " + String.format("%.2f", y)); zText.setText("Z: " + String.format("%.2f", z)); // Calculate magnitude and store in array double mag = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)); if (pos < array.length) { array[pos] = mag; pos++; } } // Helper: Check if a position is a local peak (higher than all neighbors in the window) private boolean isLocalPeak(double[] data, int index) { // Skip edge positions that can't have a full window if (index < PEAK_WINDOW_SIZE || index >= pos - PEAK_WINDOW_SIZE) { return false; } double current = data[index]; // Verify all surrounding points are smaller than current for (int i = index - PEAK_WINDOW_SIZE; i <= index + PEAK_WINDOW_SIZE; i++) { if (i != index && data[i] >= current) { return false; } } return true; } // Helper: Check if a position is a valid valley (lower than neighbors, and <5) private boolean isLocalValley(double[] data, int index) { if (index < PEAK_WINDOW_SIZE || index >= pos - PEAK_WINDOW_SIZE) { return false; } double current = data[index]; if (current >= 5) return false; // Valley must be below 5 // Verify all surrounding points are larger than current for (int i = index - PEAK_WINDOW_SIZE; i <= index + PEAK_WINDOW_SIZE; i++) { if (i != index && data[i] <= current) { return false; } } return true; } private void stopSensor() { SM.unregisterListener(this); // Save collected data to file (fixed newline character from /n to \n) File path = getApplicationContext().getExternalFilesDir(null); File file = new File(path, "my_file-name.txt"); try (FileOutputStream outputStream = new FileOutputStream(file)) { for (int i = 0; i < pos; i++) { String s = Double.toString(array[i]) + ","; outputStream.write(s.getBytes()); } outputStream.write("\n".getBytes()); } catch (Exception e) { e.printStackTrace(); } // Start peak-valley detection logic try { if (pos < PEAK_WINDOW_SIZE * 3) { resultText.setText("Not enough data"); resultText2.setText("Not enough data"); return; } // Step 1: Find the second valid local peak int peak2Index = -1; int peakCount = 0; for (int i = PEAK_WINDOW_SIZE; i < pos - PEAK_WINDOW_SIZE; i++) { if (isLocalPeak(array, i)) { peakCount++; if (peakCount == 2) { peak2Index = i; break; } } } if (peak2Index == -1) { resultText.setText("No second peak found"); resultText2.setText("No second peak found"); return; } // Step 2: Trace back from peak2 to find the first valid valley (<5) int valleyIndex = -1; for (int i = peak2Index - PEAK_WINDOW_SIZE; i >= PEAK_WINDOW_SIZE; i--) { if (isLocalValley(array, i)) { valleyIndex = i; break; } } if (valleyIndex == -1) { resultText.setText("No valid valley (<5) found"); resultText2.setText("Peak2 at: " + peak2Index); return; } // Step 3: Find first peak near position 40 (prioritize target range) int peak1Index = -1; // Define range around 40, clamped to valid data bounds int rangeStart = Math.max(PEAK_WINDOW_SIZE, 40 - PEAK1_TARGET_RANGE); int rangeEnd = Math.min(valleyIndex - PEAK_WINDOW_SIZE, 40 + PEAK1_TARGET_RANGE); // First check the target range around 40 for (int i = rangeEnd; i >= rangeStart; i--) { if (isLocalPeak(array, i)) { peak1Index = i; break; } } // If no peak in target range, fall back to the last peak before the valley if (peak1Index == -1) { for (int i = valleyIndex - PEAK_WINDOW_SIZE; i >= PEAK_WINDOW_SIZE; i--) { if (isLocalPeak(array, i)) { peak1Index = i; break; } } } // Update UI with results if (peak1Index != -1) { resultText.setText("Peak1 at: " + peak1Index); resultText2.setText("Peak2 at: " + peak2Index); } else { resultText.setText("No Peak1 found"); resultText2.setText("Peak2 at: " + peak2Index); } } catch (Exception e) { e.printStackTrace(); resultText.setText("Error: " + e.getMessage()); } } }
Key Improvements Breakdown
- Noise Filtering: The
isLocalPeakandisLocalValleymethods use a sliding window to ignore small, spiky peaks—only values that are the clear highest/lowest in their surrounding area count as valid. - Sequence Enforcement: We strictly follow your desired flow: find the second peak first, then trace back to a valley below 5, then locate the first peak.
- Position Constraint for Peak1: We prioritize searching for Peak1 in a configurable range around position 40. If no peak is found there, we fall back to the last valid peak before the valley.
- File Write Fix: Corrected the newline character from
/nto\nso your saved data file has proper line breaks.
Tuning Tips
- Adjust
PEAK_WINDOW_SIZE: Increase this value (e.g., to 5) if small peaks are still slipping through; decrease it if valid peaks are being missed. - Modify
PEAK1_TARGET_RANGE: Widen or narrow the range around position 40 to adjust how strictly we enforce the "near 40" requirement.
内容的提问来源于stack exchange,提问作者Joe Dobbs

