PlateVision SDK

Real-time, offline license plate recognition for iOS, Android & Flutter.

🍎 iOS 16+ 🤖 Android 8+ 💙 Flutter 3+ ✈️ Offline 🌎 90+ Countries

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.

  1. Get your credentials

    Sign up and copy your API key and secret from the dashboard.

  2. Build and add the framework

    Run ./build.sh from the Framework folder, drag the resulting PlateVisionSDK.xcframework into Xcode, set Embed & Sign.

  3. Add camera permission

    Add NSCameraUsageDescription to Info.plist.

  4. Activate and scan
swift
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.

CredentialFormatNotes
apiKeypv_live_...Identifies your app. Safe to bundle in the binary.
secretKeysk_...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).

bash — build the framework
cd PlateVisionSDK/Framework
chmod +x build.sh && ./build.sh
# Outputs: build/PlateVisionSDK.xcframework

Then drag PlateVisionSDK.xcframework into Xcode → set Embed & Sign → add NSCameraUsageDescription to Info.plist.

Swift Package Manager

Package.swift
.package(url: "https://github.com/platevision/platevision-ios-sdk", from: "1.0.0")

Activation

swift
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

swift
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)
PropertyTypeDefaultDescription
regionPVRegion.autoCountry hint for validation.
minimumConfidenceFloat0.60OCR threshold (0–1).
deduplicationWindowTimeInterval2.0 sSuppress duplicate detections.
enableSoundBooltrueSystem tick on each hit.
scanROICGRectcentre 86×20%Normalised pixel-buffer crop rect.

Delegate

swift
public protocol PlateVisionDelegate: AnyObject {
    func plateVision(_ sdk: PlateVisionScanner, didDetect result: PVDetectionResult)
    func plateVision(_ sdk: PlateVisionScanner, didChangeState state: PVState)
    func plateVisionDidFailLicence(_ sdk: PlateVisionScanner)
}
PVDetectionResultTypeDescription
plateTextStringUppercased stripped plate number
confidenceFloatOCR confidence 0–1
confidencePercentIntconfidence × 100
boundingBoxCGRectNormalised Vision rect within scan ROI
timestampDateTime of detection
regionPVRegionActive region at time of detection

Camera UI

swift — SwiftUI
ZStack {
    PVCameraPreview(sdk: .shared).ignoresSafeArea()
    // overlay: scan guide, result card, controls...
}
swift — UIKit
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:

swift — AVCaptureVideoDataOutputSampleBufferDelegate
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

swift
PlateVisionScanner.shared.process(image: image) { result in
    if case .success(let detections) = result {
        let best = detections.first
        print(best?.plateText ?? "No plate")
    }
}
Input APIUse 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.

Download the External Frame Input evaluation package →

Full API Reference

Method / PropertyDescription
activate(apiKey:secretKey:completion:)Validate credentials. Once per launch.
licenceValidationURLValidation 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.
delegatePlateVisionDelegate receiver.
state.idle / .running / .paused / .permissionDenied / .error
previewLayerAVCaptureVideoPreviewLayer (set after startScanning).
licenceInfoPlateVisionLicence (set after activation).

Android SDKAndroid

build.gradle.kts
dependencies { implementation("io.platevision:sdk-android:1.0.0") }
kotlin
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

pubspec.yaml
dependencies:
  platevision_sdk: ^1.0.0
dart
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

RegionEnumLengthExample
Auto (global).auto5–10Any valid plate
US / Canada.us5–8ABC1234, 353-42T
United Kingdom.uk5–7AB12CDE
Europe.europe5–8AB1234CD
Australia.australia5–7ABC123
India.india8–10MH01AB1234
Middle East.middleEast5–8ABC12345

Plate validation pipeline

  1. Clean — uppercase, strip spaces, dashes, dots
  2. Length — per-region min/max bounds
  3. Alphanumeric — reject symbols
  4. Mix check — must have ≥1 letter AND ≥1 digit
  5. Run check — reject 4+ identical consecutive chars
  6. False-positive list — block common OCR noise words
  7. 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.

swift
// 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

Pro & Enterprise: same-day response. Starter: 2–3 business days.