Approach

A detector, a tracker, one reference view and a rule per class

Nothing here is trained by us. The only learned parts are COCO-pretrained YOLO26 detectors (26m for Part A, 26s for Part B); everything after them is geometry and rules we wrote for this one junction. This page has enough detail to rebuild it.

Pipeline

Pipeline diagramPart A: 4K clip, sample every 3rd frame, YOLO26m detection, ByteTrack, registration to the reference view, trajectories, rules using the signal phase and the scene layout, then per-class segments. Part B: frames as they arrive, YOLO26s detection and tracking, constant-velocity look-ahead, risk per frame.PART AeventsPART Brisk, causal4K clipinput3840x2160, 29.97 fpsH.264 4:2:2 10-bitSampleruleevery 3rd frame, about 10 fpsresized to 1920x1080DetectlearnedYOLO26m, COCO weights1280 px input, conf 0.1TrackruleByteTrack, one tracker per group:vehicles, people, bikes, animalsRegistergeometrySIFT + MAGSAC homographyclip -> reference viewTrajectoriesgeometryfoot points in reference pxsmoothed speed and headingRulesruleper actor and interval8 classes emittedSegmentsoutputunion per class, no overlap[start, end, class]Signal phaserulelamp colour on the medianhead, every 0.1 sScene layoutgeometrystop line, crossings, road,bus stop; drawn onceFrames as they arriveinputcausal: past frames onlyevery 3rd, 1280 px wideDetect and tracklearnedYOLO26s at 960 px, conf 0.2ByteTrack, 1.5 s buffer (A: 2 s)Look aheadgeometryconstant velocity, 3 sfootprints in metresRisk per frameoutputworst pair hazard, EMAalarm threshold 0.5Pipeline diagramPART A eventsPART B risk, causal4K clipinput3840x2160, 29.97 fpsH.264 4:2:2 10-bitSampleruleevery 3rd frame, about 10 fpsresized to 1920x1080Signal phaserulelamp colour on the medianhead, every 0.1 sDetectlearnedYOLO26m, COCO weights1280 px input, conf 0.1TrackruleByteTrack, one tracker per group:vehicles, people, bikes, animalsRegistergeometrySIFT + MAGSAC homographyclip -> reference viewTrajectoriesgeometryfoot points in reference pxsmoothed speed and headingScene layoutgeometrystop line, crossings, road,bus stop; drawn onceRulesruleper actor and interval8 classes emittedSegmentsoutputunion per class, no overlap[start, end, class]Frames as they arriveinputcausal: past frames onlyevery 3rd, 1280 px wideDetect and tracklearnedYOLO26s at 960 px, conf 0.2ByteTrack, 1.5 s buffer (A: 2 s)Look aheadgeometryconstant velocity, 3 sfootprints in metresRisk per frameoutputworst pair hazard, EMAalarm threshold 0.5
learned (pretrained, not fine-tuned)hand-written rulegeometryinput or output

The scene

A camcorder on a tripod, high on a building at one corner of the junction, looking straight up the avenue. North, south, east and west are our names for the arms as the camera sees them, not compass bearings. We describe everything in one 1920x1080 reference view; the 4K clips are mapped onto it.

The junction from the camera, with the road mask in green, the crossings outlined in blue and the southbound stop line in red.
Reference view with our scene layout drawn on it: road mask (green), crossings (blue), southbound stop line (red).

Southbound (SB) traffic comes towards the camera on the left of the median, four lanes at the stop line. Northbound (NB) traffic drives away on the right, past a bus stop near the top of the frame.

The north crossing is a zebra across both carriageways just past the SB stop line, split by the median nose. The west crossing is a long diagonal zebra over the west arm at the bottom left, between three pink islands. Nothing else in view is a legal place to cross.

Things that are easy to get wrong here: cars parked along the left edge are off the carriageway; buses wait at the NB stop for 20 to 60 s; the SB queue at a red light stands still for 36 to 39 s; cyclists come out of the detector as a person plus a bike.

What is learned and what is a rule

PartStageKindWhat exactly
ADetectionLearnedYOLO26m with COCO weights, not fine-tuned. 1280 px input, confidence 0.1. Keeps person, bicycle, car, motorcycle, bus, truck and six animal classes.
ATrackingAlgorithmByteTrack (the Ultralytics implementation), one tracker per group so boxes of different kinds never get linked. High 0.3, low 0.1, new track 0.4, 2 s buffer.
ARegistrationGeometrySIFT, 6000 features, on 960 px wide copies; homography by MAGSAC with 3 px tolerance against a midday and a dusk reference, both per-pixel median backgrounds. Done again on keyframes (every 2 s for 30 s, then every 10 s): the lamps use the latest, the rules one registration of the keyframes' median.
ASignal phaseRuleLamp patches on the median-nose head, mapped from the reference view; the lamp with the strongest colour contrast relative to its threshold wins (20 on a 0 to 255 scale for red and yellow, 6 for green).
ARider or pedestrianRuleA person whose box is more than 30% covered by a bicycle or motorbike box in over 40% of its samples, or whose median speed is above 110 px/s or above 1.2 of its own height per second, is a rider.
AEventsRulesOne function per class on trajectories, zones and phase. Thresholds were tuned on our dev labels, by hand and with a small grid search for jaywalking and failure to yield.
BDetectionLearnedYOLO26s with COCO weights at 960 px, confidence 0.2, every 3rd frame (10 Hz).
BRiskHand-built modelConstant-velocity look-ahead of 3 s from 0.8 s of history; pair hazard from closing speed, time to contact and footprint overlap in metres (one scale per pair, taken between the two users), plus hard braking; a pair counts after 0.6 s of danger; the worst pair sets the score, EMA 0.35.

