Thesis
Projekt Link
https://www.tomgromann.com/fischzaehlung/
Dokumentationsvideo
KW40
KW41
- Einarbeitung in YOLO v8
- Erste Tests zur Erkennung zwischen Lachs und Forelle da diese sich sehr ähnlich sehen
- Sammeln von Bildmaterial aus Netz und Videos
- Anmontieren der Bilder in CVAT
- Trainieren von YOLOv8n Modell: 50 Epochen a 780 Bilder
Ergebnis: Erkennung sehr Ungenau und Schwach: Sicherheit unter 10%
Model V1

50 Epochen a 100 Bilder. Bilder ausschließlich aus dem Netz
Boxen sehr ungenau und Erkennungszeichen Sicherheit zu schwach
Model V2

20 Epochen a 780 Bilder. Bilder aus Web und zusätzlich ca 500 aus Video.
Boxen erkennen die position besser aber es wird hauptsächlich die falsche Fischart angezeigt
KW 42
- Überarbeitung der daten (Bilderlast auf 250 reduziert)
- Neues Modell mit YOLO11 trainiert: 200 Epochen
- Erkennung mit YOLO11m
Ergebnis: Genaue Erkennung der arten mit genauigkeit von bis zu 95%
Model V3

Mit Sicherheiten
Die anderen Fische werden hier als Lachs erkannt da diese noch nicht in dem Modell trainiert sind
Weitere Test:
Lachs version2: https://drive.google.com/file/d/1jPfMDVvkTgggAQic628hSsXnxmUB0HYg/view?usp=sharing
Forelle: https://drive.google.com/file/d/11L5tsXLqYvohNwrIvTukk1XDi8CJburJ/view?usp=sharing
KW43
Training des fertigen Modells
Für das Training habe ich das Modell mit 16 verschiedenen Fischarten trainiert.
Das Training erfolgte über 500 Epochen mit einer Bilddatenmenge von 550.
Das Modell erreicht eine Genauigkeit von bis zu 93 %. Vereinzelt treten kurze Fehlkennungen auf.
Stand bis 2. Dez
Zum testen meines Modells wollte ich einen mitschnitt der live cam aufnehmen, der jedoch keine aufnahmen von fischen brachte.
Also startete ich mein Erkennung Modell direkt mit dem livestream. Dabei funktioniert der code so:
- Wenn Erkennung -> Abspeicherung von einem cropped image in Ordnern mit Klassennamen
- Jeder erkannte frame wird hierbei aufgenommen
- Die benötigte confidence habe ich hier bei 75% angelegt
Ergebnis: Bei Fischen wie der Ukelei, die etwas kleiner ist, werden sehr häufig Blätter und Stöcke erkannt.
Ausserdem wird die Putzmaschine erkannt.
Bei Fischen die erkannt werden ist die Genauigkeit bis jetzt sehr hoch.
Bild1: Bilder Von einer Nase, die automatisch aufgenommen wurden.
Bild 2: Putzmaschine (Fälschlicherweise erkannt)
Bild 3: Code zur Erkennung der fische und Speicherung der Bilder




Für die Lösung der Fehlerkennungen trainiere ich Ein Modell mit Blättern, Stöcken und der Putzmaschine, und schliesse diese Klassen bei der Dateiausgabe aus.
Speichern der Daten in CSV
Damit ich die daten für eine live Visualisierung verwenden kann, muss ich durch die Erkennung eines Fisches einen CSV Eintrag generieren.
In diesem CSV Eintrag sind folgende angaben enthalten:
- Datum
- Uhrzeit
- Klasse (Name des Fisches)
Hierfür habe ich einen code geschrieben, der für getreckte Objekte eine id vergibt.
Um kurzzeitige Fehlerkennungen und damit auch Fehleinträge zu minimieren habe ich den code so geschrieben, dass für der Zeitraum, in dem eine id vergeben wurde, die durchschnittliche confidence der für diese ID vergebenen Klassen errechnet werden, und die klasse mit der höchsten confidence eine runde weiter kommt. In der nächsten runde wird diese klasse nun einem erneuten confidence check unterzogene um zu verhindern, dass Klassen mit einem niedrigen Durchschnittswert in die cdv aufgeschrieben werden.
Video: Funktion des Codes
Bild: Wie wird die confidence errechnet

