
Security News
Browserslist-rs Gets Major Refactor, Cutting Binary Size by Over 1MB
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
š 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.
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.
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:
SimpleLPR achieves industry-leading accuracy under typical conditions:
The library has been extensively tested in production environments including:
pip install SimpleLPR
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}")
SimpleLPR supports approximately 90 countries. Use --list-countries
in the demo below to see all options.
Popular countries:
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()
Enable concurrent processing of multiple frames for better throughput:
pool = engine.createProcessorPool(3) # 3 parallel processors
pool.plateRegionDetectionEnabled = True # Better accuracy
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)
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()
SimpleLPR employs a multi-stage processing pipeline:
SimpleLPR is commercial software with a 60-day evaluation period. Production licenses include:
Contact support@warelogic.com for pricing.
š SimpleLPR - Professional license plate recognition for Python developers
FAQs
SimpleLPR License Plate Recognition (LPR/ANPR) library.
We found that SimpleLPR demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
Research
Security News
Eight new malicious Firefox extensions impersonate games, steal OAuth tokens, hijack sessions, and exploit browser permissions to spy on users.
Security News
The official Go SDK for the Model Context Protocol is in development, with a stable, production-ready release expected by August 2025.