SuS verstehen konzeptuell, was die Fourier-Transformation macht
SuS können die dominante Frequenz aus einem Signal extrahieren
SuS können Frequenz in Herzschläge pro Minute umrechnen
Im vorherigen Experiment haben wir das PPG-Signal aufgenommen und visualisiert. Dabei konnten wir das pulsierende Signal sehen, mussten aber die Herzschläge manuell zählen. Das ist mühsam und ungenau – Zeit für Automatisierung!
Stellen Sie sich vor, Sie hören einen Akkord am Klavier. Ihr Ohr kann automatisch die einzelnen Töne heraushören – etwa ein C, ein E und ein G. Genau das macht die Fourier-Transformation (FFT) mathematisch mit Signalen: Sie zerlegt komplexe Wellenformen in ihre Grundfrequenzen.
Zeitbereich : "Wann erklingt welcher Ton?"
Frequenzbereich : "Welche Töne stecken im Akkord?"
Unser PPG-Signal enthält verschiedene Frequenzen:
Drift (~0 Hz): Langsame Helligkeitsänderungen
Atmung (~0.2 Hz): 12 Atemzüge pro Minute
Puls (~1-1.5 Hz): 60-90 Herzschläge pro Minute
Rauschen : Zufällige Störungen
Die FFT zeigt uns, welche Frequenz am stärksten ist – das ist unser Puls!
Python Loading editor…
import numpy as np
import matplotlib.pyplot as plt
# Simuliertes PPG-Signal mit verschiedenen Frequenzen
time = np.linspace(0, 10, 1000) # 10 Sekunden
drift = 0.1 * time # Langsame Drift
atmung = 0.05 * np.sin(2 * np.pi * 0.2 * time) # Atmung (0.2 Hz)
puls = 0.3 * np.sin(2 * np.pi * 1.2 * time) # Puls (1.2 Hz = 72 bpm)
rauschen = 0.02 * np.random.randn(1000)
signal = drift + atmung + puls + rauschen
plt.figure(figsize=(12, 4))
plt.plot(time, signal)
plt.title('Simuliertes PPG-Signal (Zeitbereich)')
plt.xlabel('Zeit [s]')
plt.ylabel('Amplitude')
plt.grid(True)
plt.show()
Jetzt wenden wir die FFT an, um die Frequenzen zu extrahieren:
Python Loading editor…
# FFT berechnen
fft_result = np.abs(np.fft.fft(signal))
freqs = np.fft.fftfreq(len(signal), time[1] - time[0])
# Nur positive Frequenzen anzeigen
positive_freqs = freqs[:len(freqs)//2]
positive_fft = fft_result[:len(fft_result)//2]
plt.figure(figsize=(12, 4))
plt.plot(positive_freqs, positive_fft)
plt.title('FFT-Spektrum (Frequenzbereich)')
plt.xlabel('Frequenz [Hz]')
plt.ylabel('Amplitude')
plt.xlim(0, 3) # Nur relevanter Bereich
plt.grid(True)
plt.show()
# Dominante Frequenz finden
puls_bereich = (positive_freqs >= 0.7) & (positive_freqs <= 3.0)
dominante_freq = positive_freqs[puls_bereich][np.argmax(positive_fft[puls_bereich])]
herzfrequenz = dominante_freq * 60
print(f"Dominante Frequenz: {dominante_freq:.2f} Hz")
print(f"Herzfrequenz: {herzfrequenz:.0f} bpm")
Umrechnung Frequenz → bpm
Frequenz [Hz] × 60 = Herzschläge pro Minute [bpm]
Beispiel: 1.2 Hz × 60 = 72 bpm
Laden wir das Signal aus dem vorherigen Experiment und wenden die FFT-Analyse an:
Python Loading editor…
import imageio
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, filtfilt
def bandpass_filter(data, lowcut, highcut, fs, order=4):
"""Bandpassfilter für PPG-Signal"""
nyquist = 0.5 * fs
low = lowcut / nyquist
high = highcut / nyquist
b, a = butter(order, [low, high], btype='band')
return filtfilt(b, a, data)
# Video laden (ersetzen Sie mit Ihrem Dateinamen)
try:
video = imageio.get_reader('video.mp4', 'ffmpeg')
fps = video.get_meta_data()['fps']
# Roten Farbkanal extrahieren
red_values = []
for frame in video:
red_mean = np.mean(frame[:, :, 0]) # Roter Kanal
red_values.append(red_mean)
video.close()
# Zeit-Achse erstellen
time = np.arange(len(red_values)) / fps
print(f"Video: {len(red_values)} Frames, {fps} fps, {time[-1]:.1f}s Dauer")
except:
# Fallback: Simulierte Daten wenn kein Video vorhanden
print("Kein Video gefunden - verwende simulierte Daten")
fps = 30
time = np.linspace(0, 20, 600)
red_values = 100 + 5*np.sin(2*np.pi*1.3*time) + 2*np.random.randn(600)
# Originalsignal plotten
plt.figure(figsize=(12, 8))
plt.subplot(3, 1, 1)
plt.plot(time, red_values, 'r-', alpha=0.7)
plt.title('Original PPG-Signal')
plt.ylabel('Helligkeit (rot)')
plt.grid(True)
Jetzt filtern wir das Signal und berechnen die Herzfrequenz:
Python Loading editor…
# Bandpassfilter anwenden (42-180 bpm entspricht 0.7-3 Hz)
filtered_signal = bandpass_filter(red_values, 0.7, 3.0, fps)
plt.subplot(3, 1, 2)
plt.plot(time, filtered_signal, 'b-')
plt.title('Gefiltertes Signal (0.7-3 Hz Bandpass)')
plt.ylabel('Amplitude')
plt.grid(True)
# FFT berechnen
fft_result = np.abs(np.fft.fft(filtered_signal))
freqs = np.fft.fftfreq(len(filtered_signal), 1/fps)
# Nur positive Frequenzen
positive_freqs = freqs[:len(freqs)//2]
positive_fft = fft_result[:len(fft_result)//2]
plt.subplot(3, 1, 3)
plt.plot(positive_freqs, positive_fft, 'g-')
plt.title('FFT-Spektrum')
plt.xlabel('Frequenz [Hz]')
plt.ylabel('Amplitude')
plt.xlim(0.5, 3.5)
plt.grid(True)
plt.tight_layout()
plt.show()
# Herzfrequenz bestimmen
puls_bereich = (positive_freqs >= 0.7) & (positive_freqs <= 3.0)
if np.any(puls_bereich):
peak_index = np.argmax(positive_fft[puls_bereich])
dominante_freq = positive_freqs[puls_bereich][peak_index]
herzfrequenz = dominante_freq * 60
print(f"\n🔍 FFT-Analyse:")
print(f"Dominante Frequenz: {dominante_freq:.3f} Hz")
print(f"Berechnete Herzfrequenz: {herzfrequenz:.0f} bpm")
else:
print("Kein klarer Puls-Peak gefunden!")
Aufgabe: Genauigkeit überprüfen
Zählen Sie in Ihrem ursprünglichen Plot manuell die Herzschläge
Rechnen Sie auf 1 Minute hoch: (Gezählte Peaks / Videodauer) × 60
Vergleichen Sie mit der FFT-Berechnung
Python Loading editor…
# Manuelle Validierung
manual_count = input("Wie viele Herzschläge haben Sie im Video gezählt? ")
if manual_count.isdigit():
manual_count = int(manual_count)
video_duration = len(red_values) / fps
manual_bpm = (manual_count / video_duration) * 60
print(f"\n📊 Vergleich:")
print(f"Manuelle Zählung: {manual_bpm:.0f} bpm")
print(f"FFT-Berechnung: {herzfrequenz:.0f} bpm")
print(f"Unterschied: {abs(herzfrequenz - manual_bpm):.0f} bpm")
if abs(herzfrequenz - manual_bpm) <= 5:
print("✅ Ausgezeichnet! Unterschied ≤ 5 bpm")
elif abs(herzfrequenz - manual_bpm) <= 10:
print("👍 Gut! Unterschied ≤ 10 bpm")
else:
print("⚠️ Grosse Abweichung - Signal eventuell zu verrauscht")
Die Fourier-Transformation ist ideal für periodische Signale wie den Herzschlag:
Automatisch : Keine manuelle Peakzählung nötig
Robust : Filtert Rauschen und Störungen heraus
Genau : Kann auch bei schwachen Signalen arbeiten
Schnell : Efficient implementiert in NumPy
Limitationen:
Benötigt gleichmässigen Herzrhythmus
Mindestens 10-15 Sekunden Aufnahmedauer
Störungen durch Bewegung können Ergebnis verfälschen
Verschiedene Körperstellen : Testen Sie Finger, Ohrläppchen, Handgelenk
Belastungstest : Messen Sie vor und nach körperlicher Aktivität
Signalqualität : Vergleichen Sie verschiedene Beleuchtungsarten
Zeitvarianz : Analysieren Sie längere Aufnahmen in 30-Sekunden-Fenstern
Im nächsten Kapitel werden wir ein Live-System entwickeln, das den Puls in Echtzeit anzeigt!