2015-11-05 14 views
7

Esiste un algoritmo adattativo per filtrare il rumore del giroscopio?Algoritmo adattivo per filtrare i dati del giroscopio

La mia app dispone attualmente di una finestra di dialogo di avvio per calibrare il giroscopio, in cui chiede all'utente di mettere il telefono sul tavolo per 5 secondi e registra i valori min/max dei dati del giroscopio raccolti in questi 5 secondi, quindi l'app scarta tutti valori tra quel min/max, questo è tecnicamente un filtro passa-alto.

L'algoritmo adattivo determinerebbe automaticamente questi valori min/max nel tempo, senza alcuna finestra di dialogo.

Qualcosa come memorizzare gli ultimi 100 valori e trovare min/max di questi valori, ma come faccio a sapere quali valori rappresentano il movimento e quali sono zero movimenti + rumore?

Ho esaminato il filtro Kalman, ma è per un giroscopio combinato + sensori accelerometro.

Il giroscopio nel mio telefono non solo è rumoroso, ma ha anche spostato la coordinata zero, quindi quando il telefono giace perfettamente immobile, il giroscopio segnala una rotazione costante costante.

Gyroscope data graph

+3

Nota che ignorare semplicemente i segnali di ampiezza bassa non è il filtraggio passa alto. Ignora i segnali a bassa frequenza _. E non c'è nulla nel filtraggio di Kalman che ne impedisca l'utilizzo in questa applicazione. Guarda oltre. Ha lo scopo di fare esattamente quello che intendi. – Gene

+0

Sono d'accordo sul filtro passa-alto, la mia formulazione non è corretta.Per favore indicami un esempio di codice del filtro di Kalman che coinvolge solo il giroscopio e non l'accelerometro, perché da quello che ho trovato finora richiede sia un funzionamento efficiente che non mi interessa l'angolo del telefono rispetto all'orizzonte, o se si ruota l'angolo esatto avanti e indietro non otterrà lo stesso valore di angolo calcolato, ho solo bisogno di angoli per non andare alla deriva e non tremare mentre il telefono è fermo. – pelya

+0

@pelya comunque gli angoli del magnetometro si spostano quando il dispositivo viene ruotato. Va bene? – pawelzieba

risposta

0

Ecco il pezzo di codice che ho trovato (Java, Android). L'algoritmo prende valori iniziali molto grandi per l'intervallo di filtri e li riduce gradualmente, e filtra il movimento confrontando i dati di input con il precedente intervallo di filtri e scartando 10 ultimi valori misurati se rileva il movimento.

Funziona meglio quando il telefono è ancora appoggiato sul tavolo, ma funziona comunque correttamente quando il telefono viene spostato e ruotato.

class GyroscopeListener implements SensorEventListener 
{ 
    // Noise filter with sane initial values, so user will be able 
    // to move gyroscope during the first 10 seconds, while the noise is measured. 
    // After that the values are replaced by noiseMin/noiseMax. 
    final float filterMin[] = new float[] { -0.05f, -0.05f, -0.05f }; 
    final float filterMax[] = new float[] { 0.05f, 0.05f, 0.05f }; 

    // The noise levels we're measuring. 
    // Large initial values, they will decrease, but never increase. 
    float noiseMin[] = new float[] { -1.0f, -1.0f, -1.0f }; 
    float noiseMax[] = new float[] { 1.0f, 1.0f, 1.0f }; 

    // The gyro data buffer, from which we care calculating min/max noise values. 
    // The bigger it is, the more precise the calclations, and the longer it takes to converge. 
    float noiseData[][] = new float[200][noiseMin.length]; 
    int noiseDataIdx = 0; 

    // When we detect movement, we remove last few values of the measured data. 
    // The movement is detected by comparing values to noiseMin/noiseMax of the previous iteration. 
    int movementBackoff = 0; 

    // Difference between min/max in the previous measurement iteration, 
    // used to determine when we should stop measuring, when the change becomes negligilbe. 
    float measuredNoiseRange[] = null; 

