Zum Inhalt

Bilderkennung in Betrieb nehmen (Inferenz-Service)

Diese Seite beschreibt die Inbetriebnahme des inference-service für die selbst gehostete Pflanzen-Bilderkennung (interne Anforderungsreferenz REQ-029-A). Der Inferenz-Service ist eine optionale Komponente — Kamerplanter funktioniert vollständig ohne sie; die Bilderkennung ist dann nicht verfügbar.


Überblick: Was wird installiert?

Der Inferenz-Service (src/inference-service/) ist ein eigenständiger FastAPI-Microservice, der:

  • das DINOv2-Modell (ViT-S/14, Apache-2.0, ~21 M Parameter) als ONNX-Artefakt (Open Neural Network Exchange) lädt,
  • Bilder vorverarbeitet und in Embedding-Vektoren (384 Dimensionen) umrechnet,
  • diese Vektoren gegen einen Referenz-Index in pgvector abgleicht und die ähnlichsten Pflanzenarten zurückgibt.

Der Service ist ein optionales, eigenständiges Modul. Es gibt zwei Deployment-Topologien:

  • Entwicklung (Skaffold): Der Service läuft im eigenen Helm-Release kamerplanter-recognition (Skaffold-Profil recognition) und teilt sich den pgvector-Speicher mit dem Knowledge-Service (kamerplanter-ki-Release, Datenbank kamerplanter_vectors, eigene Tabelle species_embeddings). Die kamerplanter-ki-vectordb muss laufen, wenn das recognition-Profil startet.
  • Produktion (ArgoCD, Single-Release): Der Inference-Service und eine dedizierte pgvector-Instanz laufen im selben Helm-Release wie Backend und Frontend (kamerplanter). Kein separates Release, kein geteilter pgvector-Speicher mit dem Knowledge-Service. Verfügbar ab Image-Tag v0.0.17 (ab diesem Release publiziert CI das kamerplanter-inference-service-Image auf GHCR).

Der Service ist in beiden Topologien nur clusterintern erreichbar (Kubernetes ClusterIP).


Aktivierungsreihenfolge

Reihenfolge einhalten

Die drei Schritte müssen in der angegebenen Reihenfolge ausgeführt werden. Wird INFERENCE_SERVICE_ENABLED=true gesetzt, bevor der Referenz-Index befüllt ist, ist die lokale Erkennung nicht verfügbar — das Backend fällt dann direkt auf Pl@ntNet zurück (sofern konfiguriert).

Schritt 1: Inferenz-Service starten — Entwicklung (Skaffold)

# Im Projektverzeichnis (Entwicklung):
# Das recognition-Profil teilt sich die pgvector-DB mit dem ki-Stack,
# daher beide Module zusammen starten:
skaffold dev -m ki -m recognition

Das recognition-Profil startet ausschließlich den inference-service (eigenes Release kamerplanter-recognition); das ki-Profil stellt die gemeinsam genutzte pgvector-DB bereit. Beim ersten Start wird das ONNX-Modell im Build-Schritt exportiert — das dauert je nach Hardware 5–15 Minuten.

Modell-Export wird gecacht

Nach dem ersten Build liegt das Modell im Layer-Cache. Folgestarts sind in wenigen Sekunden abgeschlossen.

Prüfen, ob der Service läuft:

# Port-Forward (lokale Entwicklung):
kubectl port-forward svc/kamerplanter-recognition 8090:8000 -n default

# Readiness prüfen (ist das Modell geladen?):
curl http://localhost:8090/ready
# Erwartete Antwort: {"status": "ok"}

# Modellinformationen abrufen:
curl http://localhost:8090/modelinfo
# Antwort enthält: model, dim, input_size, license, checksum

Schritt 2: Referenz-Index befüllen

Der Referenz-Index enthält Embedding-Vektoren für alle Pflanzenarten aus den Stammdaten. Er wird durch einen Celery-Task befüllt, der Referenzbilder von GBIF (Global Biodiversity Information Facility) und Wikimedia Commons abruft (nur CC0/CC-BY-Lizenzen), einbettet und die Vektoren in pgvector speichert. Originalbilder werden dabei nicht persistiert.

Dieser Schritt befüllt auch die UI-Bilder in der Artenansicht

Nach Abschluss dieses Tasks erscheinen in der Artenliste Thumbnails und auf der Artdetailseite eine vollständige Referenzbild-Galerie. Beide Ansichten zeigen vor dem ersten Beschaffungslauf einen Platzhalter-Hinweis. Lizenz-Attribution (CC-BY) wird automatisch in den Metadaten mitgeführt und im UI angezeigt. Weitere Informationen: Referenzbilder in der Artenansicht.

