Inspiration
Rental companies now scan returned cars with AI. One Hertz renter got a $440 bill for a one-inch scuff ($250 damage, $125 processing, $65 admin), and a US House subcommittee chair asked Hertz about damage assessments issued without human review. Landlords keep careful move-out photos too. Most renters have a few blurry phone shots that nobody can compare. We wanted the renter to have evidence as good as the company's.
What it does
Twice is renter-side evidence for apartments and rental cars.
- At move-in, you photograph a few spots. A quality gate waits until each photo is sharp and textured enough to match later. The set is hashed and sealed with a signature.
- At move-out, Twice puts a ghost of each first photo over the camera and guides you back to the same angle.
- OpenCV 5 aligns the two photos, finds what physically changed, and rules out shadows, lamps and exposure.
- When a change could be a reflection, the agent asks for one more photo from a step to the side. A reflection moves with the camera; damage stays. If the camera did not really move, it asks again, and then hands the finding to a person.
- The result is a signed report with every finding, every ruled-out shadow, the hash of every photo and the agent's full trace. Anyone can check it offline with the evidence bundle and
python verify.py.
How we built it
- OpenCV 5: SIFT and FLANN matching, a
USAC_MAGSAChomography,warpPerspectiveand DIS optical flow for registration; a structure and colour z-score change map withconnectedComponentsWithStats; NanoDet-Plus on the new DNN engine (cv2.dnn.ENGINE_NEW) to tell objects from surface marks. - Agent: a perception-decision-action loop with tools
relight_check,glare_check,parallax_check,size_checkandobject_check, and a budget of two follow-up photos per spot. Every decision is logged with the measurement that caused it and signed into the report. - MCP: the same OpenCV 5 tools are exposed as an MCP server, so any agent (Claude Desktop, Claude Code, or our own runner) can run a check through them.
- AWS: one container on Lambda (arm64, Graviton) behind a Function URL; S3 with Object Lock and SSE-KMS for evidence; an asymmetric KMS key (ECDSA P-256) for signatures; Claude on Bedrock for plain-language labels (it can name a change or flag it for a human, never change a verdict); CloudWatch metrics for latency and every agent decision. All of it is one SAM template (
deploy/template.yaml). A COOL kit runs the same check on Graviton4 against pip OpenCV on Arm and x86. - Web: React and Vite, phone and desktop layouts.
Evaluation
- Agent level (170 synthetic pairs × 5 RANSAC seeds = 850 runs, follow-up photos included): defect recall 0.88, precision 1.00; 754 of 850 runs fully correct. The agent asked for a side-step photo in 70 of 75 reflection runs, and all 70 ended correctly.
- Real photos of unchanged scenes (Oxford affine sequences): 0 false alarms on 22 judged pairs; views too far away get a retake request instead.
- Hybrid (drawn defects on real photos): recall 0.24, precision 0.96. The synthetic numbers are an upper bound, and we say so.
- Failure gallery, ablations and methods are in
docs/evaluation.md.
Agentic Vision evidence
1. Workflow diagram (perception → decision → action): agent-loop.png, also in the gallery above.
| Stage | What runs |
|---|---|
| Perception | OpenCV 5: SIFT + FLANN, USAC_MAGSAC homography, warpPerspective, DIS optical flow, structure/colour z-score change map, NanoDet on cv2.dnn.ENGINE_NEW |
| Decision | Rule planner (agent.py) with a budget of two follow-up photos per spot; each tool result selects the next tool call |
| Action | Ask for a retake, a side-step photo or a close-up; rule out, report, or mark "can't judge"; hand a finding to a person; sign the report |
2. Trace where OpenCV 5 output changes the next action (agent-trace.md, generated through the real API). Rental car spot, finding f1:
| Run | OpenCV 5 result | Agent's next action |
|---|---|---|
| Renter steps aside | glare_check: specular change +12% → side-step photo → compare_followup: mark vanished after the camera moved 2.5% |
Rule out as glare |
| Renter does not move (counterfactual) | parallax_check: camera moved 0.0% of the frame, under 1%, twice → budget: no follow-ups left |
Mark "can't judge" and hand to a person |
Only the photo differs between the two runs. The same loop is exposed as an MCP tool server (mcp_server.py); an external agent driving it makes request_human_review(f1) only in the counterfactual (moved, not moved), checked by test_mcp.py.
3. Evaluation (agentic-vision.md, section 3):
- Task success: 754 of 850 full-loop runs correct (170 pairs × 5 RANSAC seeds); recall 0.88, precision 1.00. The engine alone flags all reflection pairs as damage; with the loop, 70 of 70 runs that asked for a side-step photo ended correctly.
- Failure handling: no registration → retake; follow-up does not register → "can't judge"; camera did not move → ask again, then "can't judge"; budget spent → "can't judge"; person in frame → "can't judge"; Bedrock unavailable → rule labels.
- Observability: every tool call is logged with the measurement that caused it, shown in the app and signed into the report; CloudWatch metrics
AgentDecision(per tool),CompareLatency; error alarm. - Human control: the renter takes every photo; the agent only asks and explains why; the language model cannot add, delete or change a verdict; every "Changed" and "Can't judge" item goes to a person; anyone can verify the signed result offline.
Challenges we ran into
- Reflections look exactly like scratches in a single comparison. The side-step photo and the check that the camera really moved were the fix.
- RANSAC uses OpenCV's global RNG, so a result could depend on what ran earlier. Every benchmark now seeds it and reports five seeds.
- Getting real damage pairs is hard. We built three benchmarks and report the weakest number alongside the best one.
Accomplishments that we're proud of
- A counterfactual trace where only the photo differs and the agent's next action changes.
- Evidence that can be checked without trusting our server.
- A sample check a judge can finish in two minutes.
What we learned
Most of the work in change detection is ruling things out: light, exposure, small shifts, reflections. Saying "can't judge" honestly is better than a confident wrong answer.
What's next
Real move-in and move-out pairs with real marks, a study with renters, and a COOL run on Graviton4 to measure the speed-up.
Responsible use
Twice gives evidence, not verdicts. The report asks a person to review every "Changed" and "Can't judge" item. The language model cannot add, delete or change a verdict. Photos expire after 30 days, are encrypted with KMS, and are only stored for the check that made them.
Links
- Code: https://github.com/mukeshkbj/opencv2026
- Technical report:
docs/report.md; evaluation:docs/evaluation.md; Agentic Vision evidence:docs/agentic-vision.md; COOL kit:docs/cool.md - Architecture diagrams:
docs/diagrams/architecture.png,docs/diagrams/agent-loop.png
Built With
- amazon-bedrock
- amazon-cloudwatch
- amazon-web-services
- aws-kms
- aws-lambda
- aws-sam
- claude
- docker
- fastapi
- graviton
- mcp
- numpy
- onnx
- opencv
- python
- react
- typescript
- vite
Log in or sign up for Devpost to join the conversation.