Loosely Decoupled
ExplorationPython

Finding a Bridge in 2 TB of Dashcam Video by the Color of Tunnel Lights

A red/blue ratio found the sodium-lit tunnel on 71 of 79 bridge drives after auto-exposure hid it from every brightness check.

A tired man with headphones holds a grey light meter in front of a tunnel lined with orange lamps, while a hard-hat pigeon holds up a small cyan colour swatch beside him.
On this page
A side-camera fisheye frame on the I-5/I-90 interchange curve at sunset: the low sun flares at the left edge, and the downtown Seattle skyline stands above the ramps and trees.

I wanted every crossing of the I-90 floating bridge in 240 drive folders of dashcam footage, 1,958 GB, for a montage. I wasn’t going to watch the footage to find them, and my Rivian R1T’s GPS was too coarse to. What found them was the color of the lights in the Mount Baker tunnel, which sits at the Seattle end of the bridge. Brightness couldn’t see that tunnel. A red/blue ratio found it on 71 of the 79 bridge drives scanned, and a person looking at one frame per candidate confirmed 68 crossings. How to sort months of dashcam footage with Claude Code has the rest of the job this was one stage of.

Signals instead of pixels

The footage already describes itself if you read the right parts of it. Each 15-minute segment carries one GPS fix, at its start, in the container’s location tag. That sorts drives by area, but one fix per 15 minutes can’t place a bridge crossing to the minute, let alone the second.

The finer signal is in the keyframes. The truck writes one keyframe per second. Decoding only those, downscaled to 64 × 48, gives a per-second record of a drive and skips the 29 frames between them. Each second becomes four numbers: the mean change in green from the previous keyframe, which I called motion, and the mean red, green and blue. That record was computed once, cached as CSV, and every detector afterward read the cache instead of the video.

The first calibration read a motion of zero everywhere, parked or moving. The cause was ffmpeg duplicating the keyframes back up to 30 fps, so every frame was compared with a copy of itself. -fps_mode passthrough fixed it. The bug showed up only because the check ran on a known parked segment and a known moving one before it ran on anything else. A detector that reads zero on both cases is measuring itself.

Why brightness failed

A tunnel should be the easiest thing in a drive to find: the outside goes away and the picture gets darker. In this footage it doesn’t get darker. The camera’s auto-exposure raises the gain as the light drops and holds the frame’s brightness close to what it was in daylight. That’s what the exposure control is for, and it erases the one property a brightness threshold depends on.

Why color worked

Exposure scales brightness, and the hue of the lamps comes through. The Mount Baker tunnel is lit by sodium lamps, and sodium light is orange. Inside the tunnel, the front camera’s mean red rises above its mean blue. That ratio is the whole detector.

It runs with three guards:

  • The red/blue ratio has to stay above 1.0 for 25–150 seconds, so a single orange frame or a long orange stretch doesn’t count.
  • A rolling median smooths the ratio first, so one bright frame inside a tunnel doesn’t break the run in two.
  • The first and last 3 minutes of every drive are ignored, because parking garages look the same to the ratio.
Two stacked charts over a 1,600-second window of one drive. The top chart is the smoothed red/blue ratio: it sits near 0.9 and crosses the dashed 1.0 threshold in five shaded runs, and the run at 381 seconds, labeled as the Mount Baker tunnel, peaks near 1.3. The bottom chart is mean green over the same seconds: it wanders between about 50 and 75 and stays in that range through the tunnel.
Drive 07_16_26_120000, front camera, from the cached per-second signals. The ratio is red / (blue + 1), smoothed by the detector’s rolling median.

On the 79 bridge drives scanned, it found a tunnel on 71. Seven of the eight misses were after dark. My reading is that the ratio describes light, and once the light outside is artificial too, a tunnel stops standing out from the road around it.

Detector proposes, person confirms

Finding a tunnel isn’t the same as finding the right one. A drive on I-90 passes more than one stretch that reads as a tunnel, including the Mercer Island lid, and most drives had several candidates. The obvious rule, pick one tunnel by its position in the drive, was right about two times in three.

I could have spent the effort on a better rule. Instead the tool writes a guess for every drive and a contact sheet to check it against, then stops. Each row of the sheet is one drive, and each tile is a frame from the bridge side of one candidate. The bridge is unmistakable in a frame, so the right column is obvious, and the edit is one number in a picks file, which the cut turns into synced clips. Reviewing that way confirmed 68 crossings. Two night drives couldn’t be confirmed, because a dark frame of a dark bridge proves nothing, and one drive had no crossing among its candidates.

One row of the review sheet: the drive from the chart above, with one tile per candidate. The first tile is a freeway lined with trees, the second is the floating bridge, and the last three are replaced by gray blocks.
The second tile is the bridge, so the pick is candidate 2. Three frames removed.

The layout of the sheet mattered as much as having one. The first version was a flat grid of every candidate frame, and it was easy to match a tile to the wrong drive. One row per drive makes that mistake visible rather than merely possible.

The sheet is also an instrument, and an instrument can be wrong. At one point I announced that the picks for some drives were the Mercer Island lid, not the Mount Baker tunnel. The sheets had been built with a seek that ffmpeg applied after tiling, so they showed a different window than the one they claimed. Extracting the same frame two ways exposed it, and the picks had been right all along. When a result contradicts work that was already verified, check the instrument before the conclusion.

The review step reads like an admission that the detector wasn’t good enough. I think it’s the reason the detector could stay simple. Take a rule that’s right two times in three, and add a sheet a person reads at a glance. That beats a rule that’s right more often and still needs someone to check it.

Signals that could not separate the cases

The same per-second record answered four other questions, and each answer was no for the same reason: the signal moved for the thing I wanted and for something I didn’t.

  • Parked footage. Near-zero motion found parked time reliably, but there was little to find: 23 GB of the 1,760 GB scanned, because with Gear Guard turned off, the truck stops recording seconds after it parks.
  • Hard stops. A sudden change in the picture marks braking, but turning changes the picture as much. Of 875 candidates, all 12 at the top were turns into parking lots or ordinary stops.
  • Traffic jams. Sustained low motion should mark crawling traffic, but the neighboring cars keep moving, so the picture keeps changing and a jam looks like driving.
  • Garage boundaries. A gray band at the top of the frame marks a concrete ceiling on a clear day, but an overcast sky is the same gray. The boundary was set by timing instead.

None of these is fixed by tuning a threshold. When a signal can’t tell two cases apart, a better number for the threshold still can’t.

Close passes did work once the frame’s overall brightness change was subtracted from the side cameras: 37 of the top 60 were real passes. A person still had to sort them.

Signals the camera leaves alone

A camera processes its own picture before anything reads it, and that processing exists to make the picture look normal. Auto-exposure made a tunnel look like daylight and left the lamp color intact. Before measuring anything in recorded video, ask which properties the camera flattens and which it passes through, and build on the second kind.

New articles, by email.

A short note when something new is published.