diff --git a/README.md b/README.md index 73f5e7a..a53238a 100644 --- a/README.md +++ b/README.md @@ -4,43 +4,69 @@ Dieses Projekt ist eine hochoptimierte KI-Steuerung für die DJI Tello Drohne (u ## 🚀 Hauptmerkmale - **Hybrid AI Engine**: Nutzt **YuNet** (Face Detection), **MiDaS** (Depth Estimation) und **MobileNetV3** (ReID) – alle via **ONNX Runtime** für maximale FPS. -- **Multithreading**: Kamera-Stream und KI-Verarbeitung laufen in getrennten Threads. Das Videobild bleibt flüssig, egal wie schwer die KI arbeitet. -- **Visual Fingerprinting (ReID)**: Die Drohne erstellt einen digitalen Fingerabdruck einer fixierten Person und findet sie automatisch wieder, wenn sie kurz aus dem Bild verschwindet. -- **Intelligente Verfolgung**: Aktive Suche in Verschwindungsrichtung und aggressiver Verfolgungsmodus. -- **Echtzeit-HUD**: Professionelles Display mit Telemetrie, AI-Tiefenkarte und Radar-Zonen zur Hindernisvermeidung. -- **Rate Limiting**: Kontrollierter Datenfluss (10Hz) zum Tello-SDK für maximale Verbindungsstabilität. +- **Sport-Modus**: Aggressives 3-Achsen-Tracking für schnelle Verfolgungsjagden. +- **Visual Fingerprinting (ReID)**: Erkennt fixierte Personen wieder, auch wenn sie das Bild kurz verlassen. +- **Multithreading**: Flüssiges Kamerabild durch Trennung von Video-Stream und KI-Logik. +- **Simulator inklusive**: Eine integrierte Ursina-Umgebung zum gefahrlosen Testen. -## 🕹 Steuerung -Klicke mit der **Maus** direkt in das Videobild, um eine Person zu fixieren (**Lock-ON**). +## 🛠 Installation -### Tastatur-Belegung: +1. **Python-Umgebung**: Empfohlen wird Python 3.10 bis 3.12. +2. **Abhängigkeiten installieren**: + ```bash + pip install -r requirements.txt + ``` +3. **Modelle prüfen**: Stelle sicher, dass im Ordner `models/` folgende Dateien liegen: + - `face_detection_yunet.onnx` + - `midas_small.onnx` + - `reid_mobilenet.onnx` + +## 🚀 Starten der Drohne + +### A) Im Simulator (Standard) +1. Öffne die Datei `run.py`. +2. Stelle sicher, dass `use_real_tello=False` eingestellt ist. +3. Starte das Programm: + ```bash + python run.py + ``` +4. Das Simulator-Fenster und das KI-Pilot-Fenster öffnen sich automatisch. + +### B) Mit der echten DJI Tello +1. Schalte deine Tello ein und verbinde deinen PC mit dem WLAN der Drohne (z.B. `TELLO-XXXXXX`). +2. Öffne die Datei `run.py` und ändere die Zeile zu: + ```python + app = FaceTrackingApp(use_real_tello=True) + ``` +3. Starte das Programm: + ```bash + python run.py + ``` + +## 🕹 Bedienung im Flug + +Sobald das Video-Fenster erscheint: +1. **Klicke mit der Maus** in das Fenster, um den Fokus zu aktivieren. +2. Drücke **'T'** zum Starten (Takeoff). +3. **Tracking aktivieren**: Klicke mit der Maus auf ein erkanntes Gesicht/Person oder drücke **'K'**, um den nächsten erkannten Kopf zu fixieren (Lock-ON). + +### Wichtige Tasten: | Taste | Aktion | | :--- | :--- | -| **T** | **Takeoff** (Starten) - Einmal drücken und kurz warten | +| **T** | **Takeoff** (Abheben) | | **L** | **Land** (Landen) | -| **M** | Wechsel zwischen **Manuell** und **KI-Modus** | -| **K** | Lock-ON Trigger aktivieren / Fixierung löschen | -| **Space**| **Not-Aus** (Stoppt alle Motoren sofort) | -| **W/S** | Vorwärts / Rückwärts (Manuell) | -| **A/D** | Links / Rechts (Manuell) | +| **2** | **Sport-Modus** (An/Aus) - Schnelleres Tracking auf allen Achsen | +| **M** | **Manueller Modus** (KI stoppt die Steuerung) | +| **Space**| **Not-Aus** (Motoren sofort aus) | +| **W/S/A/D**| Vorwärts, Rückwärts, Links, Rechts (Manuell) | | **R/F** | Steigen / Sinken (Manuell) | | **E/Z** | Drehen Links / Rechts (Manuell) | -| **1** | Automatisches Drehen (Scan-Modus) an/aus | -| **Enter**| Programm beenden | +| **Enter**| Programm sicher beenden | -## 🏗 Architektur -- `drone_pilot/main.py`: Das Herzstück. Verwaltet Threads, UI-Events und die Hauptschleife. -- `drone_pilot/vision.py`: Die KI-Engine. Lädt ONNX-Modelle und verarbeitet Bilddaten. -- `drone_pilot/flight.py`: Der Flug-Controller. Berechnet RC-Vektoren basierend auf KI-Ergebnissen. -- `drone_pilot/ui.py`: HUD-Renderer für das OpenCV-Fenster. -- `drone_pilot/config.py`: Zentrale Konfiguration für Geschwindigkeiten und Schwellenwerte. - -## 🛠 Installation & Start -1. Installiere die Abhängigkeiten: `pip install -r requirements.txt` -2. Stelle sicher, dass die Modelle im Ordner `models/` liegen. -3. Starte das Programm: - - Simulator: `python run.py` (Default: `use_real_tello=False`) - - Reale Drohne: Ändere in `run.py` zu `use_real_tello=True` +## 🏗 Architektur & Performance +- **AI-Worker Thread**: Verarbeitet alle Modelle parallel zum Haupt-Thread. +- **Rate Limiting**: RC-Befehle werden mit 10Hz gesendet, um das SDK-Protokoll nicht zu überlasten. +- **ONNX Acceleration**: Nutzt CPU-Optimierungen, die bis zu 5x schneller als Standard-PyTorch sind. --- -*Hinweis: Beim Fliegen mit der realen Drohne immer auf einen Akkustand > 15% achten, da der Startbefehl sonst vom SDK abgelehnt wird.* +*Viel Spaß beim Fliegen! Achte bei der echten Drohne immer auf genügend Platz und einen vollen Akku (>15%).* diff --git a/blaze_face_long_range.tflite b/blaze_face_long_range.tflite new file mode 100644 index 0000000..353e922 --- /dev/null +++ b/blaze_face_long_range.tflite @@ -0,0 +1 @@ +NoSuchKeyThe specified key does not exist.
No such object: mediapipe-models/face_detector/blaze_face_long_range/float16/latest/blaze_face_long_range.tflite
\ No newline at end of file diff --git a/drone_pilot/config.py b/drone_pilot/config.py index 5c90950..bdd6f13 100644 --- a/drone_pilot/config.py +++ b/drone_pilot/config.py @@ -1,6 +1,6 @@ # drone_pilot/config.py class Config: - WIN_NAME = "Tello AI Pilot v2.0 (High Speed)" + WIN_NAME = "Tello AI Pilot v2.0 (Extreme Speed)" WIDTH, HEIGHT = 1024, 720 TARGET_ALTITUDE = 1.5 @@ -8,21 +8,28 @@ class Config: TARGET_PERSON_SIZE = 400 ALT_THRESHOLD = 0.12 - YAW_GAIN = 0.08 # Reduced for smoother rotation - FORWARD_GAIN = 1.5 # Kept high for fast pursuit + # Normal Mode Gains + YAW_GAIN = 0.12 + FORWARD_GAIN = 1.5 ALT_GAIN = 40 + # Sport Mode Gains (Much more aggressive) + SPORT_YAW_GAIN = 0.25 + SPORT_FB_GAIN = 2.0 + SPORT_LR_GAIN = 0.6 + DEPTH_THRESHOLD = 0.90 OBSTACLE_TOF_CM = 70 - FACE_DEADZONE = 30 # Slightly larger deadzone for stability - FACE_ROT_ONLY = 100 + FACE_DEADZONE = 20 + FACE_ROT_ONLY = 80 PERSON_CONF_THRESHOLD = 0.5 - SMOOTHING_ALPHA = 0.35 # High directness, but slightly more damped than extreme + SMOOTHING_ALPHA = 0.35 class Colors: GREEN = (0, 255, 0) RED = (0, 0, 255) BLUE = (255, 0, 0) + PURPLE = (255, 0, 255) WHITE = (255, 255, 255) BLACK = (0, 0, 0) HUD_BG = (10, 10, 10) diff --git a/drone_pilot/flight.py b/drone_pilot/flight.py index c204262..3a20008 100644 --- a/drone_pilot/flight.py +++ b/drone_pilot/flight.py @@ -4,19 +4,24 @@ from typing import List, Tuple, Dict from .config import Config class FlightController: + """ + Verantwortlich für die Berechnung der Flugvektoren basierend auf KI-Ergebnissen. + Unterstützt Normal-Modus (sequenziell) und Sport-Modus (simultan/LR). + """ def __init__(self): self.last_sent_rc = [0, 0, 0, 0] self.smooth_face = None self.search_start = time.time() self.status = "INITIALIZING" - # Memory for lost targets - self.last_target_side = 0 # -1 for left, 1 for right + # Speicher für verloren gegangene Ziele + self.last_target_side = 0 self.lost_time = 0 def calculate(self, faces: List[Tuple], is_manual: bool, + is_sport: bool, # NEU: Sport-Modus Flag emergency_stop: bool, is_locked: bool, locked_person: Tuple, @@ -29,7 +34,6 @@ class FlightController: lr, fb, ud, yv = 0, 0, 0, 0 - # Face smoothing for UI/Visuals if len(faces) > 0: target = max(faces, key=lambda f: f[2] * f[3]) if self.smooth_face is None: self.smooth_face = target @@ -42,13 +46,13 @@ class FlightController: self.status = "EMERGENCY STOP" return (0, 0, 0, 0), self.status - # Obstacle Avoidance (always active if flying) + # Hindernisvermeidung center_blocked = zones["CENTER"] or tof < Config.OBSTACLE_TOF_CM if center_blocked: self.status = "AVOIDING OBSTACLE" yv = 80 if zone_scores["LEFT"] < zone_scores["RIGHT"] else -80 fb = -30 - return self._smooth(lr, fb, ud, yv) + return self._smooth(0, fb, 0, yv) if is_manual: self.status = "MANUAL CONTROL" @@ -59,54 +63,57 @@ class FlightController: # AI LOGIC if is_locked: if locked_person is not None: - # Target is visible -> Normal Pursuit self.search_start = time.time() self.lost_time = 0 (x, y, w, h) = locked_person center_x = x + w // 2 err_x = center_x - (Config.WIDTH // 2) - # Remember which side it was on self.last_target_side = 1 if err_x > 0 else -1 - # Rotation (Yaw) - SMOOTHER - if abs(err_x) > Config.FACE_DEADZONE: - yv = int(np.clip(Config.YAW_GAIN * err_x, -50, 50)) - - # Forward/Backward pursuit - EXTREME SPEED - alignment_factor = max(0.4, 1.0 - (abs(err_x) / Config.FACE_ROT_ONLY)) - target_fb = int(np.clip(Config.FORWARD_GAIN * (Config.TARGET_PERSON_SIZE - w), -90, 90)) - fb = int(target_fb * alignment_factor) - - self.status = "PURSUIT: EXTREME" + if is_sport: + # SPORT MODUS: Alles gleichzeitig + LR-Strafing + yv = int(np.clip(Config.SPORT_YAW_GAIN * err_x, -100, 100)) + fb = int(np.clip(Config.SPORT_FB_GAIN * (Config.TARGET_PERSON_SIZE - w), -100, 100)) + lr = int(np.clip(Config.SPORT_LR_GAIN * err_x, -60, 60)) + self.status = "SPORT PURSUIT: FULL AXIS" + else: + # NORMAL MODUS: Sequenziell (Drehen ODER Fliegen) + if abs(err_x) > Config.FACE_DEADZONE: + yv = int(np.clip(Config.YAW_GAIN * err_x, -50, 50)) + fb = 0 + self.status = "PURSUIT: AIMING" + else: + yv = 0 + fb = int(np.clip(Config.FORWARD_GAIN * (Config.TARGET_PERSON_SIZE - w), -80, 80)) + self.status = "PURSUIT: APPROACHING" else: - # Target is LOST -> Rapid Search logic + # Target verloren if self.lost_time == 0: self.lost_time = time.time() elapsed = time.time() - self.lost_time - if elapsed < 10.0: # Search longer and faster - yv = 40 * self.last_target_side + search_speed = 60 if is_sport else 40 + if elapsed < 10.0: + yv = search_speed * self.last_target_side self.status = f"LOST TARGET: SCANNING {'RIGHT' if self.last_target_side > 0 else 'LEFT'}" else: - self.status = "TARGET LOST: AGGRESSIVE PATROL" + self.status = "TARGET LOST: PATROL" yv = 30 elif self.smooth_face is not None: - # Face found but not locked (x, y, w, h) = self.smooth_face err_x = (x + w // 2) - (Config.WIDTH // 2) - if abs(err_x) > Config.FACE_DEADZONE: - yv = int(np.clip(Config.YAW_GAIN * err_x, -40, 40)) + yv = int(np.clip(Config.YAW_GAIN * err_x, -40, 40)) self.status = "AWAITING LOCK" else: - # Patrol mode - faster - elapsed = (time.time() - self.search_start) % 6.0 - if elapsed < 2.0: - self.status = "PATROL: DASH" - fb = 40 + # Patrouille + elapsed = (time.time() - self.search_start) % 8.0 + if elapsed < 3.0: + self.status = "PATROL: ADVANCE" + fb = 35 else: self.status = "PATROL: SCAN" - yv = 30 + yv = 35 return self._smooth(lr, fb, ud, yv) @@ -117,10 +124,10 @@ class FlightController: sud = int(self.last_sent_rc[2] * (1-alpha) + ud * alpha) syv = int(self.last_sent_rc[3] * (1-alpha) + yv * alpha) - if abs(slr) < 2: slr = 0 - if abs(sfb) < 2: sfb = 0 - if abs(sud) < 2: sud = 0 - if abs(syv) < 2: syv = 0 + if abs(slr) < 3: slr = 0 + if abs(sfb) < 3: sfb = 0 + if abs(sud) < 3: sud = 0 + if abs(syv) < 3: syv = 0 self.last_sent_rc = [slr, sfb, sud, syv] return (slr, sfb, sud, syv), self.status diff --git a/drone_pilot/main.py b/drone_pilot/main.py index 585abca..1a88864 100644 --- a/drone_pilot/main.py +++ b/drone_pilot/main.py @@ -23,6 +23,7 @@ class FaceTrackingApp: # State Management self.is_running = True self.is_manual = True + self.is_sport = False # NEW: Sport Mode flag self.is_locked = False self.is_taking_off = False self.is_flying = False @@ -180,9 +181,10 @@ class FaceTrackingApp: self.locked_person_features = feat self.is_locked = True self.lock_trigger = False + self.is_manual = False break - # UI + # UI Graphics if self.is_locked and self.locked_person: (x,y,w,h) = self.locked_person cv2.rectangle(frame, (x,y), (x+w,y+h), Colors.BLUE, 3) @@ -204,6 +206,7 @@ class FaceTrackingApp: rc, status = self.flight_controller.calculate( faces=faces, is_manual=self.is_manual, + is_sport=self.is_sport, # NEW emergency_stop=self.emergency_stop, is_locked=self.is_locked, locked_person=self.locked_person, @@ -215,7 +218,8 @@ class FaceTrackingApp: manual_rc=active_manual_rc ) - # Throttle and optimized sending + if self.is_sport: status = "SPORT MODE: ACTIVE" + now = time.time() if now - self.last_rc_time >= 0.1: changed = any(abs(rc[i] - self._prev_rc[i]) > 1 for i in range(4)) @@ -250,7 +254,6 @@ class FaceTrackingApp: def _handle_takeoff(self): if self.is_taking_off or self.is_flying: return - self.is_taking_off = True self.takeoff_error = False def _task(): @@ -265,13 +268,13 @@ class FaceTrackingApp: self.takeoff_error = True finally: self.is_taking_off = False - threading.Thread(target=_task, daemon=True).start() def _handle_input(self, key: int): if key == 13: self.is_running = False elif key == 32: self.emergency_stop = not self.emergency_stop elif key == ord('m'): self.is_manual = not self.is_manual + elif key == ord('2'): self.is_sport = not self.is_sport # Toggle Sport Mode elif key == ord('k'): self.lock_trigger = not self.lock_trigger self.is_locked = False @@ -287,11 +290,9 @@ class FaceTrackingApp: except: pass elif key == ord('1'): self.is_rotating = not self.is_rotating - # Reset manual speed self.m_lr, self.m_fb, self.m_ud, self.m_yv = 0, 0, 0, 0 - if self.is_manual and not self.emergency_stop: - s = 100 # Maximum manual speed + s = 100 if key == ord('w'): self.m_fb = s elif key == ord('s'): self.m_fb = -s elif key == ord('a'): self.m_lr = -s diff --git a/models/face_detection_yunet.onnx b/models/face_detection_yunet.onnx new file mode 100644 index 0000000..1ccd411 --- /dev/null +++ b/models/face_detection_yunet.onnx @@ -0,0 +1,1462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/models/midas_small.onnx b/models/midas_small.onnx new file mode 100644 index 0000000..d98aa7f Binary files /dev/null and b/models/midas_small.onnx differ diff --git a/models/midas_small.onnx.data b/models/midas_small.onnx.data new file mode 100644 index 0000000..fde3def Binary files /dev/null and b/models/midas_small.onnx.data differ diff --git a/models/mobilenet_v3.onnx b/models/mobilenet_v3.onnx new file mode 100644 index 0000000..0a2d4e1 Binary files /dev/null and b/models/mobilenet_v3.onnx differ diff --git a/models/mobilenet_v3.onnx.data b/models/mobilenet_v3.onnx.data new file mode 100644 index 0000000..087658c Binary files /dev/null and b/models/mobilenet_v3.onnx.data differ diff --git a/models/reid_mobilenet.onnx b/models/reid_mobilenet.onnx new file mode 100644 index 0000000..c61c2da Binary files /dev/null and b/models/reid_mobilenet.onnx differ diff --git a/models/reid_mobilenet.onnx.data b/models/reid_mobilenet.onnx.data new file mode 100644 index 0000000..106f5cb Binary files /dev/null and b/models/reid_mobilenet.onnx.data differ diff --git a/models/yolov8n.onnx b/models/yolov8n.onnx new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 91dab7c..cac174e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,10 @@ ursina==7.0.0 PyOpenGL==3.1.9 -numpy==2.2.3 -opencv-python==4.11.0.86 \ No newline at end of file +numpy>=2.0.0 +opencv-python>=4.10.0.0 +opencv-contrib-python>=4.10.0.0 +djitellopy>=2.5.0 +onnxruntime>=1.19.0 +torch>=2.0.0 +torchvision>=0.15.0 +Pillow>=10.0.0