# Celery-Task für alle Arten starten (einmalig, dauert je nach Artenzahl mehrere Stunden):
kubectl exec -it deploy/kamerplanter-backend -n default -- \
  celery -A app.tasks call \
  app.tasks.reference_image_tasks.acquire_all_reference_images_task

# Alternativ: über die Backend-API (Admin-Endpoint):
curl -X POST http://localhost:8000/api/v1/admin/reference-images/acquire \
  -H "Authorization: Bearer <admin-token>"

Fortschritt verfolgen:

# Abdeckungs-Report abfragen (wie viele Arten sind erkennbar?):
curl http://localhost:8000/api/v1/admin/reference-images/coverage \
  -H "Authorization: Bearer <admin-token>"

Die Antwort zeigt pro Art, wie viele Referenzbilder akzeptiert wurden und ob die Art als "erkennbar" gilt (usable_for_recognition, mindestens 5 akzeptierte Referenzen):

{
  "total_species": 66,
  "usable_species": 48,
  "entries": [
    {
      "species_key": "species_alocasia_zebrina",
      "scientific_name": "Alocasia zebrina",
      "accepted": 2,
      "candidates_found": 7,
      "usable_for_recognition": false,
      "license_breakdown": {"CC0": 1, "CC_BY": 1}
    }
  ]
}

Abdeckungslücken

Arten mit weniger als 5 akzeptierten Referenzbildern erscheinen nicht in Erkennungsergebnissen. Das System kommuniziert dies ehrlich im UI. Häufige Ursachen für Lücken: seltene Arten, exotische Zimmerpflanzen oder Arten ohne CC0/CC-BY-Fotos in GBIF.

Admin-API-Endpunkte für die Referenzbild-Beschaffung (Übersicht):

Methode Endpunkt Beschreibung
POST /api/v1/admin/reference-images/acquire Beschaffungslauf für alle Arten starten
POST /api/v1/admin/reference-images/acquire/{species_key} Beschaffungslauf für eine einzelne Art starten oder wiederholen
GET /api/v1/admin/reference-images/coverage Abdeckungs-Report: erkennbare Arten, Arten unter Schwellenwert

Alle drei Endpunkte erfordern einen gültigen Admin-Token (Authorization: Bearer <admin-token>). Die Endpunkte sind über den regulären Backend-Ingress erreichbar — es ist kein separater Port-Forward nötig.

Schritt 3: Lokalen Pfad aktivieren

Setze die Umgebungsvariable im Backend:

# Backend-Env (values-dev.yaml) oder Umgebungsvariable:
INFERENCE_SERVICE_ENABLED=true

Nur aktivieren wenn Index befüllt ist

Wenn INFERENCE_SERVICE_ENABLED=true gesetzt ist und der Referenz-Index leer ist, fällt das System auf Pl@ntNet zurück — aber nur, wenn ein Pl@ntNet-Key konfiguriert und die Einwilligung erteilt ist. Ist beides nicht der Fall, liefert die Erkennung keine Ergebnisse.


Helm-Konfiguration

Entwicklung (Skaffold)

Im Entwicklungs-Workflow wird der Inferenz-Service über das Skaffold-Profil recognition als separates Helm-Release kamerplanter-recognition gestartet. Konfiguration in helm/kamerplanter/values-dev-recognition.yaml — der pgvector-Speicher wird mit dem kamerplanter-ki-Release geteilt (siehe Überblick oben).

Produktion (ArgoCD — Single-Release)

Mindest-Image-Tag: v0.0.17

Das CI-Image ghcr.io/nolte/kamerplanter-inference-service wird erst ab Release v0.0.17 publiziert (ab diesem Release enthält der Workflow den build-inference-service-Job). ArgoCD-Applications müssen auf v0.0.17 oder höher pinnen — ältere Tags enthalten das Image nicht.

In Produktion laufen der Inferenz-Service und eine dedizierte pgvector-Instanz (vectordb) im selben Helm-Release wie Backend und Frontend (Release-Name kamerplanter). Das Chart helm/kamerplanter/values.yaml enthält dafür zwei zusätzliche Controller, die standardmäßig deaktiviert sind: vectordb und inference-service. Der Operator aktiviert sie über valuesObject in der ArgoCD-Application.

Ein Secret-Schlüssel: POSTGRES_PASSWORD

