Filtern eines digitalen Signals online in Echtzeit mit Python


7

Ich versuche derzeit, ein Bandpassfilter in Echtzeit auf ein Signal anzuwenden. Es kommen Samples mit einer konstanten Sampling-Rate herein und ich möchte das entsprechende bandpassgefilterte Signal berechnen.

Was wäre der beste Weg, dies zu tun? Muss ich das gesamte Signal (oder zumindest ein großes Stück) jedes Mal filtern, wenn ein paar neue Samples eingehen, oder gibt es eine Möglichkeit (wie die gleitende DFT), den neuen Teil des gefilterten Signals effizient zu bestimmen? Signal?

Ich möchte einen Butterworth-Filter verwenden (für die Offline-Analyse verwende ich derzeit die Butter und den Filter von scipy). Ich weiß, dass diese Funktion eine Filterverzögerung zurückgeben kann, aber ich weiß nicht, wie ich damit ein konstantes Signal erhalten soll.

Antworten:


1

Die grundlegende Mechanik für die Durchführung der digitalen Auido-Verarbeitung in Echtzeit unter einer X-Windows-basierten Allzweck-PC-Plattform basiert auf der Verwendung einer doppelt gepufferten Architekturfamilie.

In dieser Architektur wird der Ton, der über ein Mikrofon / Line-In kommt, zuerst über den Soundkarten-ADC in Samples umgewandelt und dann mit der vom Benutzer ausgewählten Abtastrate Fs in einen Eingangspuffer gefüllt. Wenn dieser Puffer voll ist, benachrichtigt zuerst die Soundkartenhardware das Betriebssystem und dann das Betriebssystem Ihr Programm. Ihr Programm kann dann auf den Block zugreifen und mit der Verarbeitung von Samples in diesem Block beginnen.

Während Sie mit dem aktuellen Block beschäftigt sind, hat Ihr Programm jedoch bereits einen weiteren (zweiten) Puffer bereitgestellt, der von der Audiokarte mit den Samples gefüllt wird, die ankommen, während Sie den zuvor gefüllten Puffer verarbeiten. Wenn dieser derzeit verfügbare Puffer vollständig verarbeitet ist, müssen Sie sofort und ohne Verzögerung mit der Verarbeitung des nächsten Puffers beginnen. Dies ist eine grundlegende Notwendigkeit für eine klickfreie, reibungslose Audiowiedergabe. Auf diese Weise der doppelten Pufferung haben Sie die Möglichkeit, eine reibungslose Audioverarbeitung ohne Störungen und Risse zu erstellen.

Unabhängig davon, ob Sie eine FIR- oder IIR-basierte Filterung durchführen, können Sie entweder den gesamten Puffer auf einmal wie im FIR-Fall filtern oder für einen IIR-Fall rekursiv Sample für Sample gehen.

Die Größe des Puffers ist wichtig für die anfängliche Verzögerung der Verarbeitung. Wenn Sie es also zu groß nehmen, müssen Sie warten, bis beide Puffer gefüllt sind, bevor Sie etwas ausgeben. Wenn Sie andererseits die Puffer zu kurz nehmen, wird das System von den eingehenden Interrupts überfordert.

Eine optimale Wahl liegt zwischen 128 und 1024 Proben. Diese Pufferlängen sind für eine spätere Verarbeitung vom FFT-Typ geeignet. Die Anzahl der Puffer kann auch erhöht werden, um einen robusteren Durchsatz unter verschiedenen Systemlastbedingungen zu erzielen. Es sind jedoch mindestens zwei Puffer erforderlich.


2
Obwohl ich EEG-Signalverarbeitung mache, kann ich dies perfekt anwenden, danke!
BStadlbauer

1
Das ist übrigens ziemlich genau die Beschreibung der kaskadierten Pufferarchitektur von GNU Radio.
Marcus Müller

1
Ich habe Ihren Beitrag in der Erweiterung zu meiner Antwort @ Fat32 adressiert. Ich hoffe es gefällt euch :)
Marcus Müller

@ MarcusMüller; Danke für die Zusammenarbeit. Ich schätze;)
Fat32

5

Muss ich das gesamte Signal (oder zumindest ein großes Stück) jedes Mal filtern, wenn ein paar neue Samples eingehen, oder gibt es eine Möglichkeit (wie die gleitende DFT), den neuen Teil des gefilterten Signals effizient zu bestimmen? Signal?

