add models

This commit is contained in:
Sebastian Unterschütz
2026-04-21 15:45:43 +02:00
parent f5d344cf75
commit e72ba2989e
14 changed files with 1589 additions and 79 deletions
+56 -30
View File
@@ -4,43 +4,69 @@ Dieses Projekt ist eine hochoptimierte KI-Steuerung für die DJI Tello Drohne (u
## 🚀 Hauptmerkmale ## 🚀 Hauptmerkmale
- **Hybrid AI Engine**: Nutzt **YuNet** (Face Detection), **MiDaS** (Depth Estimation) und **MobileNetV3** (ReID) alle via **ONNX Runtime** für maximale FPS. - **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. - **Sport-Modus**: Aggressives 3-Achsen-Tracking für schnelle Verfolgungsjagden.
- **Visual Fingerprinting (ReID)**: Die Drohne erstellt einen digitalen Fingerabdruck einer fixierten Person und findet sie automatisch wieder, wenn sie kurz aus dem Bild verschwindet. - **Visual Fingerprinting (ReID)**: Erkennt fixierte Personen wieder, auch wenn sie das Bild kurz verlassen.
- **Intelligente Verfolgung**: Aktive Suche in Verschwindungsrichtung und aggressiver Verfolgungsmodus. - **Multithreading**: Flüssiges Kamerabild durch Trennung von Video-Stream und KI-Logik.
- **Echtzeit-HUD**: Professionelles Display mit Telemetrie, AI-Tiefenkarte und Radar-Zonen zur Hindernisvermeidung. - **Simulator inklusive**: Eine integrierte Ursina-Umgebung zum gefahrlosen Testen.
- **Rate Limiting**: Kontrollierter Datenfluss (10Hz) zum Tello-SDK für maximale Verbindungsstabilität.
## 🕹 Steuerung ## 🛠 Installation
Klicke mit der **Maus** direkt in das Videobild, um eine Person zu fixieren (**Lock-ON**).
### 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 | | Taste | Aktion |
| :--- | :--- | | :--- | :--- |
| **T** | **Takeoff** (Starten) - Einmal drücken und kurz warten | | **T** | **Takeoff** (Abheben) |
| **L** | **Land** (Landen) | | **L** | **Land** (Landen) |
| **M** | Wechsel zwischen **Manuell** und **KI-Modus** | | **2** | **Sport-Modus** (An/Aus) - Schnelleres Tracking auf allen Achsen |
| **K** | Lock-ON Trigger aktivieren / Fixierung löschen | | **M** | **Manueller Modus** (KI stoppt die Steuerung) |
| **Space**| **Not-Aus** (Stoppt alle Motoren sofort) | | **Space**| **Not-Aus** (Motoren sofort aus) |
| **W/S** | Vorwärts / Rückwärts (Manuell) | | **W/S/A/D**| Vorwärts, Rückwärts, Links, Rechts (Manuell) |
| **A/D** | Links / Rechts (Manuell) |
| **R/F** | Steigen / Sinken (Manuell) | | **R/F** | Steigen / Sinken (Manuell) |
| **E/Z** | Drehen Links / Rechts (Manuell) | | **E/Z** | Drehen Links / Rechts (Manuell) |
| **1** | Automatisches Drehen (Scan-Modus) an/aus | | **Enter**| Programm sicher beenden |
| **Enter**| Programm beenden |
## 🏗 Architektur ## 🏗 Architektur & Performance
- `drone_pilot/main.py`: Das Herzstück. Verwaltet Threads, UI-Events und die Hauptschleife. - **AI-Worker Thread**: Verarbeitet alle Modelle parallel zum Haupt-Thread.
- `drone_pilot/vision.py`: Die KI-Engine. Lädt ONNX-Modelle und verarbeitet Bilddaten. - **Rate Limiting**: RC-Befehle werden mit 10Hz gesendet, um das SDK-Protokoll nicht zu überlasten.
- `drone_pilot/flight.py`: Der Flug-Controller. Berechnet RC-Vektoren basierend auf KI-Ergebnissen. - **ONNX Acceleration**: Nutzt CPU-Optimierungen, die bis zu 5x schneller als Standard-PyTorch sind.
- `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`
--- ---
*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%).*
+1
View File
@@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Details>No such object: mediapipe-models/face_detector/blaze_face_long_range/float16/latest/blaze_face_long_range.tflite</Details></Error>
+13 -6
View File
@@ -1,6 +1,6 @@
# drone_pilot/config.py # drone_pilot/config.py
class Config: class Config:
WIN_NAME = "Tello AI Pilot v2.0 (High Speed)" WIN_NAME = "Tello AI Pilot v2.0 (Extreme Speed)"
WIDTH, HEIGHT = 1024, 720 WIDTH, HEIGHT = 1024, 720
TARGET_ALTITUDE = 1.5 TARGET_ALTITUDE = 1.5
@@ -8,21 +8,28 @@ class Config:
TARGET_PERSON_SIZE = 400 TARGET_PERSON_SIZE = 400
ALT_THRESHOLD = 0.12 ALT_THRESHOLD = 0.12
YAW_GAIN = 0.08 # Reduced for smoother rotation # Normal Mode Gains
FORWARD_GAIN = 1.5 # Kept high for fast pursuit YAW_GAIN = 0.12
FORWARD_GAIN = 1.5
ALT_GAIN = 40 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 DEPTH_THRESHOLD = 0.90
OBSTACLE_TOF_CM = 70 OBSTACLE_TOF_CM = 70
FACE_DEADZONE = 30 # Slightly larger deadzone for stability FACE_DEADZONE = 20
FACE_ROT_ONLY = 100 FACE_ROT_ONLY = 80
PERSON_CONF_THRESHOLD = 0.5 PERSON_CONF_THRESHOLD = 0.5
SMOOTHING_ALPHA = 0.35 # High directness, but slightly more damped than extreme SMOOTHING_ALPHA = 0.35
class Colors: class Colors:
GREEN = (0, 255, 0) GREEN = (0, 255, 0)
RED = (0, 0, 255) RED = (0, 0, 255)
BLUE = (255, 0, 0) BLUE = (255, 0, 0)
PURPLE = (255, 0, 255)
WHITE = (255, 255, 255) WHITE = (255, 255, 255)
BLACK = (0, 0, 0) BLACK = (0, 0, 0)
HUD_BG = (10, 10, 10) HUD_BG = (10, 10, 10)
+41 -34
View File
@@ -4,19 +4,24 @@ from typing import List, Tuple, Dict
from .config import Config from .config import Config
class FlightController: 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): def __init__(self):
self.last_sent_rc = [0, 0, 0, 0] self.last_sent_rc = [0, 0, 0, 0]
self.smooth_face = None self.smooth_face = None
self.search_start = time.time() self.search_start = time.time()
self.status = "INITIALIZING" self.status = "INITIALIZING"
# Memory for lost targets # Speicher für verloren gegangene Ziele
self.last_target_side = 0 # -1 for left, 1 for right self.last_target_side = 0
self.lost_time = 0 self.lost_time = 0
def calculate(self, def calculate(self,
faces: List[Tuple], faces: List[Tuple],
is_manual: bool, is_manual: bool,
is_sport: bool, # NEU: Sport-Modus Flag
emergency_stop: bool, emergency_stop: bool,
is_locked: bool, is_locked: bool,
locked_person: Tuple, locked_person: Tuple,
@@ -29,7 +34,6 @@ class FlightController:
lr, fb, ud, yv = 0, 0, 0, 0 lr, fb, ud, yv = 0, 0, 0, 0
# Face smoothing for UI/Visuals
if len(faces) > 0: if len(faces) > 0:
target = max(faces, key=lambda f: f[2] * f[3]) target = max(faces, key=lambda f: f[2] * f[3])
if self.smooth_face is None: self.smooth_face = target if self.smooth_face is None: self.smooth_face = target
@@ -42,13 +46,13 @@ class FlightController:
self.status = "EMERGENCY STOP" self.status = "EMERGENCY STOP"
return (0, 0, 0, 0), self.status return (0, 0, 0, 0), self.status
# Obstacle Avoidance (always active if flying) # Hindernisvermeidung
center_blocked = zones["CENTER"] or tof < Config.OBSTACLE_TOF_CM center_blocked = zones["CENTER"] or tof < Config.OBSTACLE_TOF_CM
if center_blocked: if center_blocked:
self.status = "AVOIDING OBSTACLE" self.status = "AVOIDING OBSTACLE"
yv = 80 if zone_scores["LEFT"] < zone_scores["RIGHT"] else -80 yv = 80 if zone_scores["LEFT"] < zone_scores["RIGHT"] else -80
fb = -30 fb = -30
return self._smooth(lr, fb, ud, yv) return self._smooth(0, fb, 0, yv)
if is_manual: if is_manual:
self.status = "MANUAL CONTROL" self.status = "MANUAL CONTROL"
@@ -59,54 +63,57 @@ class FlightController:
# AI LOGIC # AI LOGIC
if is_locked: if is_locked:
if locked_person is not None: if locked_person is not None:
# Target is visible -> Normal Pursuit
self.search_start = time.time() self.search_start = time.time()
self.lost_time = 0 self.lost_time = 0
(x, y, w, h) = locked_person (x, y, w, h) = locked_person
center_x = x + w // 2 center_x = x + w // 2
err_x = center_x - (Config.WIDTH // 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 self.last_target_side = 1 if err_x > 0 else -1
# Rotation (Yaw) - SMOOTHER if is_sport:
if abs(err_x) > Config.FACE_DEADZONE: # SPORT MODUS: Alles gleichzeitig + LR-Strafing
yv = int(np.clip(Config.YAW_GAIN * err_x, -50, 50)) 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))
# Forward/Backward pursuit - EXTREME SPEED lr = int(np.clip(Config.SPORT_LR_GAIN * err_x, -60, 60))
alignment_factor = max(0.4, 1.0 - (abs(err_x) / Config.FACE_ROT_ONLY)) self.status = "SPORT PURSUIT: FULL AXIS"
target_fb = int(np.clip(Config.FORWARD_GAIN * (Config.TARGET_PERSON_SIZE - w), -90, 90)) else:
fb = int(target_fb * alignment_factor) # NORMAL MODUS: Sequenziell (Drehen ODER Fliegen)
if abs(err_x) > Config.FACE_DEADZONE:
self.status = "PURSUIT: EXTREME" 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: else:
# Target is LOST -> Rapid Search logic # Target verloren
if self.lost_time == 0: self.lost_time = time.time() if self.lost_time == 0: self.lost_time = time.time()
elapsed = time.time() - self.lost_time elapsed = time.time() - self.lost_time
if elapsed < 10.0: # Search longer and faster search_speed = 60 if is_sport else 40
yv = 40 * self.last_target_side 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'}" self.status = f"LOST TARGET: SCANNING {'RIGHT' if self.last_target_side > 0 else 'LEFT'}"
else: else:
self.status = "TARGET LOST: AGGRESSIVE PATROL" self.status = "TARGET LOST: PATROL"
yv = 30 yv = 30
elif self.smooth_face is not None: elif self.smooth_face is not None:
# Face found but not locked
(x, y, w, h) = self.smooth_face (x, y, w, h) = self.smooth_face
err_x = (x + w // 2) - (Config.WIDTH // 2) 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" self.status = "AWAITING LOCK"
else: else:
# Patrol mode - faster # Patrouille
elapsed = (time.time() - self.search_start) % 6.0 elapsed = (time.time() - self.search_start) % 8.0
if elapsed < 2.0: if elapsed < 3.0:
self.status = "PATROL: DASH" self.status = "PATROL: ADVANCE"
fb = 40 fb = 35
else: else:
self.status = "PATROL: SCAN" self.status = "PATROL: SCAN"
yv = 30 yv = 35
return self._smooth(lr, fb, ud, yv) return self._smooth(lr, fb, ud, yv)
@@ -117,10 +124,10 @@ class FlightController:
sud = int(self.last_sent_rc[2] * (1-alpha) + ud * alpha) sud = int(self.last_sent_rc[2] * (1-alpha) + ud * alpha)
syv = int(self.last_sent_rc[3] * (1-alpha) + yv * alpha) syv = int(self.last_sent_rc[3] * (1-alpha) + yv * alpha)
if abs(slr) < 2: slr = 0 if abs(slr) < 3: slr = 0
if abs(sfb) < 2: sfb = 0 if abs(sfb) < 3: sfb = 0
if abs(sud) < 2: sud = 0 if abs(sud) < 3: sud = 0
if abs(syv) < 2: syv = 0 if abs(syv) < 3: syv = 0
self.last_sent_rc = [slr, sfb, sud, syv] self.last_sent_rc = [slr, sfb, sud, syv]
return (slr, sfb, sud, syv), self.status return (slr, sfb, sud, syv), self.status
+8 -7
View File
@@ -23,6 +23,7 @@ class FaceTrackingApp:
# State Management # State Management
self.is_running = True self.is_running = True
self.is_manual = True self.is_manual = True
self.is_sport = False # NEW: Sport Mode flag
self.is_locked = False self.is_locked = False
self.is_taking_off = False self.is_taking_off = False
self.is_flying = False self.is_flying = False
@@ -180,9 +181,10 @@ class FaceTrackingApp:
self.locked_person_features = feat self.locked_person_features = feat
self.is_locked = True self.is_locked = True
self.lock_trigger = False self.lock_trigger = False
self.is_manual = False
break break
# UI # UI Graphics
if self.is_locked and self.locked_person: if self.is_locked and self.locked_person:
(x,y,w,h) = self.locked_person (x,y,w,h) = self.locked_person
cv2.rectangle(frame, (x,y), (x+w,y+h), Colors.BLUE, 3) cv2.rectangle(frame, (x,y), (x+w,y+h), Colors.BLUE, 3)
@@ -204,6 +206,7 @@ class FaceTrackingApp:
rc, status = self.flight_controller.calculate( rc, status = self.flight_controller.calculate(
faces=faces, faces=faces,
is_manual=self.is_manual, is_manual=self.is_manual,
is_sport=self.is_sport, # NEW
emergency_stop=self.emergency_stop, emergency_stop=self.emergency_stop,
is_locked=self.is_locked, is_locked=self.is_locked,
locked_person=self.locked_person, locked_person=self.locked_person,
@@ -215,7 +218,8 @@ class FaceTrackingApp:
manual_rc=active_manual_rc manual_rc=active_manual_rc
) )
# Throttle and optimized sending if self.is_sport: status = "SPORT MODE: ACTIVE"
now = time.time() now = time.time()
if now - self.last_rc_time >= 0.1: if now - self.last_rc_time >= 0.1:
changed = any(abs(rc[i] - self._prev_rc[i]) > 1 for i in range(4)) 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): def _handle_takeoff(self):
if self.is_taking_off or self.is_flying: return if self.is_taking_off or self.is_flying: return
self.is_taking_off = True self.is_taking_off = True
self.takeoff_error = False self.takeoff_error = False
def _task(): def _task():
@@ -265,13 +268,13 @@ class FaceTrackingApp:
self.takeoff_error = True self.takeoff_error = True
finally: finally:
self.is_taking_off = False self.is_taking_off = False
threading.Thread(target=_task, daemon=True).start() threading.Thread(target=_task, daemon=True).start()
def _handle_input(self, key: int): def _handle_input(self, key: int):
if key == 13: self.is_running = False if key == 13: self.is_running = False
elif key == 32: self.emergency_stop = not self.emergency_stop elif key == 32: self.emergency_stop = not self.emergency_stop
elif key == ord('m'): self.is_manual = not self.is_manual 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'): elif key == ord('k'):
self.lock_trigger = not self.lock_trigger self.lock_trigger = not self.lock_trigger
self.is_locked = False self.is_locked = False
@@ -287,11 +290,9 @@ class FaceTrackingApp:
except: pass except: pass
elif key == ord('1'): self.is_rotating = not self.is_rotating 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 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: if self.is_manual and not self.emergency_stop:
s = 100 # Maximum manual speed s = 100
if key == ord('w'): self.m_fb = s if key == ord('w'): self.m_fb = s
elif key == ord('s'): self.m_fb = -s elif key == ord('s'): self.m_fb = -s
elif key == ord('a'): self.m_lr = -s elif key == ord('a'): self.m_lr = -s
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
+8 -2
View File
@@ -1,4 +1,10 @@
ursina==7.0.0 ursina==7.0.0
PyOpenGL==3.1.9 PyOpenGL==3.1.9
numpy==2.2.3 numpy>=2.0.0
opencv-python==4.11.0.86 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