Hackathon submission

Genz — traffic-camera event detection & accident anticipation

A two-part solution for the fixed-road-camera challenge: Part A detects and timestamps traffic events (accidents, near-misses, congestion, jaywalking, and more) in a finished video, and Part B watches a video frame-by-frame and outputs a causal, real-time accident-risk score that never looks at the future. Everything ships as a zero-training, zero-GPU, rule-based pipeline that runs on CPU out of the box, with an optional learned-detector upgrade path already wired in.

Rule-based baseline, no training data Runs on CPU Optional YOLO upgrade path 14 challenge classes, 8 implemented automatically
Who built this

Team

Team member details below are placeholders — real names, roles, and profile links go here before submission.

M1
TODO

[Member 1 Name]

[Role, e.g. Detection & tracking]
[What they built: e.g. src/detector.py, src/tracker.py]
[GitHub link] [LinkedIn link] [Portfolio link]
M2
TODO

[Member 2 Name]

[Role, e.g. Rules engine & road model]
[What they built: e.g. src/rules.py, src/road_model.py]
[GitHub link] [LinkedIn link] [Portfolio link]
M3
TODO

[Member 3 Name]

[Role, e.g. Risk engine & website/demo]
[What they built: e.g. src/risk.py, website/]
[GitHub link] [LinkedIn link] [Portfolio link]
What we're solving

Problem & approach

The challenge has two parts, both over the same fixed, non-moving road-camera footage:

Part A -- event detection. Given a finished video, return every interesting event as [start_sec, end_sec, label], where the label is one of 14 fixed classes (accidents, near-misses, wrong-way driving, jaywalking, congestion, and so on). This is scored after the fact, so the whole video is available at once.

Part B -- accident anticipation. Given the same video played frame by frame, output a risk score from 0 to 1 at every frame, before anything bad has necessarily happened -- and it must be causal: at frame t the model may only use frames up to and including t, never later ones. The scoring rewards catching danger a few seconds early.

Part A pipeline (offline, sees the whole video)
Video.mp4 file
→
MotionDetectorMOG2 background subtraction (default)
or optional YOLO
→
IoU Trackergreedy per-frame box matching
→
Road Modelconvex-hull road mask +
dominant flow direction
→
Rules Engineper-track & pairwise heuristics
→
Events list[start, end, label]
Part B pipeline (causal, one frame at a time)
Video framesdelivered one at a time,
in order
→
CausalRiskEngineits own detector + tracker,
rebuilt frame by frame
→
TTC / braking /
pedestrian signalscombined with max(), then
smoothed with EMA
→
Risk score0.0–1.0 per frame
causal — never sees future frames
What's learned vs. what's rules: by default, 100% of this pipeline is hand-written rules on top of classical computer vision (OpenCV MOG2 background subtraction + contour geometry + an IoU tracker). There is no trained model and no learned weights anywhere in the default path. src/detector.py also defines an optional YoloDetector that swaps in automatically if the ultralytics package and a weights/yolov8n.pt file are both present -- a learned-detector upgrade path that's wired but not required to run.

8 classes detected automatically

Each has a concrete, per-camera-agnostic signal derived from track geometry.

  • stopped_vehicle — stationary ≥10s
  • congestion — ≥3 vehicle tracks mostly stationary across the road for ≥8s
  • wrong_way — heading ≥135° opposed to dominant flow
  • near_miss — close approach + high closing speed, then divergence, no overlap
  • accident — bounding-box overlap between two tracks
  • road_obstacle — static "unknown"-class blob on the road for ≥10s
  • jaywalking — person-classified blob inside the road-mask polygon
  • failure_to_yield — vehicle–person bounding-box overlap

6 classes not yet implemented

Documented extension points — each needs per-camera calibration data (a calibration.json) we don't have without real sample footage. They never appear in detect_events output today.

  • red_light — needs a calibrated signal-state ROI
  • stop_line — needs a calibrated stop-line geometry
  • illegal_u_turn — needs no-U-turn zone geometry
  • illegal_turn — needs lane/turn geometry
  • solid_line_crossing — needs lane-marking geometry
  • fire_smoke — a MOG2 motion blob carries no reliable color/texture cue for smoke