Der Operator setzt vor dem ersten Deploy genau einen Schlüssel in kamerplanter-secrets: POSTGRES_PASSWORD. Den verlangt der vectordb-Container (PostgreSQL 18) zwingend unter diesem Namen; der inference-service leitet sein eigenes VECTORDB_PASSWORD per secretKeyRef aus demselben Schlüssel ab (das Chart verdrahtet das). Kein Passwort landet im Chart oder in Git, und beide Container ziehen gezielt nur diesen einen Schlüssel — nicht das ganze Secret per envFrom.

Schlüssel generieren und setzen (bestehende Secret-Schlüssel bleiben erhalten):

PW=$(openssl rand -base64 24)
kubectl patch secret kamerplanter-secrets -n kamerplanter --type merge \
  -p "{\"stringData\":{\"POSTGRES_PASSWORD\":\"$PW\"}}"

Falls kamerplanter-secrets noch nicht existiert:

kubectl create secret generic kamerplanter-secrets -n kamerplanter \
  --from-literal=POSTGRES_PASSWORD="$(openssl rand -base64 24)"

Wird kamerplanter-secrets über ESO / Sealed Secrets / Vault gespeist, trage POSTGRES_PASSWORD stattdessen an der Quelle ein.

ArgoCD Application — spec.sources[].helm.valuesObject:

controllers:
  vectordb:
    enabled: true
  inference-service:
    enabled: true
  backend:
    containers:
      main:
        env:
          INFERENCE_SERVICE_ENABLED: "true"
          INFERENCE_SERVICE_URL: "http://kamerplanter-inference-service:8000"
service:
  vectordb:
    enabled: true
  inference-service:
    enabled: true
persistence:
  inference-service-tmp:
    enabled: true
networkpolicies:
  vectordb:
    enabled: true
  inference-service:
    enabled: true

Ressourcen und Sicherheitskontext (Chart-Defaults, nicht überschreiben sofern nicht nötig):

Komponente CPU request/limit RAM request/limit Besonderheit
vectordb 50m / 500m 128Mi / 512Mi StatefulSet, 5Gi PVC (/var/lib/postgresql/data, PGDATA = .../pgdata), uid/gid/fsGroup 999 (postgres), helm.sh/resource-policy: keep
inference-service 250m / 2 512Mi / 2Gi readOnlyRootFilesystem: true, memory-backed /tmp emptyDir

Service-Hostnamen im Cluster (Release-Name kamerplanter):

  • Backend → Inferenz-Service: http://kamerplanter-inference-service:8000
  • Inferenz-Service → pgvector: kamerplanter-vectordb:5432 (Chart-Default für VECTORDB_HOST)

Aktivierungsreihenfolge in Produktion

Reihenfolge einhalten (Produktion)

Den Stack (vectordb + inference-service) deployen und den Referenz-Index befüllen, bevor INFERENCE_SERVICE_ENABLED=true gesetzt wird. Andernfalls findet die Erkennung keinen Referenz-Index und gibt keine Ergebnisse zurück.

  1. ArgoCD-Application mit den oben gezeigten valuesObject-Feldern (ohne INFERENCE_SERVICE_ENABLED: "true") deployen. Warten, bis kamerplanter-vectordb und kamerplanter-inference-service Ready sind.
  2. Referenz-Index befüllen (identisch mit Schritt 2 im Entwicklungs-Pfad oben — das kubectl exec-Ziel ist deploy/kamerplanter-backend).
  3. INFERENCE_SERVICE_ENABLED: "true" in das valuesObject eintragen und die Application synchronisieren.

Ressourcenbedarf

Szenario RAM CPU Latenz/Anfrage
DINOv2 ViT-S/14 512 MB – 1 GB 0,5–2 Kerne 500ms–2s (CPU)

Raspberry Pi / ARM

DINOv2 ViT-S/14 läuft auf ARM64 (Raspberry Pi ⅘, Apple Silicon). Die Latenz ist höher (~3–8s), aber für Batch-Indexierung und interaktive Erkennung ausreichend.


Umgebungsvariablen

