ANPR Scanner Flutter
A Flutter package for Automatic Number Plate Recognition (ANPR) using TFLite models. Runs a two-stage ML pipeline (detection + OCR) entirely on-device for real-time license plate scanning.
BYO Models — the package does not bundle any TFLite models. You provide your own detection and OCR
.tflitefiles.
Features
- Real-time license plate detection and OCR on camera frames
- GPU-accelerated inference via TFLite GPU delegate (falls back to CPU)
- Drop-in
AnprScannerWidgetwith built-in camera, permissions, loading UI, and scan flow - Low-level
LicensePlateDetectorAPI for custom integrations - Stability tracking with configurable quality gates for auto-capture
- Optimized YUV-to-RGB conversion with lookup tables and isolate offloading
Quick Start
1. Add the dependency
dependencies:
anpr_scanner_flutter:
path: ../ # or git/pub reference
2. Provide model files
Place your TFLite model files in your app's assets:
your_app/
assets/
models/
detection.tflite # YOLOv8-style plate detection model
ocr.tflite # Character detection model
Register them in pubspec.yaml:
flutter:
assets:
- assets/models/
3. Platform setup
Android — Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
iOS — Add to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is needed to scan license plates.</string>
4. Use the scanner
import 'package:anpr_scanner_flutter/anpr_scanner_flutter.dart';
// Drop-in widget — handles everything automatically
AnprScannerWidget(
detModelPath: 'assets/models/detection.tflite',
ocrModelPath: 'assets/models/ocr.tflite',
onPlateRecognized: (result) {
print('Plate: ${result.fullPlate}');
print('Code: ${result.code}, Number: ${result.number}');
},
onError: (error) => print('Error: $error'),
)
Two Abstraction Levels
High-Level: AnprScannerWidget
A self-contained widget that manages the full scan lifecycle:
loading -> scanning -> capturing -> recognizing -> result
AnprScannerWidget(
detModelPath: 'assets/models/detection.tflite',
ocrModelPath: 'assets/models/ocr.tflite',
onPlateRecognized: (LicensePlateResult result) {
Navigator.pop(context, result);
},
// Optional customization:
delegateType: DelegateType.gpu, // gpu (default), cpu, nnapi, auto
captureQualityConfig: CaptureQualityConfig(
minConfidence: 0.70,
requiredStableFrames: 3,
maxCentreMoveFraction: 0.08,
),
preloadedDetector: myDetector, // skip loading if already loaded
)
Includes:
- Camera permission handling
- Model loading overlay with progress
- Live bounding box overlay
- Pinch-to-zoom and tap-to-focus
- Torch toggle
- Capture flash animation
- Result card with plate image and metrics
Low-Level: LicensePlateDetector
Direct access to the two-stage inference pipeline. Use this when you want full control over the image source and UI.
final detector = LicensePlateDetector();
await detector.initialize(
detModelPath: 'assets/models/detection.tflite',
ocrModelPath: 'assets/models/ocr.tflite',
delegateType: DelegateType.gpu,
);
// Run on any img.Image (from camera, gallery, file, etc.)
final result = await detector.recognizePlate(image);
if (result != null) {
print('Full plate: ${result.fullPlate}');
print('Code: ${result.code}');
print('Number: ${result.number}');
print('Confidence: ${result.plateBox?.confidence}');
print('Detection time: ${result.metrics.detectionMs}ms');
print('OCR time: ${result.metrics.ocrMs}ms');
print('Total time: ${result.metrics.totalMs}ms');
}
detector.dispose();
ML Pipeline
The package runs a two-stage pipeline:
Stage 1: Detection
- Input: 640x640 float32 letterboxed image
- Model: YOLOv8-style object detection
- Output: Bounding boxes with confidence scores
- Post-processing: Non-Maximum Suppression (NMS)
Stage 2: OCR
- Input: 160x160 float32 letterboxed crop of the detected plate
- Model: Character-level detection (0-9, A-Z)
- Output: Character positions with class IDs and confidences
- Post-processing: Left-to-right sorting, then code/number splitting via:
- Letter/number regex separation (when alphabetic characters are present)
- Largest-gap heuristic (for all-numeric plates)
GPU Delegate Strategy
- The detection model uses the GPU delegate for maximum performance (it's the heavier model).
- The OCR model uses CPU threads. Running two simultaneous GPU delegates crashes the native GPU driver on many Qualcomm Adreno and ARM Mali devices.
Model Requirements
Detection Model
| Property | Value |
|---|---|
| Input shape | [1, 640, 640, 3] |
| Input type | float32 (0.0-1.0) |
| Output | YOLOv8 transposed format |
| Classes | 1 (license plate) |
OCR Model
| Property | Value |
|---|---|
| Input shape | [1, 160, 160, 3] |
| Input type | float32 (0.0-1.0) |
| Output | YOLOv8 transposed format |
| Classes | 36 (0-9, A-Z) |
Key Classes
| Class | Purpose |
|---|---|
AnprScannerWidget |
Drop-in scanner widget with full UI |
LicensePlateDetector |
Core TFLite inference engine |
ModelServiceManagerOptimized |
Singleton model cache with async loading |
DetectionOnlyProcessor |
Real-time camera frame pipeline with stability tracking |
LicensePlateResult |
Recognition result (code, number, metrics, cropped plate) |
CaptureQualityConfig |
Stability gate thresholds for auto-capture |
RealtimeConfig |
Global tuning for frame pacing, conversion, thresholds |
DetectionOverlay |
Animated bounding box overlay widget |
ModelLoadingOverlay |
Model loading progress widget |
Configuration
CaptureQualityConfig
Controls when auto-capture triggers during real-time scanning:
CaptureQualityConfig(
minConfidence: 0.55, // Min detection confidence per frame
requiredStableFrames: 3, // Consecutive passing frames before capture
maxCentreMoveFraction: 0.08, // Max box centre drift between frames
maxAreaChangeFraction: 0.06, // Max box area change between frames
minPlateAreaFraction: 0.005, // Min plate area relative to frame
)
RealtimeConfig
Static constants for global real-time tuning:
downsampleFactor-- Frame downsampling during conversion (default: 1)useIsolateConversion-- Offload YUV-to-RGB to background isolate (default: true)minFrameIntervalMs-- Min time between processed frames (default: 50ms)detectionConfidence-- Live detection threshold (default: 0.50)staleFrameThreshold-- Frames before clearing overlay (default: 3)useLookupTables-- Pre-computed YUV tables (default: true)
Package Structure
lib/
anpr_scanner_flutter.dart # Public API barrel file
src/
config/
realtime_config.dart # Frame pacing, conversion, stability thresholds
models/
detection_box.dart # DetectionBox, CharDetection
license_plate_result.dart # LicensePlateResult, InferenceMetrics
captured_frame.dart # CapturedFrame, CaptureQualityConfig
services/
license_plate_detector.dart # Core TFLite inference (detection + OCR)
model_service_manager_optimized.dart # Singleton async model loader/cache
detection_only_processor.dart # Real-time camera frame processor
utils/
camera_image_converter_optimized.dart # YUV420/BGRA -> img.Image
widgets/
anpr_scanner_widget.dart # High-level drop-in scanner widget
detection_overlay.dart # Animated bounding box overlay
scan_result_overlay.dart # Frozen frame, result card, flash, recognizing overlay
loading_widgets.dart # Model loading overlay
Dependencies
| Package | Purpose |
|---|---|
tflite_flutter |
TFLite model inference |
camera |
Camera stream access |
image |
Image manipulation (resize, crop, encode) |
permission_handler |
Camera permission requests |
image_picker |
Gallery image selection (low-level usage) |
path_provider |
File system paths |
License
See LICENSE for details.