SimpleLPR License Plate Recognition (LPR/ANPR) library.
Project description
SimpleLPR Python
๐ High-performance license plate recognition for Python developers. Detect and read vehicle license plates from images and video streams with just a few lines of code.
๐ฏ Overview
SimpleLPR brings professional-grade license plate recognition to Python. Whether you're building a parking management system, analyzing traffic patterns, or developing security applications, SimpleLPR handles the complexity of plate detection and OCR so you can focus on your application logic.
Built on over 10 years of continuous development and deployed in thousands of production systems worldwide, SimpleLPR delivers reliable performance with a developer-friendly API.
โจ Key Features
- ๐ High Accuracy: 85-95% recognition rate under typical conditions
- โก Real-time Processing: Analyze video streams with multi-threaded performance
- ๐ Global Coverage: Support for ~90 countries with region-specific formats
- ๐ Video Tracking: Track plates across frames for improved accuracy
- ๐ Pythonic API: Native Python interface that feels natural
- ๐ญ Production Ready: Battle-tested in commercial deployments worldwide
๐ What is SimpleLPR?
SimpleLPR is more than just an OCR library - it's a complete license plate recognition system that handles the entire workflow from image acquisition to final text output. The library automatically:
- Detects license plates in complex scenes
- Extracts the plate region with precise boundaries
- Corrects perspective and lighting issues
- Reads alphanumeric characters with high accuracy
- Validates results against country-specific formats
- Tracks plates across video frames for consistency
๐ฏ Real-World Performance
SimpleLPR achieves industry-leading accuracy under typical conditions:
- Plate text height of at least 20 pixels
- Reasonable image quality without severe motion blur
- Plates in good physical condition
- Viewing angles within ยฑ30 degrees
The library has been extensively tested in production environments including:
- ๐ ฟ๏ธ Parking facilities with varying lighting conditions
- ๐ฃ๏ธ Highway toll collection systems processing thousands of vehicles
- ๐ช Access control gates with fixed camera positions
- ๐ฑ Mobile enforcement units with handheld cameras
- ๐น Traffic monitoring systems with wide-angle views
๐ป Requirements
- Python: 3.8, 3.9, 3.10, 3.11, or 3.12 (64-bit)
- OS: Windows x64, Linux x64 (Ubuntu 20.04+)
- Memory: 2GB RAM minimum, 4GB+ recommended for video
- License: 60-day trial included, production licenses available
๐ฆ Installation
pip install SimpleLPR
๐ Quick Start
Basic Image Recognition
import simplelpr
# Initialize the engine
setup_params = simplelpr.EngineSetupParms()
engine = simplelpr.SimpleLPR(setup_params)
# Configure for your country (e.g., UK = 90)
engine.set_countryWeight(90, 1.0)
engine.realizeCountryWeights()
# Create a processor
processor = engine.createProcessor()
# Analyze an image
candidates = processor.analyze("car_image.jpg")
# Print results
for candidate in candidates:
for match in candidate.matches:
print(f"Plate: {match.text}")
print(f"Confidence: {match.confidence:.3f}")
๐ Country Configuration
SimpleLPR supports approximately 90 countries. Use --list-countries in the demo below to see all options.
Popular countries:
- ๐ฆ๐น Austria: 5
- ๐ง๐ท Brazil: 12
- ๐จ๐ฆ Canada: 16
- ๐ซ๐ท France: 32
- ๐ฉ๐ช Germany: 34
- ๐ฎ๐ณ India: 41
- ๐ฎ๐น Italy: 45
- ๐ช๐ธ Spain: 82
- ๐ฌ๐ง UK: 90
๐ฌ Complete Video Tracking Demo
This demo shows how to process video files or RTSP streams with real-time plate tracking:
#!/usr/bin/env python3
"""
SimpleLPR Video Tracking Demo
Track license plates across video frames with temporal correlation.
This improves accuracy by reducing transient misreads.
Usage:
python tracking_demo.py <video_source> <country_id> [product_key]
python tracking_demo.py --list-countries
Examples:
python tracking_demo.py traffic.mp4 82
python tracking_demo.py rtsp://camera:554/stream 90 license.xml
"""
import sys
import os
import argparse
import time
from datetime import datetime
from collections import deque
try:
import simplelpr
except ImportError:
print("โ Error: SimpleLPR is not installed. Run 'pip install SimpleLPR'")
sys.exit(1)
def list_supported_countries():
"""Display all supported countries"""
print("\n๐ SimpleLPR - Supported Countries")
print("="*50)
setup_params = simplelpr.EngineSetupParms()
engine = simplelpr.SimpleLPR(setup_params)
# Show version
ver = engine.versionNumber
print(f"๐ Version: {ver.A}.{ver.B}.{ver.C}.{ver.D}\n")
print("ID : Country")
print("-"*30)
for i in range(engine.numSupportedCountries):
print(f"{i:3d} : {engine.get_countryCode(i)}")
print("\n๐ก Use either the country name or ID as the country_id parameter.")
def configure_country(engine, country_id):
"""Configure engine for specific country"""
# Disable all countries first
for i in range(engine.numSupportedCountries):
engine.set_countryWeight(i, 0.0)
# Try as string first, then as integer
country_name = None
if isinstance(country_id, str):
try:
engine.set_countryWeight(country_id, 1.0)
country_name = country_id
except:
# Try as integer
try:
country_idx = int(country_id)
if 0 <= country_idx < engine.numSupportedCountries:
engine.set_countryWeight(country_idx, 1.0)
country_name = engine.get_countryCode(country_idx)
else:
raise ValueError(f"Country ID {country_idx} out of range")
except ValueError:
print(f"\nโ Error: Invalid country '{country_id}'")
print("๐ก Run with --list-countries to see valid options")
sys.exit(1)
else:
# Integer country ID
engine.set_countryWeight(country_id, 1.0)
country_name = engine.get_countryCode(country_id)
engine.realizeCountryWeights()
return country_name
def process_tracker_result(tracker_result, track_count):
"""Process and display tracker results"""
# Process new tracks
for track in tracker_result.newTracks:
track_count += 1
if track.representativeCandidate.matches:
match = track.representativeCandidate.matches[0]
print(f"๐ [NEW #{track_count:03d}] {match.text:12} "
f"Country: {match.country} "
f"Confidence: {match.confidence:.3f} "
f"Frame: {track.firstDetectionFrameId}")
# Process closed tracks
for track in tracker_result.closedTracks:
if track.representativeCandidate.matches:
match = track.representativeCandidate.matches[0]
duration = (track.newestDetectionTimestamp -
track.firstDetectionTimestamp)
frames = (track.newestDetectionFrameId -
track.firstDetectionFrameId + 1)
print(f"โ
[CLOSED] {match.text:12} "
f"Duration: {duration:.1f}s ({frames} frames)")
return track_count
def process_pending_results(proc_pool, tracker, frame_queue, track_count):
"""Process all pending results from the pool"""
while True:
result = proc_pool.pollNextResult(0, simplelpr.TIMEOUT_IMMEDIATE)
if result is None:
break
if frame_queue:
orig_frame = frame_queue.popleft()
tracker_result = tracker.processFrameCandidates(
result, orig_frame
)
track_count = process_tracker_result(tracker_result, track_count)
return track_count
def process_video(video_path, country_id, product_key=None):
"""Main video processing function"""
print("\n๐ SimpleLPR Video Tracking Demo")
print("="*50)
# Initialize engine
setup_params = simplelpr.EngineSetupParms()
setup_params.maxConcurrentImageProcessingOps = 0 # Let the ANPR engine decide
engine = simplelpr.SimpleLPR(setup_params)
# Show version
ver = engine.versionNumber
print(f"๐ Version: {ver.A}.{ver.B}.{ver.C}.{ver.D}")
# Load product key if provided
if product_key:
try:
engine.set_productKey(product_key)
print("๐ Product key loaded")
except:
print("โ ๏ธ Warning: Failed to load product key")
else:
print("๐ Running in evaluation mode")
# Configure country
country_name = configure_country(engine, country_id)
print(f"๐ Country: {country_name}")
# Create processor pool
proc_pool = engine.createProcessorPool(3) # 3 processors in the pool
proc_pool.plateRegionDetectionEnabled = True
# Create tracker
tracker_params = simplelpr.PlateCandidateTrackerSetupParms()
tracker = engine.createPlateCandidateTracker(tracker_params)
print(f"๐ Tracker: {tracker_params.minTriggerFrameCount} min frames, "
f"{tracker_params.maxIdleTimeInSec}s max idle")
# Open video
print(f"\n๐น Opening: {video_path}")
video = engine.openVideoSource(
video_path,
simplelpr.FrameFormat.FRAME_FORMAT_BGR24,
1920, 1080 # Max resolution
)
if video.state != simplelpr.VideoSourceState.VIDEO_SOURCE_STATE_OPEN:
print(f"โ Error: Failed to open video (state: {video.state})")
sys.exit(1)
print(f"๐ก Type: {'Live stream' if video.isLiveSource else 'Video file'}")
print("\nโถ๏ธ Processing... Press Ctrl+C to stop\n")
# Processing variables
frame_queue = deque()
frame_count = 0
track_count = 0
start_time = time.time()
try:
# Main processing loop
while True:
# Get next frame
frame = video.nextFrame()
if frame is None:
if video.isLiveSource:
print("๐ Stream interrupted, reconnecting...")
video.reconnect()
time.sleep(0.1)
continue
else:
break # End of file
frame_count += 1
frame_queue.append(frame)
# Submit for processing
success = proc_pool.launchAnalyze(
0, # Stream ID
frame.sequenceNumber,
simplelpr.TIMEOUT_INFINITE,
frame
)
if not success:
frame_queue.pop()
continue
# Process all available results
track_count = process_pending_results(
proc_pool, tracker, frame_queue, track_count
)
# Progress update
if frame_count % 100 == 0:
elapsed = time.time() - start_time
fps = frame_count / elapsed
print(f"๐ [Progress] {frame_count} frames @ {fps:.1f} fps")
# Process remaining results
print("\nโณ Finalizing...")
while proc_pool.ongoingRequestCount_get(0) > 0:
result = proc_pool.pollNextResult(0, 100) # 100ms timeout
if result and frame_queue:
orig_frame = frame_queue.popleft()
tracker_result = tracker.processFrameCandidates(
result, orig_frame
)
track_count = process_tracker_result(tracker_result, track_count)
# Final tracker flush
final_result = tracker.flush()
for track in final_result.closedTracks:
if track.representativeCandidate.matches:
match = track.representativeCandidate.matches[0]
print(f"๐ [FINAL] {match.text}")
except KeyboardInterrupt:
print("\n\nโ ๏ธ Stopped by user")
# Statistics
elapsed = time.time() - start_time
print(f"\n{'='*50}")
print(f"โ
Processing Complete!")
print(f"๐ Frames processed: {frame_count:,}")
print(f"๐ Plates tracked: {track_count}")
print(f"โฑ๏ธ Processing time: {elapsed:.1f}s")
print(f"โก Average speed: {frame_count/elapsed:.1f} fps")
def main():
parser = argparse.ArgumentParser(
description="SimpleLPR Video Tracking Demo",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
๐ Examples:
%(prog)s traffic.mp4 82 # Spain
%(prog)s video.avi France # Using country name
%(prog)s rtsp://camera:554 90 key.xml # UK with license
๐ก Tips:
- Use --list-countries to see all supported regions
- Country can be specified by name or ID number
- RTSP streams will reconnect automatically
"""
)
parser.add_argument("video_source", nargs='?',
help="Video file path or RTSP stream URL")
parser.add_argument("country_id", nargs='?',
help="Country name (e.g., 'Spain') or ID (0-102)")
parser.add_argument("product_key", nargs='?',
help="License key file path (optional)")
parser.add_argument("--list-countries", action="store_true",
help="Show all supported countries and exit")
args = parser.parse_args()
if args.list_countries:
list_supported_countries()
return
if not args.video_source or not args.country_id:
parser.print_help()
print("\n๐ก Tip: Use --list-countries to see supported regions")
sys.exit(1)
# Process video
process_video(args.video_source, args.country_id, args.product_key)
if __name__ == "__main__":
main()
๐ง Key Concepts
๐ Processor Pools
Enable concurrent processing of multiple frames for better throughput:
pool = engine.createProcessorPool(3) # 3 parallel processors
pool.plateRegionDetectionEnabled = True # Better accuracy
๐ฏ Plate Tracking
The tracker correlates detections across frames, eliminating transient misreads:
tracker_params = simplelpr.PlateCandidateTrackerSetupParms()
tracker_params.minTriggerFrameCount = 3 # Frames needed to confirm
tracker_params.maxIdleTimeInSec = 2.0 # Time before closing track
tracker = engine.createPlateCandidateTracker(tracker_params)
๐น Video Sources
SimpleLPR handles both files and live streams transparently:
# Video file
video = engine.openVideoSource("traffic.mp4", ...)
# RTSP stream
video = engine.openVideoSource("rtsp://camera:554/stream", ...)
# Auto-reconnect for streams
if video.isLiveSource:
video.reconnect()
๐๏ธ Architecture
SimpleLPR employs a multi-stage processing pipeline:
- ๐ผ๏ธ Image Preprocessing: Noise reduction, contrast enhancement
- ๐ Plate Detection: Locate potential plate regions
- ๐ Perspective Correction: Transform skewed plates to frontal view
- โ๏ธ Character Segmentation: Identify individual character boundaries
- ๐ค OCR Recognition: Classify characters using specialized neural networks
- โ Format Validation: Match against country-specific templates
- ๐ Tracking: Correlate detections across frames for consistency
๐ก Common Use Cases
๐ ฟ๏ธ Parking Management
- Entry/exit gate control
- Space occupancy monitoring
- Permit verification
- Duration tracking
๐ฃ๏ธ Toll Collection
- High-speed highway processing
- Multi-lane free-flow systems
- Violation enforcement
๐จ Security & Access Control
- Gated community entry
- Corporate campus security
- VIP list verification
- Visitor management
๐ Analytics & Smart City
- Traffic flow analysis
- Journey time calculation
- Congestion monitoring
- Environmental zone enforcement
๐ Documentation
- ๐ User Guide - Complete documentation
- ๐ Python API Reference - Detailed API docs
- ๐ Python Quick Start - Get started fast
- ๐ป GitHub Examples - More sample code
๐ค Support
- ๐ง Email: support@warelogic.com
- ๐ Website: https://www.warelogic.com
๐ License
SimpleLPR is commercial software with a 60-day evaluation period. Production licenses include:
- โ One-time payment - no recurring fees
- โ Unlimited deployments
- โ Royalty-free redistribution
- โ 1 year of updates and support
Contact support@warelogic.com for pricing.
๐ SimpleLPR - Professional license plate recognition for Python developers
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simplelpr-3.6.3-cp314-cp314-win_amd64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp314-cp314-win_amd64.whl
- Upload date:
- Size: 72.7 MB
- Tags: CPython 3.14, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be1dfe8198b76e3485b2e6533ff92791274b5c1c7dc1dffa29e46a01230dff3d
|
|
| MD5 |
dd00ef53ab4827d36243e0d5bcbc28cb
|
|
| BLAKE2b-256 |
a6f1e278221f52dd21dc534c313c67821e0ad9f794ea43e874c46182693a4c1c
|
File details
Details for the file simplelpr-3.6.3-cp314-cp314-manylinux_2_31_x86_64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp314-cp314-manylinux_2_31_x86_64.whl
- Upload date:
- Size: 135.9 MB
- Tags: CPython 3.14, manylinux: glibc 2.31+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e01efd51b781b54778dfab381c53e62b57181a0e2495c0b1fc227dd42e51f67
|
|
| MD5 |
e564405fc85ea09fd25445b4be9a6c25
|
|
| BLAKE2b-256 |
13061e797c9cf0d2462165b4344571af31cf31e2a77b2e3535a11e75b916c12c
|
File details
Details for the file simplelpr-3.6.3-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 72.7 MB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6d25a8b913d22e994e6cd188557913b5019dcd7fcf8298aa2c64665c6265a47
|
|
| MD5 |
4dc8027dbb7a4eddd75ed514e0a500fe
|
|
| BLAKE2b-256 |
2d6967eff09170152245907d0c18de7fad0e3788be8bee0c5451d1772baa40c8
|
File details
Details for the file simplelpr-3.6.3-cp313-cp313-manylinux_2_31_x86_64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp313-cp313-manylinux_2_31_x86_64.whl
- Upload date:
- Size: 135.9 MB
- Tags: CPython 3.13, manylinux: glibc 2.31+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c7b96e99a2477b7546ebda361b1cc17d6c23c452461b952df67c4c9f46c1501
|
|
| MD5 |
3d83f4c85e2335567cdb88b4a7682dcd
|
|
| BLAKE2b-256 |
0630e16662c4e05fdae8ca6e539ff6232440f19cbd73447ab81a023af08a131c
|
File details
Details for the file simplelpr-3.6.3-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 72.7 MB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97692f1950610b0e517a16575b20060508e38ff7e8be2de3d9bbffc2de0d2e2b
|
|
| MD5 |
bb56c29ddf9bdc32dbe279620c2706b1
|
|
| BLAKE2b-256 |
1ffd45417ad0c0ae2eea6d0101f93b6e3931ec70a3f0ada711ddcd499c6ea508
|
File details
Details for the file simplelpr-3.6.3-cp312-cp312-manylinux_2_31_x86_64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp312-cp312-manylinux_2_31_x86_64.whl
- Upload date:
- Size: 135.9 MB
- Tags: CPython 3.12, manylinux: glibc 2.31+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7ec8708c43befdab93389e5d4c3cb85fd3b09c0ed242b52aec8752ed336b792
|
|
| MD5 |
176e9edf13b2328334ae4dc6551afd44
|
|
| BLAKE2b-256 |
0d7c6627e3f47c9101a8c7c42c80091c55ee6dbf7d5c8c9a7ac56edca735d73e
|
File details
Details for the file simplelpr-3.6.3-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp311-cp311-win_amd64.whl
- Upload date:
- Size: 72.7 MB
- Tags: CPython 3.11, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ed0fa29e0e84b128e99ef9ba79a81006f01f268b6222aad5d3887bf3f6ae591
|
|
| MD5 |
80c73299f3630e03b3f10dec33009131
|
|
| BLAKE2b-256 |
dd76de8a7af3f68e0ecaa53e009ff46f86e2dd520ac466794d7acbfc63a56294
|
File details
Details for the file simplelpr-3.6.3-cp311-cp311-manylinux_2_31_x86_64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp311-cp311-manylinux_2_31_x86_64.whl
- Upload date:
- Size: 135.9 MB
- Tags: CPython 3.11, manylinux: glibc 2.31+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d1546224b3b9562e059473bb3f277c34224505238fc25fc176b9194b09141e80
|
|
| MD5 |
ec0e25f799eb5cf12e2f34f72719d0d8
|
|
| BLAKE2b-256 |
54d6c327067658e848bf49fcab54da5f1dd16d694eb7463ce76718f4f56a97da
|
File details
Details for the file simplelpr-3.6.3-cp310-cp310-win_amd64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp310-cp310-win_amd64.whl
- Upload date:
- Size: 72.7 MB
- Tags: CPython 3.10, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
474606664110ab8cf9d45f233fafcd494a8fac45de097d94d2a770e287880132
|
|
| MD5 |
62b23f64c615d0d19868e8f001b686aa
|
|
| BLAKE2b-256 |
91c993697629ac1d810453e8f3290010ceff94e18d5227567c6d5fa793caa48f
|
File details
Details for the file simplelpr-3.6.3-cp310-cp310-manylinux_2_31_x86_64.whl.
File metadata
- Download URL: simplelpr-3.6.3-cp310-cp310-manylinux_2_31_x86_64.whl
- Upload date:
- Size: 135.9 MB
- Tags: CPython 3.10, manylinux: glibc 2.31+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a38dc80ee9a610c0708c2c509fc897ef1e6892fea4dd79bab34bf83e0e19869e
|
|
| MD5 |
d4767eb2eb5dc09211c3561fb1640eef
|
|
| BLAKE2b-256 |
83b9c25c024c1de9f32939a94559daf43bbf20eca4df7006e67217d69ab6be94
|