KW 49
Sortieren der Daten
Als Erstes habe ich mich damit beschäftigt, wie ich die Daten visualisieren kann. Hierfür habe ich Google Charts verwendet. Zuerst musste ich jedoch meine Test-Spreadsheet-Datei so umstrukturieren, dass die Daten ausgelesen werden können. Ich habe Daten wie den Zeitpunkt auf die volle Stunde gerundet, um die angezeigten Säulen in einer überschaubaren Anzahl zu halten. Zusätzlich habe ich eine Funktion erstellt, die mir in einer neuen Tabelle bei einer Erkennung die Zahl 1 beim erkannten Fisch einträgt, um mit der Anzahl der Fische rechnen zu können. Dies habe ich zweimal gemacht, da für die Tagesansicht die Stunden im Vordergrund stehen sollen und für die Monatsansicht das Datum.
Hier ist das Spreadsheet:
https://docs.google.com/spreadsheets/d/1lfOq4ARrJq2gbmiyFhC1PFGZP8u_oXbKGkLZj2xRbnw/edit?usp=sharing
Abbildung1: Eingabe der Saten in die csv durch python code
Abbildung2: Ordnung der Daten für 24h Ansicht
Abbildung3: Ordnung der Daten für Monats Ansicht






QUERY Abfragen Testen
Im nächsten Schritt habe ich an den Query-Abfragen gearbeitet:
Vereinfacht gesagt, habe ich bei der täglichen Ansicht eine Abfrage verwendet, die mir alle Daten für ein bestimmtes Datum liefert.
1. Für die monatliche Ansicht musste ich einen Zeitraum festlegen und mir somit alle Daten anzeigen lassen, die innerhalb des ausgewählten Monats liegen.
2. Für die jährliche Ansicht habe ich nur nach dem Jahr sortiert, indem ich lediglich die ersten vier Ziffern (in diesem Fall 2024) ausgewertet habe.
3. Diese Query-Abfragen habe ich in meinem Spreadsheet auf ihre Funktionsfähigkeit geprüft, musste sie jedoch im Code der Google-Charts-Visualisierung noch anpassen.
Beispiel query für Abfrage nach den Daten vom 02.12.2024:
\=QUERY(A1:AP, „SELECT Z, SUM(AA), SUM(AB), SUM(AC), SUM(AD), SUM(AE), SUM(AF), SUM(AG), SUM(AH), SUM(AI), SUM(AJ), SUM(AK), SUM(AL), SUM(AM), SUM(AN), SUM(AO), SUM(AP) WHERE F = '2024-12-02' GROUP BY Z ORDER BY Z LABEL SUM(AA) 'Aal', SUM(AB) 'Neunauge', SUM(AC) 'Maifisch', SUM(AD) 'Brachse', SUM(AE) 'Zobel', SUM(AF) 'Güster', SUM(AG) 'Rapfen', SUM(AH) 'Aland', SUM(AI) 'Ukelei', SUM(AJ) 'Wels', SUM(AK) 'Nase', SUM(AL) 'Barbe', SUM(AM) 'Lachs', SUM(AN) 'Forelle', SUM(AO) 'Döbel', SUM(AP) 'Karpfen'“, 1)
Output in folgendem Bild

Tests mit Google Charts
Im nächsten schritt habe ich mich bei ersten Visualisierungen versucht, mit den getesteten QUERYS.
Versuch mit Teils Variabler Query (Datum über DatePicker gewählt): Abbildung unten.
QUERY für tag:
var dateFilter = `WHERE F = '${selectedDate}'`
query.setQuery(`SELECT G , SUM(H), SUM(I), SUM(J), SUM(K), SUM(L), SUM(M), SUM(N), SUM(O), SUM(P), SUM(Q), SUM(R), SUM(S), SUM(T), SUM(U), SUM(V), SUM(W) ${dateFilter} GROUP BY G ORDER BY G LABEL SUM(H) „Aal“, SUM(I) „Neunauge“, SUM(J) „Maifisch“, SUM(K) „Brachse“, SUM(L) „Zobel“, SUM(M) „Güster“, SUM(N) „Rapfen“, SUM(O) „Aland“, SUM(P) „Ukelei“, SUM(Q) „Wels“, SUM(R) „Nase“, SUM(S) „Barbe“, SUM(T) „Lachs“, SUM(U) „Forelle“, SUM(V) „Döbel“, SUM(W) „Karpfen“`);
Query für Monat:
Vorbereitung in JS:
const [year, month] = selectedDate.split('-');
const startOfMonth = `${year}-${month}-01`
const endOfMonth = new Date(year, month, 0).toISOString().split('T')[0]
var monthFilter = `WHERE F >= '${startOfMonth}' AND F < = '${endOfMonth}'`
QUERY: query.setQuery(`SELECT Z, SUM(AA), SUM(AB), SUM(AC), SUM(AD), SUM(AE), SUM(AF), SUM(AG), SUM(AH), SUM(AI), SUM(AJ), SUM(AK), SUM(AL), SUM(AM), SUM(AN), SUM(AO), SUM(AP) ${monthFilter} GROUP BY Z ORDER BY Z LABEL SUM(AA) „Aal“, SUM(AB) „Neunauge“, SUM(AC) „Maifisch“, SUM(AD) „Brachse“, SUM(AE) „Zobel“, SUM(AF) „Güster“, SUM(AG) „Rapfen“, SUM(AH) „Aland“, SUM(AI) „Ukelei“, SUM(AJ) „Wels“, SUM(AK) „Nase“, SUM(AL) „Barbe“, SUM(AM) „Lachs“, SUM(AN) „Forelle“, SUM(AO) „Döbel“, SUM(AP) „Karpfen“`);

