Skip to content

Repository files navigation

PlateWatch

An open-source, privacy-first license plate scanner with a personal watchlist.

PlateWatch turns your phone into a real-time license plate reader. Point the camera at traffic, and it reads plates using on-device OCR. Keep a personal watchlist of plates you care about — when one is spotted, the screen flashes red and you get a local notification.

Everything runs entirely on your device. No cloud APIs, no accounts, no analytics, no data collection. Your watchlist and everything the camera sees never leave your phone.

This is a proof of concept. It works, but expect rough edges — and read the responsible use section before pointing it at the world.

What it does

  • 📷 Live scanning — full-screen camera preview with OCR running at ~2.5 fps (throttled to be kind to your battery)
  • 🔍 Plate extraction — filters OCR output down to plate-shaped text (2–8 alphanumeric characters, US-style formats)
  • 📋 Personal watchlist — add plates manually; they're normalized (uppercased, spaces/hyphens stripped) so abc-1234 matches ABC1234
  • 🚨 Match alerts — pulsing red border, the matched plate front and center, and a local notification
  • 🔕 Alert deduplication — the same plate won't re-alert within a 60-second window
  • 🔒 On-device only — ML Kit text recognition runs locally; storage is a local Hive box

Screenshots

Scanner Match alert Watchlist
coming soon coming soon coming soon

Getting started

Install the APK (Android)

Grab the APK from the latest release and install it (you'll need to allow installs from unknown sources). It's a universal APK, so it works on any Android 7.0+ device.

iOS has no sideloading — build from source with Xcode (see below).

Build from source

Prerequisites

  • Flutter 3.32+ (stable channel)
  • Android: a physical device recommended (emulator cameras won't read real plates); minSdk follows Flutter's default (24)
  • iOS: Xcode 15+, iOS 15.5+ (required by ML Kit), a physical device

Run it

git clone https://github.com/flutterWithChris/platewatch.git
cd platewatch
flutter pub get
flutter run

On first launch the app asks for camera permission (and notification permission on Android 13+ / iOS). If you decline, the scanner screen shows a prompt with a shortcut to system settings.

Try it out

  1. Open the watchlist (list icon, top right) and add a plate — your own car is a good test subject.
  2. Go back to the scanner and point the camera at the plate.
  3. You should see detected text appear in the bottom overlay, then the red match alert and a notification.

Run the tests

flutter test

The plate extraction and matching logic is pure Dart and fully unit-tested — no device needed.

How it works

camera frames (~30 fps)
  └─ throttle to ~2.5 fps            scanner_screen.dart
      └─ ML Kit text recognition     services/ocr_service.dart
          └─ extract plate shapes    services/plate_extractor.dart
              └─ match vs watchlist  services/plate_matcher.dart
                  ├─ overlay + red flash
                  └─ local notification   services/notification_service.dart

Project layout

lib/
├── main.dart                  # bootstrap: Hive, services, orientation lock
├── app.dart                   # MaterialApp + theme
├── models/
│   └── watchlist_entry.dart   # Hive model (hand-written adapter)
├── screens/
│   ├── scanner_screen.dart    # camera lifecycle + frame pipeline
│   └── watchlist_screen.dart  # add / swipe-to-delete plates
├── services/
│   ├── ocr_service.dart       # ML Kit wrapper + CameraImage conversion
│   ├── plate_extractor.dart   # normalization + candidate filtering (pure Dart)
│   ├── plate_matcher.dart     # watchlist matching + 60s alert cooldown
│   ├── watchlist_service.dart # Hive-backed watchlist store
│   └── notification_service.dart
└── widgets/
    ├── detection_overlay.dart # live "what the OCR sees" chips
    ├── match_alert_overlay.dart
    └── camera_permission_view.dart

Design notes

  • Plate filtering is heuristic. A candidate is any 2–8 character alphanumeric string after normalization. The live overlay additionally requires ≥4 chars and at least one digit to cut down on street-sign noise — but watchlist matching checks all candidates, so short or all-letter vanity plates still alert. Tune these rules in plate_extractor.dart.
  • The camera is torn down when the app leaves the foreground and rebuilt on resume — no background processing (yet, see roadmap).
  • No state-management package. Services are plain classes injected through constructors; WatchlistService is a ChangeNotifier. If the app grows, swapping in provider/riverpod is mechanical.
  • Alert cooldown state is in-memory and resets when scanning restarts. A persistent match history is a roadmap item.

Roadmap

Things this architecture anticipates but the POC intentionally doesn't build:

  • Crowdsourced / community watchlists with subscribe–unsubscribe (Supabase or Firebase) — ⚠️ gated on the responsible use design principles: opt-in vehicle-incident lists (e.g., stolen-vehicle reports) only, never lists that track people
  • Match history log with timestamps and GPS coordinates
  • Plate image capture on match
  • Watchlist export / import
  • Background scanning mode
  • Non-US plate format profiles

The seams are already in place: all plate sources funnel through WatchlistService, and every "a match happened" decision goes through PlateMatcher — history, image capture, and GPS tagging all hook in there.

Responsible use

PlateWatch is a neutral tool built for legitimate uses: spotting a stolen vehicle from a community alert, watching for a car you own, neighborhood-watch coordination. License plate surveillance carries real privacy implications — tracking individuals without consent may be illegal where you live, and it's a bad thing to do regardless. Know your local laws. Don't build databases of other people's movements.

Know your state law. Several US states restrict or prohibit private use of automated license plate readers outright — Arkansas, Maine, and New Hampshire effectively limit ALPR use to law enforcement. See the NCSL summary of state ALPR statutes and check your own jurisdiction before using this app.

Design principles

Contributions must preserve these, in this order of priority:

  1. On-device only. The core app makes no network calls — no cloud OCR, no analytics, no telemetry, no accounts.
  2. No shared or supplied watchlists of people. Watchlists are personal, entered by the user, about vehicles they have a legitimate reason to watch. Never ship a list, and never make a person the unit of tracking.
  3. Match history, if ever built, stays local — with a short retention default and a one-tap purge.
  4. Alerts point to reporting, not confrontation. If a future feature acts on a match (e.g., a stolen-vehicle alert), it should direct the user to report to police — never to approach or follow anyone.

Contributing

This project is meant to be forked and extended. PRs welcome — keep the on-device, no-cloud promise intact for the core app, and keep the plate pipeline (plate_extractor.dart, plate_matcher.dart) covered by unit tests.

License

MIT

About

Open-source, privacy-first license plate scanner with a personal watchlist. On-device OCR - no cloud, no accounts, no tracking.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages