====== Taktrückgewinnung 1.2 ======
===== takt_finder =====
Wie der Name schon sagt, soll dieser Code den hinterlegten Takt in unserer
Aufnahme in Form von Zeitpunkten ausgeben, der ja durch unsere mitgespielte
Takt-Frequenz erkennbar ist. Hierfür wird unsere Aufnahme um unsere
Takt-Frequenz gefiltert, in Beträge umgewandelt und die Hillbert-Funktion angewendet.
Die Hillbert-Funktion sorgt für eine Konturlinie um die Aufnahme
der gefilterten Frequenz. Am Ende erhalten wir eine Funktion der Intensität von
der Takt-Frequenz.
{{:ss15:phase_1.jpg?direct&200|ursprüngliche Aufnahme}} **⇒** {{:ss15:phase_2.jpg?direct&200|Filterung der Takt-Frequenz}} **⇒** {{:ss15:phase_3.jpg?direct&200|Umwandlung in Beträge}} **⇒** {{:ss15:phase_4.jpg?direct&200|Hillbert-Kurve}}
def takt_finder(y):
'''liefert Liste mit Indizes der Taktflanken'''
takt = 0.05*RATE
f_takt = 12600
mind = 0.005
minl = 16
idx = []
b, a = signal.butter(4, [(f_takt-100)*2./RATE, (f_takt+100)*2./RATE], btype="bandpass") # Parameter für Butterworth-Filter
yf = signal.lfilter(b,a,y) # Taktfrequenzfilter
yh = np.abs(signal.hilbert(y)) # Hilbert-Transformation
# [...]
Somit können wir feststellen, wann die Takt-Frequenz gespielt
wurde oder nicht, da bei Auslassung die Lautstärke gegen Null geht. Nun wollen
wir eben diese Zeitpunkte von diesem Wechsel zwischen 'An' und 'Aus' der Takt-Frequenz
herauslesen.
==== Unterschiede zur Taktrückgewinnung 1.1 ====
Die Taktrückgewinnung 1.1 gibt nur einen Wert, die Taktverschiebung, zurück. Die Version 1.2 liefert dagegen eine Liste mit den konkreten Indizes an welchen sich Taktgrenzen befinden. Daraus ergibt sich ein großer Vorteil der neuen Methode: Es werden nur die Indizes geliefert, an denen auch wirklich etwas gesendet wurde und damit wird auch nicht versucht Aufnahmeabschnitte ohne Signal zu decodieren. Das kann auch dabei helfen verlorene Bits zu vermeiden, wenn weder eine ''0'' oder ''1'' erkannt wird und dadurch weniger Bits ankommen als gesendet wurden. Diese ganzen Vorteile setzen aber voraus, dass der Erkennungsprozess zuverlässig funktioniert. Nachfolgend ist aufgeführt welche Probleme auftreten können und was wir turn um sie zu vermeiden.
==== 1. Problem: ====
Wie eine Lautstärke-Grenze setzen, bei dessen Über- und Unterschreitung ganz
klar ein Wechsel von An zu Aus stattfindet und diese auch immer von Flanken durchlaufen wird?
=== Variante 1 ====
Dazu muss ein absoluter Wert als Grenze gesetzt werden, der jedoch schwer
festzusetzen ist, da immer unterschiedliche Lautstärken verwendet werden könnten.
Dazu vereinheitlichen wir die bisher gewonnen Aufnahme (siehe ''ye''). Egal mit
welcher Lautstärke der Sender sendet, wir erhalten immer das ungefähr gleiche
Bild einer Funktion in ihrem Maximum (ca. 0.0034 bei guten Aufnahmen). Dazu setzen wir eine
möglichst kleine Grenze mit einem kleinen Intervall als Bedingung, die nun
unsere Takt-Flanken erkennt.
{{:ss15:takt_1.jpg?direct&300 |Resultat einer guten Aufnahme}} {{:ss15:takt_2.jpg?direct&300 |keine Beeinflussung durch einzelne Extrema}}
ye = (1/(np.linalg.norm(yf)))*yh # Vereinheitlichung der Hillbertkurve für jede Lautstärke
idx = np.where(np.abs(ye-0.001)<0.00002) # Grenze 0.001 im Toleranzbereich 0.00002 enthält ca. 3 indices an einer Flanke
v.extend(np.array(idx).flatten().tolist())
\\
\\
\\
=== Variante 2 ===
Die Variante 1 wirft allerdings ein Problem auf: Wurde im untersuchten Abschnitt kein Signal aufgenommen, d.h. es herrscht nahezu Stille auf der Taktfrequenz, wird diese Stille mit ihrem ganz geringen Grundrauschen trotzdem auf den üblichen Wert verstärkt. Das führt dann aber dazu, dass Takt-Flanken erkannt werden wo gar keine sind. Deshalb erachte ich es für sinnvoller eine feste minimale Grenze zu definieren, ab dem das Taktsignal als vorhanden gewertet wird und die Grenze zur Erkennung wie gehabt über das Maximum oder ähnlich zu berechnen.
# [...]
ma = max(yh)
if ma > mind:
s = ma/5
tol = 0.002 * ma
idx = np.where(np.abs(yh-s)
==== 2. Problem: ====
Wie können wir unsere 'richtigen' Takt-Flanken von den 'falschen' Takt-Flanken
unterscheiden und herausfiltern? Denn es können bei unschönen Aufnahmen immer
Fehler entstehen, die mit unserer einfachen Grenzsetzung einhergehen.
Wir haben somit erstmal nur eine Sammlung von sehr vielen potentiellen
'richtigen' Flanken. Zum einen enthält das Intervall um die Grenze mehrere
Indices für nur eine gewollte Flanke und zum anderen müssen diese Indices auch
in dem vorgegebenen Taktzeitabstand passen! Und dazu wird die Rohliste ''v''
überprüft.
Per for-Schleife werden die 'richtigen' Flanken in eine neue Liste
übertragen. Der momentane Index wird mit dem nächstkommenden Index auf seinen
Abstand überprüft und nur als 'richtig' übertragen, wenn diese im Taktabstand
mit einer Toleranz von +-5% vom Taktabstand zueinander stehen. Somit fallen
sofort alle Indizes raus, deren Abstand ungleich des Taktabstandes zum
nächsten Index ist, also die der gleichen Flanke angehören oder die den
'falschen' Flanken angehören, die sich zwischenmogeln könnten.
Da Flanken nur als 'richtig' gelten, wenn es die nächste bestätigt, werden hier
'falsche' Flanken ein Problem. Obwohl eine Flanke 'richtig' wäre, würde sie
nicht bestätigt werden können. Dazu gibt es einen Zähler k, der später auf diese
versäumte Flanke zurückführt, sobald die nächste 'richtige' Flanke gefunden
wurde. So würde die gegenwärtige Flanke in der Liste zur Bestätigung für eine
versäumte Flanke werden.
{{:ss15:takt_7.jpg?direct&300 |}} {{:ss15:takt_9.jpg?direct&300 |}}
\\
\\
\\
\\
\\
\\
\\
\\
\\
==== 3. Problem: ====
Die letzte Flanke muss ja auch noch erkannt werden, jedoch geht das schlecht
ohne eine nachfolgende Flanke nach bisherigem System.
Das System wird da einfach umgedreht. Die Liste v wird dabei nun rückwärts
durchlaufen bis es zu einer Bestätigung kommt.
Hierbei wurde uns klar, dass das letzte Bit natürlich auch von Flanken
eingeschlossen werden muss. So mussten wir unsere code-Funktion umschreiben.
Vorher endete unsere Takt-Frequenz nämlich mit Aus. Nun mit An.
==== 4. Problem: ====
Trotz aller Takterkennungsmaßnahmen könnten ja sicherlich einfach durch eine
schlechte Übertragung/Aufnahme wichtige Flanken fehlen, die dann rekonstruiert
werden müssten.
Dies geschieht durch nochmalige Überprüfung der Taktabstände aller Indices der
Liste h. Lücken werden dabei durch einfaches Addieren des Taktabstandes mit
Indices gefüllt.
# [...]
v=[] # alle Indices um Grenze
h=[] # Sortierung der Indices aus v nach nur richtigen Takt-Flanken
e=[] # Hilfliste zur Ergänzung von Takt-Flanken, falls richtige Takt-Flanken nicht auffindbar (Lückenliste)
n=0 # Zähler für v-Liste
m=0 # Zähler für h-Liste
k=0 # Zähler für falsche Takt-Flanken in v-Liste
v.extend(np.array(idx).flatten().tolist())
for x in v:
n+=1
if n==len(v):
for x in sorted(v,reverse=True): # v-liste von hinten nochmal durchgehen, damit letzte Takt-Flanke noch in h hinzugefügt wird
if h[-1]+takt+0.1*takt>x and h[-1]+takt-0.05*taktv[n] and x+takt-0.05*taktx and v[n-k-1]+takt-0.05*takth[m] and x+takt-0.05*takt
====Resultat:====
Wir erhalten die genauen Zeitpunkte eines Wechsels der Lautstärke der Takt-Frequenz und damit lässt sich die Takteinteilung des Signals ablesen.