PlateVision SDK
Real-time, offline license plate recognition for iOS, Android & Flutter.
PlateVision is a mobile SDK delivering real-time ALPR entirely on-device. Images and recognized plate text never leave the device; only anonymous aggregate licence and usage counters synchronize periodically.
You need an API key. Create a free account — no credit card required.
Quickstart
Scan plates in your iOS app in under 5 minutes with the camera-managed distribution. If your application already owns the camera pipeline, use the External Frame Input Version.
- Get your credentials
Sign up and copy your API key and secret from the dashboard.
- Build and add the framework
Run
./build.shfrom the Framework folder, drag the resultingPlateVisionSDK.xcframeworkinto Xcode, set Embed & Sign. - Add camera permission
Add
NSCameraUsageDescriptiontoInfo.plist. - Activate and scan
import PlateVisionSDK PlateVisionScanner.shared.activate(apiKey: "pv_live_...", secretKey: "sk_...") { result in guard case .success(let l) = result, l.isValid else { return } PlateVisionScanner.shared.delegate = self PlateVisionScanner.shared.startScanning() } func plateVision(_ sdk: PlateVisionScanner, didDetect result: PVDetectionResult) { print(result.plateText, result.confidencePercent) }
Add the preview. Use PVCameraPreview(sdk: .shared) in SwiftUI or access PlateVisionScanner.shared.previewLayer for UIKit.
Authentication
Every SDK call is authenticated with an API key + secret key pair from your dashboard.
| Credential | Format | Notes |
|---|---|---|
apiKey | pv_live_... | Identifies your app. Safe to bundle in the binary. |
secretKey | sk_... | Authenticates activation. Keep private, never log. |
Never commit keys to source control. Use environment variables or a gitignored config file.
iOS SDKiOS
Installation
Binary XCFramework for iOS 16+ (arm64 device + arm64/x86_64 Simulator).
cd PlateVisionSDK/Framework
chmod +x build.sh && ./build.sh
# Outputs: build/PlateVisionSDK.xcframeworkThen drag PlateVisionSDK.xcframework into Xcode → set Embed & Sign → add NSCameraUsageDescription to Info.plist.
Swift Package Manager
.package(url: "https://github.com/platevision/platevision-ios-sdk", from: "1.0.0")
Activation
let scanner = PlateVisionScanner.shared scanner.licenceValidationURL = URL(string: "https://logentrixsystems.com/platevision/api/validate-licence.php")! scanner.activate(apiKey: key, secretKey: secret) { result in switch result { case .success(let l) where l.isValid: // ready case .failure(let e): print(e) default: break } }
Configuration
var config = PVConfiguration.default config.region = .us config.minimumConfidence = 0.70 config.deduplicationWindow = 3.0 config.enableSound = true config.scanROI = CGRect(x:0.07, y:0.40, width:0.86, height:0.20) PlateVisionScanner.shared.configure(config)
| Property | Type | Default | Description |
|---|---|---|---|
region | PVRegion | .auto | Country hint for validation. |
minimumConfidence | Float | 0.60 | OCR threshold (0–1). |
deduplicationWindow | TimeInterval | 2.0 s | Suppress duplicate detections. |
enableSound | Bool | true | System tick on each hit. |
scanROI | CGRect | centre 86×20% | Normalised pixel-buffer crop rect. |
Delegate
public protocol PlateVisionDelegate: AnyObject { func plateVision(_ sdk: PlateVisionScanner, didDetect result: PVDetectionResult) func plateVision(_ sdk: PlateVisionScanner, didChangeState state: PVState) func plateVisionDidFailLicence(_ sdk: PlateVisionScanner) }
| PVDetectionResult | Type | Description |
|---|---|---|
plateText | String | Uppercased stripped plate number |
confidence | Float | OCR confidence 0–1 |
confidencePercent | Int | confidence × 100 |
boundingBox | CGRect | Normalised Vision rect within scan ROI |
timestamp | Date | Time of detection |
region | PVRegion | Active region at time of detection |
Camera UI
ZStack {
PVCameraPreview(sdk: .shared).ignoresSafeArea()
// overlay: scan guide, result card, controls...
}if let layer = PlateVisionScanner.shared.previewLayer { layer.frame = view.bounds view.layer.insertSublayer(layer, at: 0) }
External Frame Input Version iOS
This distribution is intended for applications that already own their camera session. PlateVision does not create or control an AVCaptureSession; the application supplies each live frame or still image.
Frame ownership stays with your application. The SDK processes the supplied image without storing or modifying it. Your application can apply its own person and unrelated-plate anonymisation before any evidence image is persisted.
Live frames
Set the AVCaptureVideoDataOutput sample-buffer delegate queue to frameQueue, then pass each available frame to PlateVision:
private let frameQueue = DispatchQueue(label: "camera.frames") private var isProcessingFrame = false func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard !isProcessingFrame else { return } isProcessingFrame = true PlateVisionScanner.shared.process( sampleBuffer: sampleBuffer, orientation: .right ) { [weak self] result in switch result { case .success(let detections): detections.forEach { print($0.plateText, $0.boundingBox) } case .failure(let error): print(error.localizedDescription) } self?.frameQueue.async { self?.isProcessingFrame = false } } }
Use backpressure as shown above: do not enqueue every camera frame while accurate OCR is still processing the previous frame.
Still images and benchmark datasets
PlateVisionScanner.shared.process(image: image) { result in if case .success(let detections) = result { let best = detections.first print(best?.plateText ?? "No plate") } }
| Input API | Use case |
|---|---|
process(sampleBuffer:orientation:completion:) | Frames from AVCaptureVideoDataOutput. |
process(pixelBuffer:orientation:completion:) | Existing Core Video or computer-vision pipelines. |
process(cgImage:orientation:completion:) | Core Graphics images and decoded datasets. |
process(image:completion:) | UIKit images and benchmark datasets. |
Completions are delivered on the main queue. Results are ordered by confidence. In the external-frame APIs, boundingBox uses Vision's normalised coordinates relative to the complete supplied frame, with the origin at the lower-left. The external-frame version processes the complete supplied image; crop the buffer before submission if your pipeline requires a region of interest.
Full API Reference
| Method / Property | Description |
|---|---|
activate(apiKey:secretKey:completion:) | Validate credentials. Once per launch. |
licenceValidationURL | Validation endpoint configured before activation. |
configure(_ config:) | Apply config any time. |
process(sampleBuffer:orientation:completion:) | Recognise plates in a caller-owned camera frame. |
process(pixelBuffer:orientation:completion:) | Recognise plates in a caller-owned pixel buffer. |
process(cgImage:orientation:completion:) | Recognise plates in a Core Graphics image. |
process(image:completion:) | Recognise plates in a UIKit still image. |
startScanning() | Request camera permission + start. |
stopScanning() | Stop and tear down session. |
pause() / resume() | Suspend/restore without teardown. |
captureOnce() | Detect one plate then auto-pause. |
delegate | PlateVisionDelegate receiver. |
state | .idle / .running / .paused / .permissionDenied / .error |
previewLayer | AVCaptureVideoPreviewLayer (set after startScanning). |
licenceInfo | PlateVisionLicence (set after activation). |
Android SDKAndroid
dependencies { implementation("io.platevision:sdk-android:1.0.0") }PlateVisionSDK.getInstance().activate(context=this, apiKey="pv_live_...", secretKey="sk_...") { result -> if(result.isSuccess) { PlateVisionSDK.getInstance().startScanning(lifecycleOwner=this, previewView=binding.pv, onDetection = { p -> Log.d("PV", p.plateText) }) } }
Flutter SDKFlutter
dependencies: platevision_sdk: ^1.0.0
final l = await PlateVisionSDK.activate(apiKey:'pv_live_...', secretKey:'sk_...'); PlateVisionCamera( onDetection: (r) => print(r.plateText), config: PVConfig(region: PVRegion.auto, minimumConfidence: 0.7), )
Regions
| Region | Enum | Length | Example |
|---|---|---|---|
| Auto (global) | .auto | 5–10 | Any valid plate |
| US / Canada | .us | 5–8 | ABC1234, 353-42T |
| United Kingdom | .uk | 5–7 | AB12CDE |
| Europe | .europe | 5–8 | AB1234CD |
| Australia | .australia | 5–7 | ABC123 |
| India | .india | 8–10 | MH01AB1234 |
| Middle East | .middleEast | 5–8 | ABC12345 |
Plate validation pipeline
- Clean — uppercase, strip spaces, dashes, dots
- Length — per-region min/max bounds
- Alphanumeric — reject symbols
- Mix check — must have ≥1 letter AND ≥1 digit
- Run check — reject 4+ identical consecutive chars
- False-positive list — block common OCR noise words
- Region regex — country-specific pattern (when region is not .auto)
Region of interest
scanROI is a normalised rect (0–1, origin top-left) used to physically crop the pixel buffer before OCR. Only pixels inside this rectangle are passed to Vision — text outside is genuinely invisible to the engine.
// Default: 86% wide, 20% tall, vertically centred config.scanROI = CGRect(x:0.07, y:0.40, width:0.86, height:0.20)
Offline mode
All OCR inference runs on-device. Licence validation only needs internet once — on first activation. Results are cached; the SDK is fully offline thereafter.
Critical for parking garages, tunnels, and any environment where connectivity is unreliable.
Changelog
v1.1.0 — External Frame Input Latest
- Dedicated distribution for application-owned camera pipelines
- CMSampleBuffer and CVPixelBuffer frame input
- CGImage and UIImage still-image input
- Full-frame normalised detection bounding boxes
- Application retains complete ownership of frame storage and anonymisation
v1.0.0 — Camera-managed version
- Initial public release
- iOS SDK: AVFoundation + Vision, fully offline
- 7-stage plate validation with per-region patterns
- Pixel buffer ROI cropping
- 7 regions / 90+ country support
- SwiftUI PVCameraPreview + UIKit preview layer
- API key + secret activation with offline fallback
- Configurable confidence, deduplication, sound, ROI
- Single-shot captureOnce() mode
Support
- Email: support@platevision.io
- Get started: Create a free account
- GitHub: github.com/platevision
Pro & Enterprise: same-day response. Starter: 2–3 business days.