# Bildedrevet tidtakingssystem — teknisk spesifikasjon ## Oversikt Et tidtakingssystem for løp uten elektroniske brikker. Utøvere identifiseres ved hjelp av bilder tatt ved start, passeringspunkter og mål. Bildene inneholder nøyaktig tidsstempel og GPS-posisjon i EXIF-metadata, noe som gjør det mulig å rekonstruere hver utøvers rute og passeringstidspunkter langs løypa. Identifikasjon skjer ved kombinasjon av to metoder: 1. OCR på startnummer 2. Biometrisk persongjenkjenning (ansikt/kroppsform) De to metodene forsterker hverandre — et delvis skjult startnummer kan disambigueres av persongjenkjenning, og omvendt. --- ## Kamerastasjoner Kamera settes opp ved: - **Start** — alle utøvere fotograferes ved avgang - **Passeringspunkter** — vilkårlig antall stasjoner langs løypa (N ≥ 0) - **Mål** — alle utøvere fotograferes ved ankomst Hvert kamera må: - Produsere JPEG/PNG med korrekt EXIF-data (se nedenfor) - Ha burst-modus eller høy framerate for å håndtere bevegelsesuskarphet - Synkroniseres mot NTP (nøyaktig klokkeslett er kritisk) ### EXIF-krav per bilde Alle bilder MÅ ha følgende EXIF-felter satt: | EXIF-felt | Innhold | Merknad | |---|---|---| | `DateTimeOriginal` | ISO 8601 UTC | Eks: `2024-06-15T10:23:45.123Z` | | `SubSecTimeOriginal` | Millisekunder | For sub-sekund presisjon | | `GPSLatitude` + `GPSLatitudeRef` | Desimalgrader | Stasjonens posisjon | | `GPSLongitude` + `GPSLongitudeRef` | Desimalgrader | Stasjonens posisjon | | `GPSAltitude` | Meter over havet | Valgfritt men anbefalt | | `CameraLabel` / `ImageDescription` | Stasjonsnavn | Eks: `"start"`, `"cp1"`, `"finish"` | EXIF-posisjonen representerer stasjonens faste posisjon (ikke utøverens bevegelse). For mobile stasjoner settes GPS per bilde fra kamerakontrollerens GPS-mottaker. --- ## Dataflyt og pipeline ``` Bilde (JPEG + EXIF) │ ▼ [1] Inntak og preprocessing - Valider EXIF (tid + GPS påkrevd, avvis ellers) - Kø-basert mottak fra alle stasjoner - Generer utsnitt av utøver (bounding box) - Kvalitetsfiltrering: skarphet, lysforhold, utøver synlig │ ├──────────────────────────────────┐ ▼ ▼ [2a] Startnummer-OCR [2b] Persongjenkjenning - Detekter nummerlapp - Generer embedding fra - Les sifre ansikt og/eller kropp - Returner: sifre[], konfidens, - Match mot profil-DB andel synlig (0.0–1.0) - Returner: profil_id, konfidens, match-type │ │ └──────────────┬───────────────────┘ ▼ [3] Konfidensbasert fusjonsmotor - Kombiner OCR-resultat og personmatch - Beslutningstre (se nedenfor) - Output: identifikasjon + konfidensscore │ ├── Høy konfidens ──► [4a] Logg passering direkte └── Lav konfidens ──► [4b] Flagg for manuell gjennomgang │ ▼ [5] Resultatdatabase - Utøver-ID, stasjon, tidsstempel (fra EXIF), GPS-pos - Beregn split-tider og løyperute ``` --- ## Fusjonslogikk Prioriteringsrekkefølge ved konflikt: ``` 1. Startnummer tydelig (konfidens > 0.90) → Bruk startnummer, ignorer person-match hvis konflikt 2. Startnummer delvis synlig (konfidens 0.50–0.90) + person-match → Kombiner: filtrer kandidater fra startnummer, disambiguer med person → Logg hvis entydig, flagg hvis flere kandidater gjenstår 3. Startnummer uleselig (konfidens < 0.50) + person-match (konfidens > 0.70) → Bruk person-match alene → Logg med merknad "number_unreadable" 4. Ingen av delene gir tilstrekkelig konfidens → Flagg for manuell gjennomgang med bilde og metadata ``` Minste akseptable konfidens for automatisk logging: konfigurbart, anbefalt startverdi `0.75`. --- ## Profildatabase (utøver-DB) Utøverprofiler bygges opp suksessivt: - **Bootstrapping:** Startkameraet genererer en profil for hver unik person som passerer. Profil-ID tildeles automatisk. - **Kobling til startliste:** Valgfritt — kan mates inn med navn + startnummer + registreringsbilde. Kobles til profil ved første gjenkjenning. - **Uten startliste:** Systemet fungerer fullt ut; utøvere får anonyme ID-er. Startliste kan legges til i etterkant for å sette navn. ### Skjema (forenklet) ```json { "profile_id": "uuid", "bib_number": "42", "name": "Ola Nordmann", "embeddings": [ /* array av vektorer fra registreringsbilder */ ], "registration_images": [ "path/to/img1.jpg" ], "created_at": "2024-06-15T09:00:00Z" } ``` --- ## Passeringslogg En passering lagres når en utøver identifiseres ved en stasjon: ```json { "passage_id": "uuid", "profile_id": "uuid", "bib_number": "42", "station": "cp1", "timestamp_utc": "2024-06-15T10:23:45.123Z", "gps_lat": 61.1234, "gps_lon": 10.5678, "gps_alt": 910, "confidence": 0.92, "id_method": "bib_ocr+person", "source_image": "path/to/image.jpg", "needs_review": false } ``` Fra passeringsloggen beregnes: - Split-tider mellom stasjoner - Totaltid start→mål - Løyperute (GPS-spor av stasjonspasseringer) - Estimert posisjon i løypa på ethvert tidspunkt (interpolasjon) --- ## Komponenter som skal implementeres ### Backend (Python anbefalt) | Modul | Ansvar | |---|---| | `ingest.py` | Ta imot bilder, valider EXIF, kø til pipeline | | `exif_parser.py` | Parse og normaliser EXIF-metadata | | `ocr.py` | Startnummer-deteksjon og OCR | | `recognition.py` | Persongjenkjenning, embedding, match mot profil-DB | | `fusion.py` | Kombiner OCR + person, beslutningslogikk | | `profile_db.py` | CRUD for utøverprofiler og embeddings | | `passage_log.py` | Skriv og query passeringslogg | | `results.py` | Beregn split-tider, totalresultat, løyperute | | `review_queue.py` | Håndter manuelle gjennomganger | ### Foreslåtte biblioteker - **OCR:** `easyocr` eller `paddleocr` (god på korte numre i varierende lysforhold) - **Persongjenkjenning:** `deepface` eller `insightface` (lokalt, ingen sky-avhengighet) - **EXIF-parsing:** `piexif` eller `exifread` - **Database:** SQLite for enkel lokal drift, PostgreSQL for produksjon - **Bildeprosessering:** `opencv-python`, `Pillow` - **Kø:** `redis` + `rq` for asynkron pipeline, eller enkel filkø for MVP ### Frontend (valgfritt, React anbefalt) - Live-visning av passeringer - Leaflet-kart med GPS-posisjoner til stasjoner og utøveres rute - Resultatliste med split-tider - Manuell gjennomgangskø med bildevisning --- ## Kanttilfeller og håndtering | Situasjon | Håndtering | |---|---| | Utøvere overlapper i bildet | Generer separate bounding boxes per utøver | | Startnummeret er delvis bak arm/klesplagg | OCR returnerer synlige sifre + `partial: true` | | Samme utøver fanges i burst-sekvens | Deduplisering: behold beste bilde per utøver per stasjon (±2 sek vindu) | | Refleks/solblending gjør bildet ubrukelig | Kvalitetsfilter forkaster bildet; neste i burst brukes | | EXIF mangler tid eller GPS | Bildet avvises med feillogg; manuell stasjon-tid kan angis som fallback | | Ukjent person, uleselig nummer | Flagges med bilde til manuell gjennomgang | | To utøvere med like startnummer (feil) | Flagges som konflikt; begge profiler merkes | --- ## Personvern - Biometrisk gjenkjenning krever samtykke (GDPR artikkel 9 — særlig kategori) - Anbefalt løsning: opt-in ved påmelding, registreringsbilde lastes opp i forkant - Embeddings lagres, ikke råbilder, etter at passering er bekreftet (konfigurerbart) - Råbilder bør slettes etter løpet med mindre deltaker har samtykket til lagring - Systemet bør kunne kjøres i "bib-only"-modus uten persongjenkjenning for arrangement som ikke innhenter biometrisk samtykke --- ## Infrastruktur ### Container-drift Hele systemet kjøres i Docker (docker-compose): - **backend** — Python/FastAPI, pipeline og API - **frontend** — React admin-grensesnitt (Nginx) - **db** — SQLite via volum (PostgreSQL i produksjon) Volumes: - `./depot` — inntakskatalog for nye bilder (kamera-drop) - `./processed` — bearbeidede bilder med unike filnavn - `./data` — SQLite-database ### Bildehåndtering 1. Kamera/bruker legger bilder i `depot/` 2. `ingest.py` oppdager nye filer (inotify / polling) 3. EXIF valideres; ved godkjenning: - Metadata lagres i DB - Filen flyttes til `processed/<år>//_` - Depot-filen slettes 4. Ved ugyldig EXIF: filen flyttes til `depot/rejected/` med feillogg --- ## Admin-webgrensesnitt (React) Tilgjengelig via nettleser, kommuniserer med backend via REST API. | Skjerm | Funksjonalitet | |---|---| | Startliste | Last opp CSV, vis/rediger utøverliste | | Live passeringer | Strøm av nylige passeringer fra alle stasjoner | | Manuell gjennomgang | Vis bilder med lav konfidens, bekreft/korriger ID | | Resultater | Split-tider og totalresultat per utøver | | Stasjoner | Oversikt over kamerastasjoner og siste aktivitet | --- ## MVP-avgrensning (fase 1) For å komme raskt i gang, begrens til: 1. EXIF-parsing og validering 2. Startnummer-OCR (ingen persongjenkjenning ennå) 3. Manuell startliste som CSV (bib_number → navn) 4. Passeringslogg til SQLite 5. REST API (FastAPI) for frontend 6. React admin-grensesnitt: startliste-import og manuell gjennomgang 7. Docker-oppsett med depot/processed-volumer Persongjenkjenning og live Leaflet-kart legges til i fase 2.