The Part B risk score

Every 0.1 s, for each pair of road users, with positions in metres (one scale per pair, taken between the two) and velocities fitted to the last 0.8 s of their tracks:

  1. Pair hazardh = 1 / (1 + exp(2.5 (t - 0.9))) × (0.4 + 0.6 d) × min(1, c / 10)if they close in at c ≥ 4 m/s and, moving in straight lines, their footprint circles overlap within 3 s; otherwise 0. t is the time in seconds until the circles first touch, d the overlap at the closest approach divided by the sum of the radii (at most 1). Radii: person 0.35 m, bicycle 0.6, motorbike 0.7, car 1.3, truck 1.8, bus 2.0; both shrink to 40% when one of the two moves under 1 m/s.
  2. Brakingrisk = min(1, h × (1 + 0.5 max(b1, b2))), b = clip((a - 4) / 6, 0, 1)a is a user's deceleration in m/s² over the same 0.8 s, counted only if it was faster than 3 m/s. A pair counts once it has looked dangerous for 0.6 s without a break.
  3. Scorescore = 0.65 × previous score + 0.35 × the highest pair risk in the framePairs left out: two pedestrians, a pedestrian off the carriageway, users on opposite sides of the median, a moving vehicle and a standing one, users more than 30 m apart.

Code: pair_hazard and Anticipator._hazard in src/parivision/risk.py.

The rule for each class

Distances are in pixels of the reference view; near the north crossing one metre left to right is about 50 px, and fewer along the avenue. Score A averages over classes, so a class we predict that never happens in the test set adds a zero. Five classes have no rule. We have a U-turn rule, but we keep it off for this reason: nothing in view says the U-turns here are illegal, so the organisers may never label one.

  1. ACC

    Accident

    emitted

    Two road users meet at speed, both velocities change at the contact, and both then stand together for 2 s. Checked on public CCTV crash clips; it never fires in our samples. Part B gives the per-frame probability that one starts within 5 s.

  2. NM

    Near miss

    no rule

    No rule yet. The Part B time-to-collision score is the closest signal we have, and we have not turned it into segments.

  3. RED

    Red light

    emitted

    A southbound vehicle's front crosses the stop line after the lamp has been red for at least 1 s, with at least 1.5 s of red still to go. Yellow does not count. The segment ends when its track ends, at most 60 s later.

  4. WW

    Wrong way

    emitted

    A vehicle faster than 40 px/s heads more than 120 degrees away from its carriageway's direction for at least 1.5 s, at least 6 px inside the carriageway (the service road along the far kerb does not count). Each carriageway has one legal direction in the reference view. On 1074 real tracks run backwards it finds 1057; it never fires on the samples.

  5. UT

    Illegal U-turn

    rule off

    We find U-turns (a southbound vehicle turns round the median nose, within 130 px of it, and leaves northbound) but do not emit the class, because we have not confirmed which U-turns are illegal at this junction.

  6. SV

    Stopped vehicle

    emitted

    A vehicle stays under 12 px/s for 10 s or more on the northbound carriageway or in the junction box on the southbound path to the south exit. The bus stop is excluded, and so are the southbound approach, where cars queue at the red, and the northbound lanes east of x = 1720 px.

  7. JAY

    Jaywalking

    emitted

    A person who is not riding a bike has their feet on the carriageway, clearly outside every zebra, for at least 1 s. Riders are found by a two-wheeler box over the person, or by a median speed above 110 px/s or above 1.2 of their own height per second (walkers stay under about 0.9).

  8. FTY

    Failure to yield

    emitted

    A car, bus or truck moving at 20 px/s or more drives across a zebra while a walking pedestrian is out on it, not at the kerb, within 160 px of the vehicle. A car that stood before the zebra and drives on once the pedestrian is past its path and walking away has let them through, and does not count. Motorbikes are skipped. The segment runs from the front entering the zebra to the rear leaving it.

  9. IT

    Illegal turn

    no rule

    No rule yet. The plan is to compare the lane a vehicle holds at the stop line with the arm it leaves by.

  10. SLC

    Solid line crossing

    no rule

    No rule yet. The solid lane lines before the southbound stop line would be drawn once in the reference view and a lane change across them flagged.

  11. STOP

    Stop line

    emitted

    A southbound vehicle stands for at least 1 s with its front more than 8 px past the stop line while the lamp is red. The segment ends at green, or earlier if its track ends.

  12. CONG

    Congestion

    emitted

    Southbound traffic stands still on green. From 8 s into the green, 8 or more vehicles under 15 px/s on roughly the last 40 m of the approach and past the stop line on the way to the south exit make a jam, and so do 5 or more past the stop line alone. A jam must start on green or yellow and last at least 6 s, and it carries on into the red while 5 or more still stand past the stop line.

  13. OBS

    Road obstacle

    no rule

    No rule yet. The detector already keeps six COCO animal classes, so an animal on the road could feed it.

  14. FIRE

    Fire or smoke

    no rule

    No rule yet, and nothing in the sample clips to test one on.