Variable Pflicht Standard Beschreibung
VECTORDB_HOST Nein localhost Hostname der pgvector-Datenbank. Im Cluster abhängig von der Topologie: DEV/Separate-Release → kamerplanter-ki-vectordb (geteilter KI-Stack); Produktion/Single-Release → kamerplanter-vectordb (dediziert, Chart-Default)
VECTORDB_PORT Nein 5432 Port der pgvector-Datenbank
VECTORDB_DATABASE Nein kamerplanter_vectors Datenbankname
VECTORDB_USERNAME Nein postgres Datenbankbenutzer
VECTORDB_PASSWORD Nein changeme Datenbankpasswort. In Produktion (Single-Release) per secretKeyRef aus dem einen Secret-Schlüssel POSTGRES_PASSWORD abgeleitet — nicht separat setzen
MODEL_NAME Nein dinov2_vits14 ONNX-Modellname
MODEL_PATH Nein /app/models/dinov2 Verzeichnis mit dem ONNX-Modellartefakt (model.onnx)
CONFIDENCE_AUTO_ACCEPT Nein 0.85 Konfidenz-Schwelle für direkte Übernahme
CONFIDENCE_SHOW_RESULTS Nein 0.10 Minimale Konfidenz für Anzeige in der Liste
Variable (Backend) Pflicht Standard Beschreibung
INFERENCE_SERVICE_ENABLED Nein false Lokalen Inferenz-Pfad aktivieren
INFERENCE_SERVICE_URL Nein http://kamerplanter-recognition:8000 Interne URL des Inferenz-Service
PLANTNET_API_KEY Nein Pl@ntNet API-Key für Fallback (optional)

Endpunkte des Inferenz-Service (intern)

Die Endpunkte sind nur clusterintern erreichbar und nicht über den Ingress exponiert.

Methode Pfad Beschreibung
POST /embed Einzelbild → Embedding-Vektor
POST /embed/batch Mehrere Bilder → Embedding-Vektoren (Beschaffung)
POST /match Bild → Top-k ähnlichste Arten mit Konfidenz (Query-Param k, Standard 5, max. 50)
POST /reference Embedding + Provenienz in pgvector speichern
GET /reference/{species_key} Indexierte Referenzen einer Art abrufen
DELETE /reference/{species_key} Referenzen einer Art löschen (Re-Index)
GET /health Liveness-Probe
GET /ready Readiness-Probe (Modell geladen?)
GET /modelinfo Modellname, Dimension, Eingabegröße, Lizenz, Prüfsumme

Lizenzen und Rechtliches

Komponente Lizenz Hinweis
DINOv2 Basis-Backbone (Meta) Apache-2.0 Vor Produktivnahme LICENSE im offiziellen Repo verifizieren
ONNX Runtime (Microsoft) MIT
Referenzbilder (GBIF) CC0 / CC-BY Nur diese Lizenzen werden indexiert
PlantCLEF Fine-tune-Gewichte CC-BY-NC Werden nicht verwendet (nicht-kommerzielle Einschränkung)
Pl@ntNet API (Fallback) ToS, frei ≤500/Tag Nur mit Nutzer-Consent, nur als Fallback

PlantCLEF-Gewichte nicht verwenden

Die auf dem PlantCLEF-2024-Datensatz fein abgestimmten DINOv2-Gewichte stehen unter CC-BY-NC-Lizenz (nicht-kommerziell). Diese Gewichte werden nicht verwendet. Kamerplanter nutzt ausschließlich das Apache-2.0-lizenzierte Basis-Backbone von facebookresearch/dinov2.


Fehlerbehebung

Der Inferenz-Service startet nicht — Fehler: Modell nicht gefunden

Das ONNX-Artefakt wurde möglicherweise nicht exportiert. Prüfe den Build-Log des inference-service-Images auf den Schritt export_dinov2_onnx.py. Führe skaffold build -m recognition erneut aus.

Erkennung liefert immer 'keine Ergebnisse' obwohl der Service läuft

Prüfe, ob der Referenz-Index befüllt ist (/api/v1/admin/reference-images/coverage). Ein leerer Index liefert keine Treffer. Führe acquire_all_reference_images_task aus (Schritt 2).

Die Celery-Task läuft sehr lange — ist das normal?

Ja. Der GBIF-Abruf für alle angelegten Arten — mit je bis zu 40 Bild-Kandidaten, anschließender Embedding-Berechnung und Indexierung — kann je nach Artenzahl, Hardware und Netzwerkgeschwindigkeit mehrere Stunden dauern. Der Task ist idempotent — bei Abbruch lässt er sich erneut starten.

Wie aktualisiere ich den Referenz-Index für eine einzelne Art?

Nutze den Admin-Endpoint POST /api/v1/admin/reference-images/acquire/{species_key} — er löst intern den Celery-Task acquire_reference_images_task für die Art aus.


Siehe auch