Compare commits
10 commits
6025583bb7
...
1ba943c76c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ba943c76c | ||
|
|
af06acba60 | ||
|
|
d0108d5290 | ||
|
|
c20859f0d4 | ||
|
|
678ea96e26 | ||
|
|
934f221e0d | ||
|
|
a733aca3eb | ||
|
|
345299afa9 | ||
|
|
bb9e358c10 | ||
|
|
909aaa71c6 |
5 changed files with 73 additions and 9 deletions
BIN
M2_Projektbericht_Interaktive_Visualisierung_Objekterkennung.pdf
Normal file
BIN
M2_Projektbericht_Interaktive_Visualisierung_Objekterkennung.pdf
Normal file
Binary file not shown.
15
README.md
15
README.md
|
|
@ -8,6 +8,8 @@ Diese Anwendung ist eine interaktive Bildungssoftware für den Raspberry Pi 4/5
|
|||
|
||||
Wo Standard-Anwendungen die Objekterkennung oft als Blackbox behandeln, bricht diese Software die komplexen Prozesse auf und visualisiert sie verständlich. Der Nutzer wird Schritt für Schritt durch die Pipeline geführt – beginnend bei der Rohdatenerfassung des IMX500-Sensors bis hin zur grafischen Darstellung der Erkennungsergebnisse. Um unterschiedlichen Wissensständen gerecht zu werden, bietet die UI zwei Modi ("Schüler:innen" und "Student:innen"), was sie zu einem idealen Werkzeug für Lehrende an Schulen, Universitäten sowie für Exponate auf Fachmessen macht.
|
||||
|
||||
Einen ausführlichen Projektbericht finden Sie unter: [Projektbericht](https://github.com/watsonove/IMX500-Object-Detection-UI/blob/main/M2_Projektbericht_Interaktive_Visualisierung_Objekterkennung.pdf).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
1. **Live-Objektdetektion:** Nutzt den Hardware-Beschleuniger des IMX500 Sensors.
|
||||
|
|
@ -38,6 +40,13 @@ Wo Standard-Anwendungen die Objekterkennung oft als Blackbox behandeln, bricht d
|
|||
|
||||
* Darstellung von Bounding Boxes und Confidence Scores.
|
||||
|
||||
## 📹 Walkthrough
|
||||
|
||||
Durch Anklicken des Bildes können Sie einen Walkthrough der Anwendung abrufen.
|
||||
|
||||
[](https://www.youtube.com/watch?v=wMuOgq-UNXw)
|
||||
|
||||
|
||||
## 🛠 Hardware-Voraussetzungen
|
||||
|
||||
* **Raspberry Pi 4 oder 5**
|
||||
|
|
@ -64,7 +73,7 @@ Stelle sicher, dass alle Projektdateien (`app.py`, `detector.py`, `steps.py`, Or
|
|||
|
||||
Alle Befehle werden im Terminal ausgeführt.
|
||||
|
||||
Das System benötigt Python 3, `picamera2` (vorinstalliert auf Bookworm) und `pygame`, sowie die IMX500 firmware `imx500`.
|
||||
Das System benötigt `Python 3.11`, `picamera2` (vorinstalliert auf Bookworm) und `pygame`, sowie die IMX500 firmware `imx500`.
|
||||
|
||||
Zuerst sicher gehen, dass der Raspberry PI die aktuelle Software hat:
|
||||
|
||||
|
|
@ -104,6 +113,8 @@ Stelle sicher, dass die Ordnerstruktur korrekt ist (siehe unten "Projektstruktur
|
|||
|
||||
Starte die Anwendung über das Terminal. Du musst den Pfad zu deiner Model-Datei angeben (z. B. ein MobileNet oder EfficientDet Modell, das für den IMX500 kompiliert ist).
|
||||
|
||||
Hier mit mobilenetv2:
|
||||
|
||||
```bash
|
||||
|
||||
python3 app.py --model=/usr/share/imx500-models/imx500_network_ssd_mobilenetv2_fpnlite_320x320_pp.rpk
|
||||
|
|
@ -119,7 +130,7 @@ Die Anwendung ist für Tastatur- und Mausbedienung optimiert.
|
|||
| Taste / Aktion | Funktion |
|
||||
| :----------------- | :---------------------------------------------------------------------- |
|
||||
| **LEERTASTE** | **Freeze / Unfreeze:** Wechselt zwischen Live-Kamera und Analyse-Modus. |
|
||||
| **ENTER** | **Weiter:** Geht zum nächsten Schritt oder bestätigt das "Gate". |
|
||||
| **ENTER** | **Weiter:** Geht zum nächsten Schritt oder bestätigt das Gate. |
|
||||
| **BACKSPACE** | **Zurück:** Geht zum vorherigen Schritt oder zurück zum Gate. |
|
||||
| **Mausklick** | Bedienung der UI-Buttons (Sprache, Home, Audio, Level-Wahl). |
|
||||
| **Mausbewegung** | Im "Schritt 1" (Analyse): Zeigt RGB-Werte unter dem Mauszeiger an. |
|
||||
|
|
|
|||
61
app.py
61
app.py
|
|
@ -45,12 +45,16 @@ class App:
|
|||
self.landing_bg_path = "assets/landingpagebg.jpg"
|
||||
self.font_path = "assets/Kanit-Bold.ttf"
|
||||
self.audio_dir = os.path.join(self.base_dir, "assets", "audio")
|
||||
self.qr_path = os.path.join(self.base_dir, "assets", "qr.png")
|
||||
|
||||
# Background caches
|
||||
# Background & Image caches
|
||||
self._landing_bg_original = None
|
||||
self._landing_bg_scaled = None
|
||||
self._landing_bg_scaled_size = None
|
||||
self._qr_code_img = None
|
||||
|
||||
self._load_landing_bg()
|
||||
self._load_qr_code()
|
||||
|
||||
# Application State
|
||||
self.running = True
|
||||
|
|
@ -242,6 +246,13 @@ class App:
|
|||
self._landing_bg_scaled = None
|
||||
self._landing_bg_scaled_size = None
|
||||
|
||||
def _load_qr_code(self) -> None:
|
||||
try:
|
||||
self._qr_code_img = pygame.image.load(self.qr_path).convert_alpha()
|
||||
except Exception as e:
|
||||
print(f"Warning: QR Code not found at {self.qr_path} ({e})")
|
||||
self._qr_code_img = None
|
||||
|
||||
def _get_landing_bg_scaled(self) -> pygame.Surface | None:
|
||||
if self._landing_bg_original is None:
|
||||
return None
|
||||
|
|
@ -421,15 +432,59 @@ class App:
|
|||
self.renderer.draw_card(self.screen, panel_rect, fill=self.theme.PANEL_2, outline=self.theme.LINE, radius=self.theme.RADIUS)
|
||||
header_h = int(60 * self._scale())
|
||||
self.renderer.draw_text(self.screen, self.font_header, "Ergebnisse", (panel_rect.x + 20, panel_rect.y + 20), self.theme.TEXT)
|
||||
|
||||
# 1. Definiere den Bereich für das Diagramm
|
||||
chart_top_y = panel_rect.y + header_h
|
||||
# Wir reservieren ca. 35% der Panel-Höhe für das Diagramm (oder fix 160px skalierbar)
|
||||
chart_height = int(160 * self._scale())
|
||||
|
||||
content_rect = pygame.Rect(
|
||||
panel_rect.x + 14,
|
||||
panel_rect.y + header_h,
|
||||
chart_top_y,
|
||||
panel_rect.w - 28,
|
||||
panel_rect.h - header_h - 14
|
||||
chart_height
|
||||
)
|
||||
|
||||
# Zeichne Diagramm
|
||||
chart_font = self._load_font(self._fsize(26))
|
||||
self.renderer.draw_bar_chart(self.screen, content_rect, self.snapshot.top3, self.args.threshold, chart_font, self.font_ui)
|
||||
|
||||
# Draw QR Code & Bias Info only in Student Step 7
|
||||
if self.level == "STUDENT" and self.step == 7:
|
||||
# Layout Cursor: Wo sind wir jetzt? (Unterhalb des Diagramms)
|
||||
cursor_y = content_rect.bottom + int(30 * self._scale())
|
||||
|
||||
# 1. Info Text (Bias/Fehler)
|
||||
bias_text = "Hinweis: KI ist nicht objektiv (Bias). Fehler sind möglich!" if self.lang == "DE" else "Note: AI is not objective (Bias). Errors are possible!"
|
||||
txt_surf = self.font_small.render(bias_text, True, self.theme.WARN)
|
||||
txt_rect = txt_surf.get_rect(centerx=panel_rect.centerx, top=cursor_y)
|
||||
self.screen.blit(txt_surf, txt_rect)
|
||||
|
||||
# Update Cursor (Text-Höhe + Padding)
|
||||
cursor_y = txt_rect.bottom + int(20 * self._scale())
|
||||
|
||||
# 2. QR Code (Nur wenn Platz ist)
|
||||
if self._qr_code_img:
|
||||
available_h = panel_rect.bottom - cursor_y - 20
|
||||
|
||||
if available_h > 80:
|
||||
# Maximal verfügbare Breite/Höhe nutzen, aber nicht riesig werden
|
||||
size = min(content_rect.w - 40, available_h)
|
||||
size = min(size, int(300 * self._scale())) # Cap size
|
||||
|
||||
# Label above QR
|
||||
lbl = self.font_small.render("Source Code (GitHub)", True, self.theme.TEXT_MUTED)
|
||||
lbl_rect = lbl.get_rect(centerx=panel_rect.centerx, top=cursor_y)
|
||||
self.screen.blit(lbl, lbl_rect)
|
||||
|
||||
cursor_y = lbl_rect.bottom + 5
|
||||
|
||||
# Draw QR Code
|
||||
scaled_qr = pygame.transform.smoothscale(self._qr_code_img, (int(size), int(size)))
|
||||
qr_rect = scaled_qr.get_rect(centerx=panel_rect.centerx, top=cursor_y)
|
||||
|
||||
self.screen.blit(scaled_qr, qr_rect)
|
||||
|
||||
def _draw_home_button(self) -> None:
|
||||
pad = self.pad
|
||||
w, h = self.home_btn_w, self.home_btn_h
|
||||
|
|
|
|||
BIN
assets/qr.png
Normal file
BIN
assets/qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -101,7 +101,6 @@ class Renderer:
|
|||
pygame.draw.line(surface, self.t.BG, (x, rect.y), (x, rect.bottom), 2)
|
||||
|
||||
# Draw Text Indicator above
|
||||
# Hier wurde das Padding erhöht (rect.y - 12 statt -4)
|
||||
label = f"{step} / {total_steps}"
|
||||
txt = font_small.render(label, True, self.t.TEXT_MUTED)
|
||||
txt_rect = txt.get_rect(centerx=rect.centerx, bottom=rect.y - 12)
|
||||
|
|
@ -139,7 +138,6 @@ class Renderer:
|
|||
pygame.draw.line(surface, self.t.ACCENT, (thr_x, chart_y - 8), (thr_x, chart_y + chart_h + 8), 2)
|
||||
|
||||
if not top3:
|
||||
# self.draw_text(surface, body_font, "...", (chart_x, chart_y), self.t.TEXT_MUTED)
|
||||
return
|
||||
|
||||
for i, (lab, conf) in enumerate(top3[:3]):
|
||||
|
|
@ -153,6 +151,6 @@ class Renderer:
|
|||
bw = int(chart_w * clamp01(conf))
|
||||
pygame.draw.rect(surface, bar_col, pygame.Rect(chart_x, y, bw, row_h), border_radius=10)
|
||||
|
||||
# Label Text
|
||||
lbl_surf = body_font.render(f"{lab} {int(conf*100)}%", True, self.t.TEXT)
|
||||
# Label Text - JETZT IN SCHWARZ (0, 0, 0)
|
||||
lbl_surf = body_font.render(f"{lab} {int(conf*100)}%", True, (0, 0, 0))
|
||||
surface.blit(lbl_surf, (chart_x + 8, y + (row_h - lbl_surf.get_height()) // 2))
|
||||
|
|
|
|||
Loading…
Reference in a new issue