SuS können ein alternatives Messsetup mit USB-Mikroskop aufbauen
SuS verstehen Vorteile kontrollierter Beleuchtung
SuS können Live-Videostream in Python verarbeiten
SuS können verschiedene Körperstellen auf Signalqualität vergleichen
In den vorherigen Lektionen haben wir mit dem Smartphone experimentiert. Das hat gut funktioniert, aber es gibt einige Limitationen:
Ungleichmässige Beleuchtung durch den Blitz
Schwierige Positionierung des Fingers
Keine präzise Kontrolle über die Aufnahmequalität
Ein USB-Mikroskop bietet uns mehrere Vorteile:
Kontrollierte Beleuchtung : Die meisten USB-Mikroskope haben eingebaute LED-Beleuchtung, die konstant und gleichmässig ist – genau wie bei professionellen Pulsmessern.
Stabile Positionierung : Das Mikroskop steht fest auf dem Tisch, der Finger kann entspannt aufgelegt werden.
Bessere Bildqualität : Durch die Nahaufnahme sehen wir die Hautstruktur detaillierter, was zu einem klareren PPG-Signal führen kann.
USB-Mikroskop anschliessen : Verbindet das Mikroskop mit eurem Computer
Positionierung : Stellt das Mikroskop so auf, dass ihr die Fingerkuppe oder das Ohrläppchen scharf sehen könnt
Beleuchtung : Aktiviert die LED-Beleuchtung des Mikroskops
Abstand : Experimentiert mit verschiedenen Abständen – die Haut soll gut ausgeleuchtet, aber nicht überbelichtet sein
Tipp: Beste Körperstellen
Fingerkuppe : Gute Durchblutung, einfach zu positionieren
Ohrläppchen : Sehr dünne Haut, starkes Signal
Handgelenk : Wie bei einer Smartwatch, aber schwieriger zu messen
Wir benötigen OpenCV für die Kamera-Kommunikation:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, filtfilt
import time
Falls OpenCV noch nicht installiert ist:
pip install opencv-python
Python Loading editor…
import cv2
import numpy as np
# Kamera initialisieren (USB-Mikroskop)
cap = cv2.VideoCapture(0) # Probiert 0, 1, 2 je nach USB-Port
# Prüfen ob Kamera funktioniert
if not cap.isOpened():
print("Fehler: Kamera konnte nicht geöffnet werden")
exit()
print("Kamera gefunden! Drückt 'q' zum Beenden.")
# Live-Vorschau
while True:
ret, frame = cap.read()
if not ret:
break
cv2.imshow('USB-Mikroskop', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
Jetzt sammeln wir systematisch die Rotwerte für unser PPG-Signal:
Python Loading editor…
def collect_ppg_data(duration_seconds=20):
"""
Sammelt PPG-Daten vom USB-Mikroskop
Args:
duration_seconds: Aufnahmedauer in Sekunden
Returns:
red_values: Liste der durchschnittlichen Rotwerte
timestamps: Zeitstempel für jeden Frame
"""
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Fehler: Kamera konnte nicht geöffnet werden")
return None, None
# FPS der Kamera ermitteln
fps = cap.get(cv2.CAP_PROP_FPS)
if fps == 0: # Fallback falls FPS nicht erkannt wird
fps = 30
print(f"Kamera läuft mit {fps} FPS")
print(f"Sammle {duration_seconds} Sekunden Daten...")
print("Legt euren Finger ruhig vor das Mikroskop!")
red_values = []
timestamps = []
start_time = time.time()
# Countdown
for i in range(3, 0, -1):
print(f"Start in {i}...")
time.sleep(1)
print("Aufnahme läuft!")
while len(red_values) < duration_seconds * fps:
ret, frame = cap.read()
if not ret:
break
# Durchschnittlicher Rotwert berechnen
# OpenCV verwendet BGR-Format, also Index 2 = Rot
red_mean = np.mean(frame[:, :, 2])
red_values.append(red_mean)
# Zeitstempel
current_time = time.time() - start_time
timestamps.append(current_time)
# Live-Anzeige mit aktuellem Rotwert
cv2.putText(frame, f"Rot: {red_mean:.1f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.imshow('PPG-Aufnahme', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print(f"Aufnahme beendet. {len(red_values)} Frames gesammelt.")
return red_values, timestamps
# Daten sammeln
red_values, timestamps = collect_ppg_data(20)
# Rohsignal plotten
plt.figure(figsize=(12, 4))
plt.plot(timestamps, red_values)
plt.xlabel('Zeit (s)')
plt.ylabel('Rotwert')
plt.title('PPG-Rohsignal vom USB-Mikroskop')
plt.grid(True)
plt.show()
Verwenden wir dieselben Techniken wie in Lektion 3:
Python Loading editor…
def bandpass_filter(data, lowcut, highcut, fs, order=4):
"""Bandpass-Filter 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)
def calculate_heart_rate(red_values, fps):
"""
Berechnet Herzfrequenz aus PPG-Signal
Args:
red_values: Liste der Rotwerte
fps: Bildrate der Aufnahme
Returns:
heart_rate: Herzfrequenz in bpm
frequencies: Frequenz-Array für FFT
fft_magnitude: FFT-Magnitude für Plotting
"""
# Bandpass-Filter: 0.7-3 Hz (42-180 bpm)
filtered_signal = bandpass_filter(red_values, 0.7, 3.0, fps)
# FFT berechnen
fft_result = np.fft.fft(filtered_signal)
fft_magnitude = np.abs(fft_result)
frequencies = np.fft.fftfreq(len(filtered_signal), 1/fps)
# Nur positive Frequenzen im relevanten Bereich
valid_indices = (frequencies > 0.7) & (frequencies < 3.0)
valid_freqs = frequencies[valid_indices]
valid_magnitudes = fft_magnitude[valid_indices]
# Peak finden
peak_index = np.argmax(valid_magnitudes)
peak_frequency = valid_freqs[peak_index]
# In bpm umrechnen
heart_rate = peak_frequency * 60
return heart_rate, frequencies, fft_magnitude, filtered_signal
# Herzfrequenz berechnen
if red_values:
fps = len(red_values) / timestamps[-1] # Tatsächliche FPS berechnen
heart_rate, freqs, fft_mag, filtered = calculate_heart_rate(red_values, fps)
print(f"Geschätzte Herzfrequenz: {heart_rate:.0f} bpm")
# Visualisierung
fig, axes = plt.subplots(3, 1, figsize=(12, 10))
# Rohsignal
axes[0].plot(timestamps, red_values, 'b-', alpha=0.7)
axes[0].set_title('Rohsignal')
axes[0].set_ylabel('Rotwert')
axes[0].grid(True)
# Gefiltertes Signal
axes[1].plot(timestamps, filtered, 'r-')
axes[1].set_title('Gefiltertes Signal (0.7-3 Hz)')
axes[1].set_ylabel('Amplitude')
axes[1].set_xlabel('Zeit (s)')
axes[1].grid(True)
# FFT
positive_freqs = freqs[freqs > 0]
positive_fft = fft_mag[freqs > 0]
axes[2].plot(positive_freqs * 60, positive_fft) # Frequenz in bpm
axes[2].axvline(heart_rate, color='r', linestyle='--',
label=f'Peak: {heart_rate:.0f} bpm')
axes[2].set_xlim(40, 180)
axes[2].set_title('Frequenzspektrum')
axes[2].set_xlabel('Frequenz (bpm)')
axes[2].set_ylabel('Amplitude')
axes[2].legend()
axes[2].grid(True)
plt.tight_layout()
plt.show()
Jetzt testen wir systematisch verschiedene Messstellen:
Python Loading editor…
def compare_body_locations():
"""Vergleicht PPG-Signalqualität an verschiedenen Körperstellen"""
locations = ['Fingerkuppe', 'Ohrläppchen', 'Handgelenk']
results = {}
for location in locations:
print(f"\n=== Messung: {location} ===")
print(f"Positioniert das Mikroskop über eurem {location}")
input("Drückt Enter wenn bereit...")
# Kurze Messung (15 Sekunden)
red_values, timestamps = collect_ppg_data(15)
if red_values:
fps = len(red_values) / timestamps[-1]
heart_rate, _, _, filtered = calculate_heart_rate(red_values, fps)
# Signalqualität bewerten
signal_std = np.std(filtered) # Standardabweichung als Qualitätsmass
results[location] = {
'heart_rate': heart_rate,
'signal_quality': signal_std,
'raw_signal': red_values,
'filtered_signal': filtered,
'timestamps': timestamps
}
print(f"Herzfrequenz: {heart_rate:.0f} bpm")
print(f"Signalqualität: {signal_std:.2f}")
return results
# Vergleichsmessungen durchführen
print("Wir testen jetzt verschiedene Körperstellen!")
print("Haltet bei jeder Messung den Körperteil ruhig.")
comparison_results = compare_body_locations()
# Ergebnisse visualisieren
if comparison_results:
fig, axes = plt.subplots(len(comparison_results), 2,
figsize=(14, 4*len(comparison_results)))
if len(comparison_results) == 1:
axes = axes.reshape(1, -1)
for i, (location, data) in enumerate(comparison_results.items()):
# Rohsignal
axes[i, 0].plot(data['timestamps'], data['raw_signal'])
axes[i, 0].set_title(f'{location} - Rohsignal')
axes[i, 0].set_ylabel('Rotwert')
axes[i, 0].grid(True)
# Gefiltertes Signal
axes[i, 1].plot(data['timestamps'], data['filtered_signal'])
axes[i, 1].set_title(f'{location} - Gefiltert ({data["heart_rate"]:.0f} bpm)')
axes[i, 1].set_ylabel('Amplitude')
axes[i, 1].grid(True)
axes[-1, 0].set_xlabel('Zeit (s)')
axes[-1, 1].set_xlabel('Zeit (s)')
plt.tight_layout()
plt.show()
# Zusammenfassung
print("\n=== VERGLEICH ===")
for location, data in comparison_results.items():
print(f"{location:12}: {data['heart_rate']:3.0f} bpm, "
f"Qualität: {data['signal_quality']:5.2f}")
Welche Körperstelle lieferte das klarste Signal?
Warum könnten manche Stellen besser funktionieren als andere?
Wie unterscheidet sich die Signalqualität vom Smartphone-Setup?
Anatomische Faktoren:
Durchblutung : Fingerkuppen und Ohrläppchen haben viele kleine Blutgefässe
Hautdicke : Dünne Haut lässt mehr Licht durch
Bewegung : Ruhigere Körperteile geben stabilere Signale
Technische Faktoren:
Beleuchtungsstärke : Konstante LED vs. schwankender Smartphone-Blitz
Abstand : Optimaler Fokus für beste Lichtausbeute
Aufnahmequalität : Höhere Bildrate = bessere Zeitauflösung
Infrarot-LED : Dringt tiefer in die Haut ein
Mehrere Wellenlängen : Kombination aus rotem und infrarotem Licht
Bewegungskompensation : Algorithmen zur Erkennung von Bewegungsartefakten
Längere Messung : 60+ Sekunden für stabilere FFT-Ergebnisse
Python Loading editor…
def advanced_ppg_analysis(red_values, fps):
"""Erweiterte PPG-Analyse mit zusätzlichen Metriken"""
# Herzratenvariabilität (HRV) schätzen
filtered_signal = bandpass_filter(red_values, 0.7, 3.0, fps)
# Peak-Detection für R-R-Intervalle
from scipy.signal import find_peaks
peaks, _ = find_peaks(filtered_signal, height=np.std(filtered_signal))
if len(peaks) > 1:
# R-R-Intervalle in Millisekunden
rr_intervals = np.diff(peaks) / fps * 1000
# HRV-Metriken
rmssd = np.sqrt(np.mean(np.diff(rr_intervals)**2)) # RMSSD
hr_variability = np.std(rr_intervals)
print(f"Gefundene Herzschläge: {len(peaks)}")
print(f"Mittleres R-R-Intervall: {np.mean(rr_intervals):.0f} ms")
print(f"RMSSD (HRV): {rmssd:.1f} ms")
print(f"HR-Variabilität: {hr_variability:.1f} ms")
# Visualisierung
plt.figure(figsize=(12, 6))
time_axis = np.arange(len(filtered_signal)) / fps
plt.plot(time_axis, filtered_signal)
plt.plot(peaks / fps, filtered_signal[peaks], 'ro', markersize=8)
plt.xlabel('Zeit (s)')
plt.ylabel('PPG-Amplitude')
plt.title('Peak-Detection für Herzschlag-Intervalle')
plt.grid(True)
plt.show()
return rr_intervals
else:
print("Zu wenige Peaks für HRV-Analyse gefunden")
return None
# Für Fortgeschrittene: HRV-Analyse
if red_values:
rr_intervals = advanced_ppg_analysis(red_values, fps)
Basis-Aufgaben:
Führt Messungen an mindestens zwei verschiedenen Körperstellen durch
Vergleicht die Herzfrequenz mit manueller Pulsmessung (15 Sek × 4)
Dokumentiert, welche Stelle das beste Signal liefert
Erweiterte Aufgaben:
4. Testet verschiedene Beleuchtungsstärken am Mikroskop
5. Experimentiert mit verschiedenen Abständen zur Haut
6. Implementiert eine Live-Anzeige der aktuellen Herzfrequenz
Für Profis:
7. Versucht die Peak-Detection zu verbessern
8. Implementiert eine einfache Bewegungserkennung
9. Vergleicht euer System mit einer Smartwatch oder Fitness-App
Das USB-Mikroskop bietet uns eine stabilere und kontrollierbarere Plattform für PPG-Messungen als das Smartphone. Die konstante LED-Beleuchtung und die feste Positionierung führen zu klareren Signalen und zuverlässigeren Messungen.
Wichtige Erkenntnisse:
Verschiedene Körperstellen haben unterschiedliche Signalqualität
Kontrollierte Beleuchtung ist entscheidend für gute PPG-Messungen
Die richtige Positionierung und Ruhe des Messsubjekts sind kritisch
In der nächsten Lektion werden wir uns ansehen, wie wir unser System weiter verbessern und professionellere Algorithmen implementieren können.