Diese Tests habe ich so lange weitergeführt, bis ich meine vier geplanten Visualisierungen erstellt hatte:
- Ausgewählter Tag (Bar Chart)
- Ausgewählter Monat (Bar Chart)
- Ausgewählter Monat (Line Chart)
- Ausgewähltes Jahr (Pie Chart)

Als ich alle Visualisierungen erstellt hatte, begann ich damit, die Queries weiter variabel zu gestalten, indem ich die Zeilen durch Checkboxen steuern ließ.
Prototyp
Ich habe auf Figma einen Prototypen meiner Web-App mithilfe von Material 3 erstellt. Das Design wollte ich farblos und im Dark Mode gestalten, um nicht zu viele Farben einzusetzen, da ich ohnehin 16 verschiedene Farben für die Charts verwende. Mithilfe von https://projects.susielu.com/viz-palette habe ich ein Farbsystem für meine Charts erstellt. Dabei hat sich herausgestellt, dass ein barrierefreies Farbsystem nicht möglich ist, da ich zu viele Farben benötige. Hier bietet mir der Hover-State der Charts eine Lösung, da er dennoch die Anzahl und die Art des Fisches anzeigt.
Bei der Gestaltung habe ich mich außerdem für Chips entschieden, die sich im aktivierten Zustand passend zu den jeweiligen Fischen einfärben und somit meine Legende ersetzen. Zusätzlich dienen sie als Filter: Beim Deaktivieren eines Chips wird der entsprechende Fisch ausgeblendet.

Bei der Umsetzung hatte ich anfangs einige Probleme, da Komponenten wie die Side-Nav nicht für das Web verfügbar sind. Also entschied ich mich, das Ganze in Next.js mit Next UI umzusetzen. Auch hier hatte ich jedoch Schwierigkeiten, die Packages korrekt zu laden, und entschied mich schließlich, alles in Vanilla JavaScript umzusetzen.
Außerdem wollte ich die einzelnen Charts großflächig untereinander anordnen. Die Side-Nav soll dabei die aktuelle Position anzeigen.
KW 50
Fertiggestellte Web App
HTML Code: https://tundra-scarecrow-a32.notion.site/HTML-CODE-15d0c7aba0d380e7b1fdc5bf19f9eb1b
Im nächsten schritt habe ich vereinzelt testings durchgeführt.
Testing Ergebnisse:
- Marker für spitzen in populations verlauf (Damit klar ist dass diese hoverbar sind
- Datums format auf deutsches format
- Uhrzeit Kürzer von 16:00:00 -> 16:00
- Ein alles abwählen Button für die Filter
Neues YOLO11 Modell
Ich habe ein neues YOLO-Modell trainiert, mit dem ich Fehlerquellen wie die Putzmaschine ausschließen wollte. Hierbei habe ich einen neuen Ansatz mit zusätzlichen und umfangreicheren Daten ausprobiert. Am Ende stellte sich dieser Ansatz jedoch als kontraproduktiv heraus, da das neue Modell mehr Fehlkennungen erzeugt.
Google Spreadsheet mit Python schreiben lassen
Ich habe meinen Python-Code, der die CSV-Datei schreibt, so abgeändert, dass die Daten nun direkt in Google Spreadsheets geschrieben werden.
Jetzt werden die erkannten Fische direkt in das Spreadsheet übertragen, und diese Daten sind von meiner App sofort abrufbar.
Python Code: https://tundra-scarecrow-a32.notion.site/Python-CODE-15d0c7aba0d380148798f4d6648afcc7