    // How long the algorithm is running, to stop it if it does not converge. 
    int measurementIteration = 0; 

    public GyroscopeListener(Context context) 
    { 
     SensorManager manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); 
     if (manager == null && manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) == null) 
      return; 
     manager.registerListener(gyro, manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE), 
      SensorManager.SENSOR_DELAY_GAME); 
    } 

    public void onSensorChanged(final SensorEvent event) 
    { 
     boolean filtered = true; 
     final float[] data = event.values; 

     if(noiseData != null) 
      collectNoiseData(data); 

     for(int i = 0; i < 3; i++) 
     { 
      if(data[i] < filterMin[i]) 
      { 
       filtered = false; 
       data[i] -= filterMin[i]; 
      } 
      else if(data[i] > filterMax[i]) 
      { 
       filtered = false; 
       data[i] -= filterMax[i]; 
      } 
     } 

     if(filtered) 
      return; 

     // Use the filtered gyroscope data here 
    } 

    void collectNoiseData(final float[] data) 
    { 
     for(int i = 0; i < noiseMin.length; i++) 
     { 
      if(data[i] < noiseMin[i] || data[i] > noiseMax[i]) 
      { 
       // Movement detected, this can converge our min/max too early, so we're discarding last few values 
       if(movementBackoff < 0) 
       { 
        int discard = 10; 
        if(-movementBackoff < discard) 
         discard = -movementBackoff; 
        noiseDataIdx -= discard; 
        if(noiseDataIdx < 0) 
         noiseDataIdx = 0; 
       } 
       movementBackoff = 10; 
       return; 
      } 
      noiseData[noiseDataIdx][i] = data[i]; 
     } 
     movementBackoff--; 
     if(movementBackoff >= 0) 
      return; // Also discard several values after the movement stopped 
     noiseDataIdx++; 

     if(noiseDataIdx < noiseData.length) 
      return; 

     measurementIteration++; 
     if(measurementIteration > 5) 
     { 
      // We've collected enough data to use our noise min/max values as a new filter 
      System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); 
      System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); 
     } 
     if(measurementIteration > 15) 
     { 
      // Finish measuring if the algorithm cannot converge in a long time 
      noiseData = null; 
      measuredNoiseRange = null; 
      return; 
     } 

     noiseDataIdx = 0; 
     boolean changed = false; 
     for(int i = 0; i < noiseMin.length; i++) 
     { 
      float min = 1.0f; 
      float max = -1.0f; 
      for(int ii = 0; ii < noiseData.length; ii++) 
      { 
       if(min > noiseData[ii][i]) 
        min = noiseData[ii][i]; 
       if(max < noiseData[ii][i]) 
        max = noiseData[ii][i]; 
      } 
      // Increase the range a bit, for safe conservative filtering 
      float middle = (min + max)/2.0f; 
      min += (min - middle) * 0.2f; 
      max += (max - middle) * 0.2f; 
      // Check if range between min/max is less then the current range, as a safety measure, 
      // and min/max range is not jumping outside of previously measured range 
      if(max - min < noiseMax[i] - noiseMin[i] && min >= noiseMin[i] && max <= noiseMax[i]) 
      { 
       // Move old min/max closer to the measured min/max, but do not replace the values altogether 
       noiseMin[i] = (noiseMin[i] + min * 4.0f)/5.0f; 
       noiseMax[i] = (noiseMax[i] + max * 4.0f)/5.0f; 
       changed = true; 
      } 
     } 

     if(!changed) 
      return; 

     // Determine when to stop measuring - check that the previous min/max range is close enough to the current one 

     float range[] = new float[noiseMin.length]; 
     for(int i = 0; i < noiseMin.length; i++) 
      range[i] = noiseMax[i] - noiseMin[i]; 

     if(measuredNoiseRange == null) 
     { 
      measuredNoiseRange = range; 
      return; // First iteration, skip further checks 
     } 

     for(int i = 0; i < range.length; i++) 
     { 
      if(measuredNoiseRange[i]/range[i] > 1.2f) 
      { 
       measuredNoiseRange = range; 
       return; 
      } 
     } 

     // We converged to the final min/max filter values, stop measuring 
     System.arraycopy(noiseMin, 0, filterMin, 0, filterMin.length); 
     System.arraycopy(noiseMax, 0, filterMax, 0, filterMax.length); 
     noiseData = null; 
     measuredNoiseRange = null; 
    } 

    public void onAccuracyChanged(Sensor s, int a) 
    { 
    } 
} 
2

