- 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>
9.4 KiB
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:
- OCR på startnummer
- 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)
{
"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:
easyocrellerpaddleocr(god på korte numre i varierende lysforhold) - Persongjenkjenning:
deepfaceellerinsightface(lokalt, ingen sky-avhengighet) - EXIF-parsing:
piexifellerexifread - Database: SQLite for enkel lokal drift, PostgreSQL for produksjon
- Bildeprosessering:
opencv-python,Pillow - Kø:
redis+rqfor 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
- Kamera/bruker legger bilder i
depot/ ingest.pyoppdager nye filer (inotify / polling)- EXIF valideres; ved godkjenning:
- Metadata lagres i DB
- Filen flyttes til
processed/<år>/<måned>/<uuid>_<originalfilnavn> - Depot-filen slettes
- 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:
- EXIF-parsing og validering
- Startnummer-OCR (ingen persongjenkjenning ennå)
- Manuell startliste som CSV (bib_number → navn)
- Passeringslogg til SQLite
- REST API (FastAPI) for frontend
- React admin-grensesnitt: startliste-import og manuell gjennomgang
- Docker-oppsett med depot/processed-volumer
Persongjenkjenning og live Leaflet-kart legges til i fase 2.