Digitale Filter funktionieren nicht so - im Grunde können klassische FIR oder IIR für jedes einzelne neue Sample funktionieren . Sie sollten sich wirklich darüber informieren, was diese Filter sind und wie die Leute sie modellieren.

Ich möchte einen Butterworth-Filter verwenden

Nun, es gibt viele Implementierungen davon da draußen,

Ich benutze derzeit Scipys Butter und Filter

von denen du schon einen kennst!

Jetzt ist ein Butterworth-Filter eine rekursive Sache. Um den nächsten Teil Ihres abgetasteten Signals zu berechnen, benötigen Sie den letzten Zustand. Das ist genau der "Filterverzögerungszustand zi", der lfilterzurückkehrt und beim nächsten Aufruf als ziParameter aufgenommen werden kann.

aber ich weiß nicht, wie ich damit ein konstantes Signal erhalten soll.

Ich denke, Sie meinen "kontinuierliche Filterung erreichen".

Der Punkt ist jedoch, dass Sie sich darauf einstellen, Ihre eigene Streaming-Architektur zu schreiben. Das würde ich nicht tun. Verwenden Sie ein vorhandenes Framework. Zum Beispiel gibt es GNU Radio, mit dem Sie Flussdiagramme für die Signalverarbeitung in Python definieren können. Außerdem ist es von Natur aus multithreaded, verwendet hochoptimierte Algorithmusimplementierungen, verfügt über zahlreiche Ein- und Ausgabefunktionen und verfügt über eine große Bibliothek von Signalverarbeitungsblöcken , die in Python oder C ++ geschrieben werden kann, wenn Sie dies tun müssen.

Ein Flussdiagramm, das Samples von einer Soundkarte aufnimmt, sie butterworth filtert und in eine Datei schreibt, lautet beispielsweise:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
##################################################
# GNU Radio Python Flow Graph
# Title: Butterworth Test
# Generated: Mon Feb  8 16:17:18 2016
##################################################

from gnuradio import audio
from gnuradio import blocks
from gnuradio import eng_notation
from gnuradio import filter
from gnuradio import gr
from gnuradio.eng_option import eng_option
from gnuradio.filter import firdes
from optparse import OptionParser


class butterworth_test(gr.top_block):

    def __init__(self):
        gr.top_block.__init__(self, "Butterworth Test")

        ##################################################
        # Variables
        ##################################################
        self.samp_rate = samp_rate = 48000

        ##################################################
        # Blocks
        ##################################################
        # taps from scipy.butter!
        self.iir_filter_xxx_0 = filter.iir_filter_ffd(([1.0952627450621233e-05, 0.00013143152940745496, 0.0007228734117410033, 0.0024095780391366808, 0.005421550588057537, 0.008674480940892064, 0.010120227764374086, 0.008674480940892081, 0.005421550588057554, 0.0024095780391366955, 0.0007228734117410089, 0.00013143152940745594, 1.0952627450621367e-05]), ([1.0, -4.4363862740719835, 10.215121830052535, -15.374408118154847, 16.57333784740102, -13.325056987818655, 8.133543488903097, -3.77641064765334, 1.3181452681671835, -0.3361758629961047, 0.05930166356243964, -0.0064815521348275, 0.00033130678123743994]), False)
        self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1, "", False)
        self.blocks_file_sink_0.set_unbuffered(False)
        self.audio_source_0 = audio.source(samp_rate, "", True)

        ##################################################
        # Connections
        ##################################################
        self.connect((self.audio_source_0, 0), (self.iir_filter_xxx_0, 0))    
        self.connect((self.iir_filter_xxx_0, 0), (self.blocks_file_sink_0, 0))    

def main(top_block_cls=butterworth_test, options=None):

    tb = top_block_cls()
    tb.start()
    try:
        raw_input('Press Enter to quit: ')
    except EOFError:
        pass
    tb.stop()
    tb.wait()


if __name__ == '__main__':
    main()

Beachten Sie, dass dieser Code automatisch aus einem grafischen Flussdiagramm generiert wurde, das ich gerade mit dem gnuradio-companionProgramm zusammengeklickt habe:

Flussdiagramm wie in GRC entworfen

Wenn Sie mehr über die Implementierung von Flussdiagrammen für die Signalverarbeitung in Python erfahren möchten, lesen Sie die GNU Radio Guided Tutorials .