Exploratory data analysis

EDA of sample videos

No camera sample footage has been provided to this team yet. This dashboard will populate automatically once videos are placed in samples/ and an EDA script (src/eda.py, to be added) is run over them. Everything below is a structural placeholder, not real data.

Resolution / FPS / duration per sample

VideoResolutionFPSDuration
————
————
————
No data yet

Object count over time

No data yet

Motion heatmap

No data yet

Traffic density (vehicles / lane)

No data yet
Model output

Results on sample videos

No sample results yet either. This section will show annotated playback, a per-video event timeline, and a per-video risk curve once real files exist under samples/*.mp4 and have been run through run_submission.py. The block below is a synthetic mockup of the intended layout only.
Illustrative mockup — not real output

sample_01.mp4 — event timeline + risk curve (synthetic example)

risk 1.0 risk 0.0
stopped_vehicle congestion accident near_miss — risk score (Part B), synthetic
Try it yourself

Live demo

Runs the real solution.detect_events() from this repo's solution.py against a video you upload, locally, on your own machine. This is a genuine model run, not a canned animation.

Limits: .mp4 only, up to 2 minutes (120s) and 200MB. Longer or larger files are rejected with an error rather than silently truncated. This demo currently shows Part A events only — risk-curve visualization: coming soon.

Pick an .mp4 file and click run.
ClassStartEndDuration
This page needs to be served by website/demo_server.py (a small Flask app) — see website/README.md for the two commands to run it. Opening this file directly from disk will render the page fine, but the "Run" button will fail to reach a server, since there isn't one. This demo runs on your own machine; it is not hosted anywhere publicly (see Links below).
Honest self-assessment

Report

What we built

  • A zero-dependency motion detector (OpenCV MOG2 + contour classification) with an optional YOLOv8 swap-in when weights are present.
  • A minimal greedy IoU multi-object tracker with full per-track position history.
  • A self-calibrating road model (convex hull of observed vehicle positions + circular-mean dominant flow direction), usable both offline and causally.
  • A rules engine that turns finished tracks into 8 of the 14 required event classes.
  • A causal risk engine (Part B) combining time-to-collision, hard-braking, and pedestrian-proximity signals with max() + EMA smoothing, calibrated so a 5s time-to-collision maps to a 0.5 score.
  • This website, including a working local live-demo server.

What worked

  • The zero-dependency motion detector runs entirely on CPU with no training data and no GPU.
  • The causal risk engine genuinely respects the no-future-frames rule: RiskEstimator.step() only ever sees the current frame and its own running state.
  • run_submission.py / evaluate.py match the challenge's own harness and metric spec (macro F1 across IoU 0.3/0.5/0.7 for Part A; AP + alarm-F1 + mean-time-to-accident for Part B; M = 0.7*ScoreA + 0.3*ScoreB) exactly, so scoring behaves as the organizers intend.

What didn't / limitations

  • No labeled data exists yet, so every threshold (stationary speed, wrong-way angle, near-miss closing speed, etc.) is hand-picked from first principles, not tuned against real ground truth.
  • 6 of 14 classes (red_light, stop_line, illegal_u_turn, illegal_turn, solid_line_crossing, fire_smoke) need per-camera calibration data we don't have and are left as documented extension points rather than guessed.
  • Classifying "vehicle" vs. "person" from blob size/aspect ratio alone is weak and will misclassify unusual shapes; a real detector would help a lot — the YOLO hook is already wired for exactly this upgrade.
  • accident / near_miss detection relies on 2D bounding-box overlap with no depth cue, so a vehicle that merely passes close to the camera's line of sight can look like an overlap and false-positive.

Next steps

  • Get real sample videos into samples/ and generate real EDA and results.
  • Hand-annotate a small dev set and tune thresholds against evaluate.py instead of by hand.
  • Drop in real YOLO weights (weights/yolov8n.pt, see weights/download.sh) for cleaner class labels.
  • Calibrate stop-line / signal-state ROIs / lane geometry per camera to unlock the remaining 6 classes.
  • Publicly host this demo (see Links) once the team has an account ready.