SignalHub
SignalHub ist ein modulares Pipeline-System.
Daten werden in Form von Signalen zwischen Modulen weitergegeben
Jedes Modul verarbeitet eingehende Daten und erzeugt neue Signale
Die Pipeline wird über die
config.yamldefiniert
Typische Signale für dieses Projekt sind z. B.:
webcam→ Rohbild der Kameradetector→ erkannte Hand + Landmarkenpreprocessor→ normalisierte Trajektorie
Ziel ist es, Module zu implementieren, die sauber miteinander über diese Signale interagieren.
Signalbasierte Kommunikation
Module kommunizieren ausschließlich über sogenannte Signale.
Dieses Konzept ist zentral für das gesamte Framework und muss unbedingt verstanden werden.
Grundprinzip
Ein Modul definiert explizit:
welche Signale es empfängt (
inputSignals)welche Signale es erzeugt (
outputSchema)
Beispiel:
def step(self, data):
detector_data = data["detector"]
return {"preprocessor": result}
Wichtige Regeln
1. Sie erhalten nur die Signale, die Sie abonnieren
Das
data-Dictionary enthält ausschließlich die Signale, die ininputSignalsdefiniert wurdenEin Zugriff auf nicht abonnierte Signale ist nicht möglich
Warnung
Wenn Sie ein Signal nicht in inputSignals angeben,
existiert es für Ihr Modul nicht.
2. Sie dürfen nur definierte Outputs zurückgeben
Die Rückgabe Ihres Moduls muss zum
outputSchemapassenNicht definierte Outputs werden ignoriert oder führen zu Fehlern
Warnung
Sie können keine beliebigen Keys zurückgeben. Jeder Output muss vorher im Schema definiert sein.
3. Datenfluss ist strikt gerichtet
Module arbeiten nicht global
Es gibt keinen direkten Zugriff auf andere Module
Kommunikation erfolgt ausschließlich über Signale
Warum ist das wichtig?
Dieses Konzept erzwingt eine klare Struktur:
Module sind voneinander entkoppelt
Datenflüsse sind explizit sichtbar
Fehler sind leichter nachvollziehbar
Gleichzeitig bedeutet das:
falsche Signalnamen → kein Zugriff
fehlende Subscriptions → leere Daten
falsche Outputs → Pipeline bricht
Replay-Modus
Neben Live-Daten können auch aufgezeichnete Daten verwendet werden.
Dafür existiert ein sogenannter Replay-Modus:
Anstelle der Webcam werden gespeicherte Aufnahmen abgespielt
Dies ermöglicht reproduzierbares Testen und Debugging
Der Replay-Modus kann über die Kommandozeile gestartet werden:
python main.py --mode replay --recorder <path_to_recording>
Dies ist besonders wichtig, um:
den Preprocessor unabhängig zu entwickeln
Visualisierungen zu testen
den Classifier ohne Live-Input zu debuggen
Steuerung über die Konfiguration
Welche Daten tatsächlich replayed werden, wird über die config.yaml gesteuert.
Beispiel:
recorder:
replay:
- trailmarker
- preprocessor
- hiddenmarkov
- detector
Nur die hier aufgeführten Module bzw. Signale werden aus den aufgezeichneten Daten gespeist.
Alle anderen Module laufen weiterhin im Live-Modus.
Live- vs. Replay-Daten
Das bedeutet konkret:
Module im
replay-Block erhalten ihre Daten aus der AufnahmeAlle anderen Module erzeugen ihre Daten wie gewohnt live
Dies erlaubt es, gezielt einzelne Teile der Pipeline zu testen.
Beispiel:
Sie verwenden aufgezeichnete
detector-Datenund entwickeln darauf Ihren
preprocessorunabhängig weiter
Datenfluss im Replay-Modus
Die aufgezeichneten Daten werden schrittweise (Frame für Frame) in die Pipeline eingespeist.
Das bedeutet:
Ihr Modul erhält pro
stepgenau die Daten, die zum jeweiligen Zeitpunkt aufgezeichnet wurdenDer zeitliche Verlauf der Sequenz bleibt erhalten
Bemerkung
Der Replay-Modus ersetzt nicht die gesamte Pipeline, sondern nur die explizit konfigurierten Teile. Dies ermöglicht eine sehr flexible Kombination aus Live- und aufgezeichneten Daten.
Recordings (bereitgestellter Datensatz)
Die bereitgestellten Recordings stammen aus SignalHub.
Sie enthalten Rohdaten aus der Pipeline (z. B. Detector-Ausgaben)
Sie bilden die Grundlage für Ihre Entwicklung
Warnung
Die bereitgestellten Daten dienen lediglich als Grundlage, damit Sie parallel an unterschiedlichen Schritten der Pipeline arbeiten können.
Ziel des Projekts ist es ausdrücklich, eigene Daten aufzuzeichnen, auf deren Basis Modelle zu trainieren und diese anschließend auch zu evaluieren.
GALY (Graphical Abstraction Layer)
GALY steht für Graphical Abstraction Layer.
Im Kern ist GALY ein Wrapper um die wichtigsten Zeichenfunktionen aus cv2 (OpenCV).
Der entscheidende Unterschied ist jedoch:
Zeichenoperationen werden nicht direkt ausgeführt, sondern als Befehle gespeichert.
Diese Befehle können:
serialisiert (gespeichert)
deserialisiert (wieder abgespielt)
werden.
Dadurch ist es möglich, Visualisierungen zusammen mit den Daten aufzuzeichnen und später im Replay-Modus exakt wiederzugeben.
Grundprinzip
Anstatt direkt auf ein Bild zu zeichnen, werden Zeichenbefehle gesammelt:
galy = GALY()
galy.line((0, 0), (100, 100), (255, 0, 0))
Intern wird dabei kein cv2.line direkt ausgeführt, sondern ein Befehl gespeichert.
Diese Befehle werden später vom Framework verarbeitet und gerendert.
Canvas und Layer
GALY arbeitet mit zwei zentralen Konzepten:
- Canvas
Eine Zeichenfläche (vergleichbar mit einem Bild)
- Layer
Eine Ebene auf dem Canvas (vergleichbar mit Photoshop-Layern)
Beispiel:
galy = GALY()
galy.canvas("main", (640, 480), (0, 0, 0))
galy.layer("trajectory")
canvaserstellt eine Zeichenflächelayererlaubt strukturierte Visualisierung
Layer können ein- und ausgeblendet werden und erleichtern Debugging.
Grundlegende Zeichenoperationen
Linie zeichnen:
galy.line((x1, y1), (x2, y2), (255, 0, 0), thickness=2)
Kreis zeichnen:
galy.circle((x, y), radius=5, color=(0, 255, 0), thickness=2)
Text anzeigen:
galy.putText("Label", (x, y), color=(255, 255, 255))
Diese Funktionen entsprechen direkt den OpenCV-Funktionen, werden jedoch über GALY abstrahiert.
Bilder einfügen (Blitting)
Mit blit können komplette Bilder auf den Canvas gelegt werden:
galy.blit("webcam", (0, 0))
Dabei wird das Bild aus einem Signal (z. B. webcam) verwendet.
Dies ist typischerweise der erste Schritt, um das Kamerabild darzustellen, auf das anschließend weitere Elemente gezeichnet werden.
Affine Transformationen
GALY unterstützt affine Transformationen pro Layer.
mapping = np.array([
[1.0, 0.0, tx],
[0.0, 1.0, ty]
])
galy.set_layer_affine_mapping(mapping)
Eine affine Transformation ist eine lineare Transformation der Form:
Translation (Verschiebung)
Skalierung
Rotation
Diese wird auf alle Punkte eines Layers angewendet.
Beispiel:
Koordinaten normalisiert → Mapping auf Bildschirm
Verschiebung der gesamten Trajektorie
Skalierung von Daten
Intern wird jeder Punkt transformiert, bevor er gezeichnet wird.
Mahalanobis-Visualisierung
GALY enthält auch spezialisierte Visualisierung:
galy.mahalanobis(mean, covariance, color=(255, 0, 0))
Dies zeichnet eine Ellipse basierend auf:
Mittelwert (mean)
Kovarianzmatrix (covariance)
Dies kann z. B. verwendet werden für:
Visualisierung von Zuständen im HMM
Darstellung von Unsicherheiten
Integration in Module
Damit GALY-Visualisierungen im Framework angezeigt werden, muss das erzeugte GALY-Objekt vom Modul zurückgegeben werden.
Beispiel:
def step(self, data):
galy = GALY()
galy.canvas("main", (640, 480), (0, 0, 0))
galy.layer("debug")
galy.line((0, 0), (100, 100), (255, 0, 0))
return {
...
"galy": galy
}
Wichtig:
Das Signal muss genau
"galy"heißenNur dann wird es vom Framework automatisch verarbeitet
Bemerkung
Wenn kein galy-Objekt zurückgegeben wird,
erfolgt auch keine Visualisierung.
Tipp
Sie können GALY in jedem Modul verwenden. Besonders hilfreich ist dies im Preprocessor und Classifier, um Zwischenergebnisse sichtbar zu machen.
Verarbeitung im Framework
Alle gesammelten Befehle werden später verarbeitet:
process_galy(data)
Dabei passiert:
Alle Commands werden iteriert
In echte
cv2-Aufrufe übersetztAuf Canvas gerendert
In GUI angezeigt
Dadurch bleibt die Visualisierung:
reproduzierbar
speicherbar
unabhängig vom Zeitpunkt der Berechnung
Wichtige Hinweise
GALY ersetzt nicht OpenCV, sondern abstrahiert es
Alle Zeichenoperationen sind zustandslos gesammelt
Reihenfolge der Befehle ist entscheidend
Visualisierung kann Teil der aufgezeichneten Daten sein
Tipp
Nutzen Sie GALY aktiv zum Debugging. Eine gute Visualisierung hilft oft mehr als jede Konsole.
Pipeline verändern
SignalHub organisiert die gesamte Verarbeitung als Pipeline aus Modulen.
Die Pipeline wird explizit im Code definiert, z. B.:
modules = [
ConfigParser(parser),
Webcam(),
HandDetector(),
TrailMarker(),
Preprocessor(),
HMMModule(),
]
engine = Engine(modules=modules, signals={})
signals = engine.run({})
Pipeline-Struktur
Durch Ändern dieser Liste können Sie die gesamte Pipeline anpassen.
Beispiele:
Module hinzufügen (z. B. eigene Visualisierung)
Module entfernen (z. B. TrailMarker deaktivieren)
Module austauschen (z. B. eigener Classifier)
Bemerkung
Die Pipeline ist vollständig flexibel und kann beliebig erweitert oder verändert werden.
Eigene Module erstellen
Neue Module werden erstellt, indem Sie von der Basisklasse Module erben:
from SignalHub import Module
class MyModule(Module):
def start(self, data):
return {}
def step(self, data):
return {}
def stop(self, data):
pass
Jedes Modul besteht aus drei zentralen Methoden:
start→ wird einmal beim Start aufgerufenstep→ wird für jeden Frame ausgeführtstop→ wird beim Beenden aufgerufen
Erweiterung der Pipeline
Sie können jederzeit eigene Module in die Pipeline integrieren:
modules = [
ConfigParser(parser),
Webcam(),
HandDetector(),
##########
MyModule(),
##########
Preprocessor(),
HMMModule(),
]
Wichtig ist dabei:
das Modul korrekt Signale abonniert
die erwarteten Datenstrukturen einhält
Tipp
Beginnen Sie mit kleinen Modulen und erweitern Sie die Pipeline schrittweise. So lassen sich Fehler deutlich einfacher finden.
Zusammenfassung
Die Pipeline wird über eine Liste von Modulen definiert
Module werden in Reihenfolge ausgeführt
Neue Module entstehen durch Vererbung von
ModuleKommunikation erfolgt über Signale und das
data-Dictionary
Dieses Konzept erlaubt eine sehr flexible und erweiterbare Architektur.