Files
steinhelge 330ba7a93d Initial commit: MVP tidtakingssystem
- Backend: FastAPI, EXIF-parser, EasyOCR, SQLite
- Frontend: React admin (startliste, passeringer, gjennomgang, resultater)
- Docker: docker-compose med depot/processed/data-volumer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 15:01:33 +01:00

9.4 KiB
Raw Permalink Blame History

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.01.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.500.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)

{
  "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:

{
  "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>/<måned>/<uuid>_<originalfilnavn>
    • 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.