How we read the signal

The heads over the SB lanes face away from the camera. Two heads do face it. The one on the median nose is a three-lamp vehicle signal for the approach from the south, which runs in the same phase as SB; we read the phase from it, and it stays readable at dusk. The one on the pole at the left corner is a two-lamp pedestrian signal for the west crossing, and we do not use it.

For each of the three lamps we take a small patch around its centre, mapped from the reference view into the frame. A lit red lamp is much redder than its dark housing, a lit green one much greener, a lit yellow one much warmer. The lamp with the strongest contrast relative to its own threshold wins. Position decides the phase, not colour alone, so tail lights passing behind the head do not read as red.

When every lamp is dark the head is "off": the flashing part of green, or a car hiding the head. The cycle here is green, 3 s flashing green, about 3 s yellow, then red. We sample it every 0.1 s.

Why registration was needed

The clips are not framed identically: the afternoon clips are shifted by up to about 60 px and zoomed by 1 to 2% against the morning ones. And the camera is not quite still at the start of a recording: in C3896 the view drifts 9 px over the first 40 s while the camera settles, enough to put the signal lamps outside the patches we read. Every zone we test against (stop line, crossings, carriageways, bus stop) is drawn once, by hand, in the reference view. If we drew them per clip, a new test clip with slightly different framing would put the stop line in the wrong place.

So we match SIFT features of the clip's frames against a midday and a dusk reference, each a per-pixel median background with most of the traffic removed, and keep the homography with the most inliers: on the first frame, then again every 2 s for the first 30 s and every 10 s after. The signal lamps are read with the latest homography. At the end the keyframes' per-pixel median is registered once more, and that homography maps the tracks into the reference view for the rules.

Models, data and licences

WhatFromLicenceHow we use it
YOLO26m, YOLO26s, YOLO26n weightsUltralyticsAGPL-3.0YOLO26m for Part A, YOLO26s for Part B and, exported to ONNX, in the in-browser demo; YOLO26n for Part B in the CPU fallback
COCO 2017COCO ConsortiumAnnotations CC BY 4.0; images under their Flickr licencesWhat the detector weights were trained on. We did not train on it ourselves.
ByteTrack (Ultralytics implementation)Zhang et al.; UltralyticsMIT (original), AGPL-3.0 (Ultralytics)Multi-object tracking
OpenCVOpenCV teamApache 2.0Video metadata, SIFT, homography, colour
PyAVPyAV contributorsBSD-3-ClauseDecoding the clips for Part A
PyTorchPyTorch FoundationBSD-3-ClauseRuns the detectors
onnxruntime-webMicrosoftMITRuns the YOLO26s export in the in-browser demo
ffmpeg.wasmffmpegwasm, FFmpeg developersMIT; FFmpeg core GPL-2.0-or-laterConverts clips the browser cannot play, in the demo (source: github.com/ffmpegwasm/ffmpeg.wasm)
Sample clips C3896, C3897, C3902, C3905Hackathon organisersNo licence stated; given to participants for this hackathonScene analysis, our dev set and tuning. Not in the repository.
Our dev labels (labels/)PariVision, drafted with Claude agentsAGPL-3.0, with the repositoryTuning and scoring on the sample clips

Frame sampling and runtime budget

Source
3840x2160, 29.97 fps, about 140 Mbit/s
Part A frames
every 3rd, 9.99 per second, resized to 1920 px, batches of 8
Part A detector
YOLO26m at 1280 px
Part A time cap
stops at 1.3x the clip length, and before that drops to every 2nd or 3rd sampled frame if it falls behind; a short clip gets up to 60 s but never more than 1.8x its length; the organisers' runner allows 3x for A and B together
Part B frames
every 3rd frame it receives, 1280 px wide, YOLO26s at 960 px; down to 1 per second if it falls behind, and none once the harness's own decoding of the frames still to come would take it past 95% of its budget (2.8x the clip length minus Part A's time)
Measured runtime
3.28x clip length, Part A + Part B, Apple M5 laptop on MPS, time guards off. The harness allows 3x. On a Colab T4 the detectors take 0.34x (Part A) and 0.19x (Part B) of real time, but its 2 vCPUs decode these 4K 10-bit files at only 9 to 12 frames per second, so there the harness alone needs about 3x to read them. Part A first times the harness's own decoding loop on 40 frames and leaves it that time, with a 1.2x margin.

At 10 fps a walking pedestrian moves about 7 px of the reference view between samples near the north crossing, and a car at 40 km/h about 1.1 m. That is enough to know who is on a zebra and who crosses the stop line. The ablations on the Results page show what 5 fps and smaller detectors do to the score.