EDIT : Ich mochte die Antwort von @ Fat32 ziemlich! Was er als Doppelpufferarchitektur beschreibt, kommt dem, was GNU Radio tut, ziemlich nahe:

Ein Upstream-Block erzeugt Samples in Sample-Chunks beliebiger Größe, schreibt sie in den Ausgangsringpuffer (der im obigen Bild als Pfeil dargestellt ist) und benachrichtigt seine Downstream-Blöcke über neue Daten.

Der Downstream-Block wird benachrichtigt, prüft, ob in seinem Ausgabepuffer genügend Speicherplatz vorhanden ist, um die Samples zu verarbeiten, die sich in seinem Eingangsringpuffer befinden (der mit dem Ausgabepuffer des Upstream-Blocks identisch ist), und verarbeitet diese. Wenn der Vorgang abgeschlossen ist, werden die Upstream-Blöcke darüber informiert, dass sie den Eingangsringpuffer (der dann von den Upstream-Blöcken als Ausgang wiederverwendet werden kann) verbraucht haben, und die Downstream-Blöcke darüber, dass neue Abtastwerte verfügbar sind.

Da GNU Radio jetzt Multithreading ist, produziert der Upstream-Block möglicherweise bereits wieder Samples. In einer normalen GNU Radio-Anwendung sind fast alle Blöcke gleichzeitig "aktiv" und die Skalierung auf Computern mit mehreren CPUs ist recht gut.

Die Hauptaufgabe von GNU Radio besteht also darin, Ihnen diese Pufferinfrastruktur, die Benachrichtigungs- und Thread-Verwaltung, die eindeutige Signalverarbeitungsblock-API und etwas zu geben, um zu definieren, wie alles verbunden ist, sodass Sie nicht schreiben müssen, was Fat32 in ihr beschreibt poste dich! Beachten Sie, dass das Marshalling von Sample-Streams nicht so einfach ist, und GNU Radio nimmt die Härte heraus und lässt Sie sich auf das konzentrieren, was Sie tun möchten: DSP.


Vielen Dank! Ich habe mir GNU Radio angesehen, aber da ich ein EEG-Signal verarbeiten werde, müsste ich mein eigenes Modul erstellen, um das Flussdiagramm zu verwenden, da jede Probe einen Zeitstempel hat, der während des gesamten Filterprozesses nachvollziehbar sein muss.
BStadlbauer

Dafür brauchst du keinen Block. Die Proben sind aufeinanderfolgend und mit einer festen Abtastrate steht die Zeit direkt als aktuelle Abtastrate zur Verfügung
Marcus Müller

@ MarcusMüller; Diese von Ihnen beschriebene GNU-Radio-Architektur ist genau das, was die moderne Philosophie bietet. Flexibilität, einfache Codierung und vor allem die Möglichkeit, sich auf Ihr Hauptziel (DSP-Verarbeitung) zu konzentrieren, anstatt dies mithilfe komplexer Details auf niedriger Ebene zu erreichen (wie dies der Fall ist, wenn Sie versuchen, mithilfe von Win32-API-Aufrufen eine Doppelpuffertechnik zu implementieren !)
Fat32

@ Fat32 sollten wir den Marketingstil abschwächen, aber: Ja, und das alles zusammen mit einer effizienten Nullkopie-Ringpufferarchitektur und einer umfangreichen Bibliothek von Blöcken, die handoptimierten Code auf x86 verwenden, ist es MMX, SSE, SSE2, AVX-Erweiterungen und ARMs NEON, wo zutreffend, unter Verwendung der VOLK Vector Optimized Library of Kernels :)
Marcus Müller

@ MarcusMüller Ich hatte gestern etwas Freizeit, also habe ich mir GNU Radio genauer angesehen und es scheint ziemlich hilfreich zu sein, nur verstehe ich nicht, wie ich den sp-Blöcken "manuell" Proben zuführen kann, weil (alle) Turorials sind gemacht für Audioquellen, die von einem Hardwaregerät kommen. Sie wissen nicht, wo ich ein Turorial usw. finden kann, um Proben manuell an die Kette zu schieben? PS Die EEG-Samples stammen aus einer speziellen Schicht (LSL - Lab Streaming Layer) und haben ungefähr 64 Kanäle pro Sample
BStadlbauer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.