Se ho capito bene, molto semplice euristica come trovare la media dei dati e la definizione di una soglia che indica vero movimento dovrebbe sia il combattimento lo spostamento origine coordinare e dare abbastanza preciso riconoscimento del picco.

// Initialize starting mean and threshold 
mean = 0 
dataCount = 0 
thresholdDelta = 0.1 

def findPeaks(data) { 
    mean = updateMean(data) 

    for point in data { 
     if (point > mean + thresholdDelta) || (point < mean - thresholdDelta) { 
      peaks.append(point) 
     } 
    } 
    max = peaks.max() 
    min = peaks.min() 

    thresholdDelta = updateThreshold(max, min, mean) 

    return {max, min} 
} 

def updateThreshold(max, min) { 
    // 1 will make threshold equal the average peak value, 0 will make threshold equal mean 
    weight = 0.5 

    newThreshold = (weight * (max - min))/2 
    return newThreshold 
} 

def updateMean(data) { 
    newMean = (sum(data) + (dataCount * mean))/(dataCount + data.size) 
    dataCount += data.size 
    return newMean 
} 

Qui abbiamo una soglia e una media che si aggiorneranno nel tempo per diventare più precisi per presentare i dati.

Se si dispone di picchi che variano in maniera forte (che il vostro più grande dei picchi può essere quadrupla il più piccolo) si vorrebbe impostare il peso della soglia di conseguenza (per il nostro esempio, 0,25 farebbe solo pescato il più piccolo dei vostri picchi, in teoria)

Modifica:.

Penso che facendo le cose come media le soglie probabilmente renderlo più resistente alla decomposizione da piccoli picchi.

thresholdCount = 0 

def updateThreshold(max, min) { 
    // 1 will make threshold equal the average peak value, 0 will make threshold equal mean 
    weight = 0.5 

    newThreshold = (weight * (max - min))/2 
    averagedThreshold = (newThreshold + (thresholdCount * thresholdDelta))/(thresholdCount + 1) 
    return averagedThreshold 
} 
+0

La domanda riguardava il calcolo di thresholdDelta, semplicemente l'uso di 0.1 non funzionerà perché i livelli di rumore variano notevolmente tra i dispositivi e anche tra i diversi assi dello stesso chip giroscopico, e l'impostazione di qualsiasi valore fisso ridurrà la sensibilità del giroscopio. Quindi l'algoritmo usa 0.1 come valore iniziale, quindi aumenta o diminuisce nel tempo a seconda del livello di rumore. Non ho bisogno di nulla di complicato, solo qualcosa come l'aggiunta (ultimo valore di rumore) * 0,05 su ogni iterazione funzionerà correttamente. – pelya

+0

@pelya sì che tocca quello che stavo dicendo alla fine. Puoi aggiornare la soglia semplicemente facendo una media tra la media e ciò che il tuo algoritmo prevede come picchi. Naturalmente, all'inizio sarebbe terribilmente inaccurato, ma man mano che aumenta costantemente troverà una vera soglia. È anche possibile pesare in modo diverso il picco e le componenti medie dell'operazione di calcolo della media se i dati sul rumore sono estremamente radicali. Fammi sapere se lo vuoi nel codice. –

+0

Sì, per favore, dammi un pezzo di codice. Non prenderebbe (picco + media)/2 mentre la soglia successiva taglia tutti i picchi nel mezzo, scartando i dati utili? http://i.imgur.com/mDzIbai.jpg – pelya

Problemi correlati