Inspiration

I ride County Road 116 in Shulin, New Taipei, every day. Just before it climbs onto a bridge, one short stretch squeezes a scooter from both sides: a solid line on the left, a narrowing painted island ahead, and on the right a drain strip with seven steel grates. Nothing is obviously illegal. Every piece sits at the edge of a written limit, and they all land on the same stretch and the same rider. I wanted to turn that feeling into numbers anyone can check.

What it does

Send one street photo to the endpoint (a full-size photo takes 1.5–4 minutes with the burst check below (measured 89.7 s and 221.7 s), so it goes to a second endpoint without the 30-second limit). It checks that the photo shows a road, then measures against written rules: how far the red no-stopping line moved (using the line's own legal 10 cm width as the ruler) and how sharply the lane closes (compared, for reference only, with the design table's minimum for the speed limit sent with the request; on this road during the works that was 5:1 at 30 km/h). A number is reported only if at least three of four re-saved versions of the image measure it and agree; when the photo alone is refused, the second loop below can measure across photos taken seconds apart. It says what it measured, what it couldn't and why, and ends with what to do next: which document to request from which office, tied to what it found in the photo.

Offline, the same OpenCV 5 methods produced the evidence in the story:

  • In all 16 top-down photos (grates 1–6; grate 7 has no top-down photo), the new red line's axis runs across the grate, 1.0–14.3 cm from its centre.
  • The red line moved out 0.63–0.68 m (median gap, new line's inner edge to old line's centre), onto the drain strip; a card laid across it confirms the 10 cm ruler (about 95–97 mm).
  • The painted island opens at about 12:1 where I measured it on photo F40, and more steeply near the cone: about 6:1 (its outer edge at 9°). The design table asks for at least 5:1 at 30 km/h; used here for reference only.
  • Seen from above. Standing on the road you can't compare old and new paint, so OpenCV turns my photo P64 into a top-down view with the lane line's dashes (4 m dash, 6 m gap) as the ruler. A triangle of the island's old end, about 8.5 m along the bridge wall and 2.1 m wide (about 9 m²), was painted over; its old slanted edge (about 14° to the road) is still faintly visible, and the left lane's arrow is painted on top of it.
  • After the merge everyone shares one lane of about 3.1 m. By my own sum (car 1.78 m + 0.5 m clearance + scooter 0.75 m = 3.03 m) that leaves about 10 cm, inside the measurement's error; next to a bus it is about 0.6 m short. In my experience as a rider, scooters and cars fight for that one lane, the grates and red line leave no room on the right, and it is worse in rain or at night: that part is experience, not measurement.
  • Who did what in the top-down view: OpenCV made the view, found the edge line, a lane dash (4.1 m, which checks the ruler) and where today's stripes stop, and measured the angle. I gave the dash centres, the old edge's two ends and the line its tip reaches. About ±10%.

Agentic Vision: the photo decides what happens next

What the system does next depends on what OpenCV found in the photo; it is not a fixed script.

  • Not a road: it stops at the first gate and asks for a retake.
  • A number that moves when the photo is re-saved: it is refused, with the reason, and the refusal becomes a "cannot answer from this photo" action instead of a figure.
  • A finding that calls for it: it adds a request (which document, from which office) or flags a box for a person to open. It files nothing and contacts no one; a person closes the loop.

The same scene at two resolutions gives different next steps, because the measurement held at one and not the other. Photo F31 at full size: red-line gap 0.673 m measured (three runs read 0.668–0.673 m), so the next steps are "request the marking plans" and "request the drain records". The same photo at 2000 px: CANNOT_MEASURE, so the next steps become "request the traffic plan" and "check the dig permit".

On 20 field photos the system produced 4 distinct sets of next actions, with a stated reason on every action (document requests on 15, a person-look on 16). That checks the decision step, not the findings: the first-pass red-line detector fires on 16 of the 20 and has often been wrong on earlier frames, which is why its action is "a person opens this box" and never a conclusion. The decision layer is a deterministic rule set over the OpenCV output, not an LLM.

When it refuses, it goes and gets more evidence. If the red-line gap is refused on one photo but the line pair was seen, or it was measured and can be confirmed, the system fetches the photos taken within 3 seconds, registers each with OpenCV 5's ECC, measures again, and reports the gap only if two or more photos measure it and all read within 5 cm of each other; otherwise it asks for a retake; a gap already measured on the first photo stands when no other photo can confirm it. ECC is used only at correlation ≥ 0.90, because it also "converges" on another view or place. Live on photo F31: confirmed on F30 and F32, 0.673 m.

Workflow diagram, trace and evaluation: section 8 of the technical report; diagram here.

How I built it

OpenCV 5.0.0 in an arm64 container on AWS Lambda, behind API Gateway and a Lambda Function URL (no 30-second limit, for full-size photos). The top-down view comes from the lane dashes: cv2.fitLine and a projective ladder give the road plane, cv2.warpPerspective the view, cv2.HoughLinesP the lines. Offline, grates are found by gap darkness, morphology and a bar-periodicity (FFT) test (the online endpoint does not detect grates); the red line by LAB colour and line fitting; in the offline analysis, the island's edges only when two independent recognisers agree (outermost paint and EDLines; the online endpoint still runs an older single-method taper check); the ground angle from vanishing points and the camera intrinsics, with a self-check that the lane edge meets the horizon at the road's own vanishing point. Most published numbers are pinned to the file that produced them and checked by a script; the rest are marked as notes or estimates. 221 tests, run automatically on every push.

How accurate is it? Nobody has surveyed this road, so I check every figure the ways a rider with a phone can: against sizes fixed by law (a lane dash reads 4.1 m against 4 m; the red line 95–97 mm against 100 mm), on repeat photos (the red line’s distance from a grate’s centre agrees within 1.8 cm across three photos), and on re-saved copies. Everything, including what failed: docs/accuracy.md

The endpoint runs stock OpenCV 5 on an arm64 (Graviton) Lambda. It does not use the Cloud-Optimized OpenCV Library (COOL) and is not entered for the COOL award.

Challenges I ran into

My first published taper, "about 10:1", was wrong. Re-saving the photo once turned 11.9 into 23.2. Drawing what the program had picked showed it was grouping different painted lines, not the island's two edges. I withdrew the number, rebuilt the measurement so two recognisers must agree, and added the re-save check (three of four versions must agree) to the endpoint. The deployment also hit the AWS account's 3,008 MB Lambda memory cap, so a full-size photo took about 2.5 minutes on v14, longer than API Gateway's 30 seconds. A Lambda Function URL has no such limit, but it returned 403 until I found that it also needs lambda:InvokeFunction granted for calls made through the URL. Now full-size photos measure online: F31 gave the red-line gap 0.668 m in 145.5 s on image v14.

Accomplishments that I'm proud of

Every number in the story has a stated source, and the ones that did not survive re-checking are listed as withdrawn rather than removed. The system refuses rather than guesses.

What I learned

A measurement that changes when the image is re-saved is not a property of the road. Drawing what the algorithm picked is faster than any statistic at showing whether it is measuring the right thing.

What's next

Request the three documents the system lists (marking plans, the works' traffic plan, the drain records); measure the grate step and wet grip on site with a 3 m straightedge and a skid tester; photograph the stretch at night; raise the Lambda memory limit so full-size photos take less than the 1.5–4 minutes they take now; find the lane dashes automatically (today a person marks them for the top-down view).

Built With

Share this project:

Updates

Submission history