SimpleLPR
SimpleLPR is a comprehensive software library designed for automatic license plate recognition (LPR/ANPR) in both static images and real-time video streams. Built on years of real-world deployment experience, SimpleLPR combines optical character recognition with preprocessing algorithms to deliver reliable plate detection and reading capabilities across a wide range of challenging conditions.
What is SimpleLPR?
SimpleLPR is more than an OCR library - it's a complete license plate recognition system that handles the workflow from image acquisition to final text output. The library automatically detects license plates in images or video frames, extracts the plate region, performs perspective correction, and reads the alphanumeric characters with high accuracy.
Core Capabilities
- Automatic Plate Detection: Locates license plates in complex scenes without manual intervention
- Robust Character Recognition: Specialized OCR engine trained specifically for license plate fonts and formats
- Video Stream Processing: Real-time analysis of video files and live streams (RTSP, HTTP, etc.)
- Multi-threaded Architecture: Process multiple images or video streams concurrently for maximum throughput
- Temporal Tracking: Track plates across video frames to improve accuracy and reduce false readings
- Global Coverage: Pre-configured templates for approximately 90 countries worldwide
- Flexible Integration: Native APIs for C++, .NET, and Python with language-appropriate conventions
Real-World Performance
SimpleLPR achieves typical recognition rates of 85-95% under normal operating 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 from perpendicular
The library has been extensively tested in production environments including:
- Highway toll collection systems processing thousands of vehicles per hour
- Parking facilities with varying lighting conditions
- Access control gates with fixed camera positions
- Mobile enforcement units with handheld cameras
- Traffic monitoring systems with wide-angle views
Key Features
Image Processing
- Support for all major image formats (JPEG, PNG, TIFF, BMP)
- Direct processing from memory buffers
- Automatic contrast enhancement for poor lighting conditions
- Perspective distortion correction
- Multi-scale detection for plates at various distances
Video Processing
- Frame extraction from video files (AVI, MP4, MKV, etc.)
- Real-time RTSP stream analysis
- Configurable frame size limits for performance optimization
- Automatic reconnection for unreliable network streams
- Frame buffering and synchronization
Advanced Capabilities
- Processor Pools: Distribute work across multiple CPU cores for parallel processing
- Plate Tracking: Correlate detections across frames to eliminate transient misreads
- Confidence Scoring: Character-level and plate-level confidence metrics
- Region Detection: Precise plate boundary extraction with quadrilateral coordinates
- Country Validation: Verify plate formats against country-specific templates
- GPU Acceleration: Optional CUDA support for enhanced performance (SDK version)
Getting Started
System Requirements
- Operating Systems: Windows 10/11, Ubuntu 20.04+, other major Linux distributions
- Architecture: x86-64 (both 32-bit and 64-bit builds available)
- Memory: Minimum 2GB RAM, 4GB+ recommended for video processing
- .NET Requirements: .NET Standard 2.0 or higher
- Python: Version 3.8, 3.9, 3.10, 3.11, or 3.12 (64-bit)
Installation
SimpleLPR is distributed as a commercial SDK with a 60-day evaluation period. The SDK includes:
- Native libraries for C++ integration
- .NET assemblies with full IntelliSense support
- Python wheel packages for pip installation
- Comprehensive documentation and sample code
- Demo applications with source code
Documentation
Quick Start Example
Here's a comprehensive example demonstrating video processing with concurrent analysis and plate tracking:
using System;
using System.Collections.Generic;
using System.IO;
using SimpleLPR3;
namespace SimpleLPR_VideoTracking
{
class Program
{
static void Main(string[] args)
{
try
{
if (args.Length < 3 || args.Length > 4)
{
ShowUsage();
return;
}
string videoPath = args[0];
uint countryId = uint.Parse(args[1]);
string outputDir = args[2];
string productKey = args.Length > 3 ? args[3] : null;
if (!File.Exists(videoPath))
{
Console.WriteLine($"Error: Video file not found: {videoPath}");
return;
}
Directory.CreateDirectory(outputDir);
Console.WriteLine($"Results will be saved to: {Path.GetFullPath(outputDir)}");
ProcessVideo(videoPath, countryId, outputDir, productKey);
}
catch (Exception ex)
{
Console.WriteLine($"\nError: {ex.Message}");
if (ex.InnerException != null)
Console.WriteLine($"Details: {ex.InnerException.Message}");
}
}
static void ShowUsage()
{
Console.WriteLine("SimpleLPR Video Processing Demo");
Console.WriteLine("\nThis demo processes a video file and tracks license plates across frames.");
Console.WriteLine("\nUsage: SimpleLPR_VideoTracking <video_file> <country_id> <output_dir> [product_key]");
Console.WriteLine("\nParameters:");
Console.WriteLine(" video_file : Path to video file (MP4, AVI, etc.) or RTSP stream URL");
Console.WriteLine(" country_id : Numeric country identifier (see list below)");
Console.WriteLine(" output_dir : Directory for results and plate thumbnails");
Console.WriteLine(" product_key : Optional path to license key file");
Console.WriteLine("\nExample:");
Console.WriteLine(" SimpleLPR_VideoTracking traffic.mp4 9 ./results");
Console.WriteLine(" SimpleLPR_VideoTracking rtsp://camera.local:554/stream 52 ./detections key.xml");
Console.WriteLine("\nSupported Countries:");
var setupParams = new EngineSetupParms
{
cudaDeviceId = -1,
enableImageProcessingWithGPU = false,
enableClassificationWithGPU = false,
maxConcurrentImageProcessingOps = 1
};
using (var tempEngine = SimpleLPR.Setup(setupParams))
{
for (uint i = 0; i < tempEngine.numSupportedCountries; i++)
{
Console.WriteLine($" {i,3}: {tempEngine.get_countryCode(i)}");
}
}
}
static void ProcessVideo(string videoPath, uint countryId, string outputDir, string productKey)
{
Console.WriteLine("\n=== SimpleLPR Video Processing Demo ===\n");
var engineParams = new EngineSetupParms
{
cudaDeviceId = -1,
enableImageProcessingWithGPU = false,
enableClassificationWithGPU = false,
maxConcurrentImageProcessingOps = 3
};
using var lpr = SimpleLPR.Setup(engineParams);
var ver = lpr.versionNumber;
Console.WriteLine($"SimpleLPR Version: {ver.A}.{ver.B}.{ver.C}.{ver.D}");
if (!string.IsNullOrEmpty(productKey))
{
lpr.set_productKey(productKey);
Console.WriteLine("Product key loaded");
}
else
{
Console.WriteLine("Running in evaluation mode");
}
for (uint i = 0; i < lpr.numSupportedCountries; i++)
lpr.set_countryWeight(i, 0.0f);
lpr.set_countryWeight(countryId, 1.0f);
lpr.realizeCountryWeights();
string countryCode = lpr.get_countryCode(countryId);
Console.WriteLine($"Country configured: {countryCode} (ID: {countryId})");
using var pool = lpr.createProcessorPool((uint)engineParams.maxConcurrentImageProcessingOps);
pool.plateRegionDetectionEnabled = true;
var trackerParams = PlateCandidateTrackerSetupParms.Default;
Console.WriteLine("\nTracker configuration:");
Console.WriteLine($" Trigger window: {trackerParams.triggerWindowInSec} seconds");
Console.WriteLine($" Max idle time: {trackerParams.maxIdleTimeInSec} seconds");
Console.WriteLine($" Min detections: {trackerParams.minTriggerFrameCount} frames");
using var tracker = lpr.createPlateCandidateTracker(trackerParams);
const int maxWidth = 1920;
const int maxHeight = 1080;
Console.WriteLine($"\nOpening video source: {videoPath}");
using var video = lpr.openVideoSource(videoPath,
FrameFormat.FRAME_FORMAT_BGR24, maxWidth, maxHeight);
if (video.state != VideoSourceState.VIDEO_SOURCE_STATE_OPEN)
{
throw new Exception($"Failed to open video source. State: {video.state}");
}
Console.WriteLine($"Video type: {(video.isLiveSource ? "Live stream" : "File")}");
Console.WriteLine("\nProcessing frames... Press Ctrl+C to stop.\n");
var frameQueue = new Queue<IVideoFrame>();
int frameCount = 0;
int trackCount = 0;
var startTime = DateTime.Now;
IVideoFrame frame;
while ((frame = video.nextFrame()) != null)
{
frameCount++;
frameQueue.Enqueue(frame);
pool.launchAnalyze(
streamId: 0,
requestId: frame.sequenceNumber,
timeoutInMs: IProcessorPoolConstants.TIMEOUT_INFINITE,
frame: frame
);
ProcessCompletedResults(pool, tracker, frameQueue, outputDir, ref trackCount);
if (frameCount % 100 == 0)
{
var elapsed = DateTime.Now - startTime;
var fps = frameCount / elapsed.TotalSeconds;
Console.WriteLine($"Processed {frameCount} frames ({fps:F1} fps) - {trackCount} plates tracked");
}
}
Console.WriteLine("\nVideo processing complete. Waiting for final results...");
while (pool.get_ongoingRequestCount(0) > 0)
{
var result = pool.pollNextResult(0, IProcessorPoolConstants.TIMEOUT_INFINITE);
ProcessResult(result, tracker, frameQueue, outputDir, ref trackCount);
}
using var flushResult = tracker.flush();
ProcessTrackerResult(flushResult, outputDir, ref trackCount, -1.0);
while (frameQueue.Count > 0)
frameQueue.Dequeue().Dispose();
var totalTime = DateTime.Now - startTime;
Console.WriteLine("\n=== Processing Complete ===");
Console.WriteLine($"Total frames: {frameCount}");
Console.WriteLine($"Processing time: {totalTime.TotalSeconds:F1} seconds");
Console.WriteLine($"Average FPS: {frameCount / totalTime.TotalSeconds:F1}");
Console.WriteLine($"License plates tracked: {trackCount}");
Console.WriteLine($"Results saved to: {Path.GetFullPath(outputDir)}");
}
static void ProcessCompletedResults(IProcessorPool pool, IPlateCandidateTracker tracker,
Queue<IVideoFrame> frameQueue, string outputDir, ref int trackCount)
{
IProcessorPoolResult result;
while ((result = pool.pollNextResult(0, IProcessorPoolConstants.TIMEOUT_IMMEDIATE)) != null)
{
ProcessResult(result, tracker, frameQueue, outputDir, ref trackCount);
}
}
static void ProcessResult(IProcessorPoolResult result, IPlateCandidateTracker tracker,
Queue<IVideoFrame> frameQueue, string outputDir, ref int trackCount)
{
using (result)
{
var frame = frameQueue.Dequeue();
using (frame)
{
using var trackerResult = tracker.processFrameCandidates(
result.candidates, frame);
ProcessTrackerResult(trackerResult, outputDir, ref trackCount,
frame.timestamp);
}
}
}
static void ProcessTrackerResult(IPlateCandidateTrackerResult trackerResult,
string outputDir, ref int trackCount, double timestamp)
{
foreach (var track in trackerResult.NewTracks)
{
trackCount++;
var candidate = track.representativeCandidate;
if (candidate.matches.Count > 0)
{
var match = candidate.matches[0];
var timeStr = timestamp >= 0 ? $"{timestamp:F2}s" : "final";
Console.WriteLine($"[NEW PLATE] Frame {track.firstDetectionFrameId} @ {timeStr}: " +
$"{match.text} ({match.country}) - Confidence: {match.confidence:F3}");
if (track.representativeThumbnail != null)
{
string filename = $"plate_{trackCount:D4}_{match.text}.jpg";
string filepath = Path.Combine(outputDir, filename);
try
{
track.representativeThumbnail.saveAsJPEG(filepath, 95);
Console.WriteLine($" -> Saved thumbnail: {filename}");
}
catch (Exception ex)
{
Console.WriteLine($" → Failed to save thumbnail: {ex.Message}");
}
}
}
}
foreach (var track in trackerResult.ClosedTracks)
{
var candidate = track.representativeCandidate;
if (candidate.matches.Count > 0)
{
var match = candidate.matches[0];
var duration = track.newestDetectionTimestamp - track.firstDetectionTimestamp;
var frameSpan = track.newestDetectionFrameId - track.firstDetectionFrameId + 1;
Console.WriteLine($"[TRACK CLOSED] {match.text} - " +
$"Duration: {duration:F1}s ({frameSpan} frames)");
}
}
}
}
}
Architecture Considerations
Threading Model
SimpleLPR is designed for multi-threaded operation:
- The engine instance is thread-safe and can be shared
- Individual processors are NOT thread-safe (use one per thread)
- Processor pools handle thread management automatically
- Video sources should be accessed from a single thread
Memory Management
- All SimpleLPR objects implement IDisposable - use
using
statements
- Video frames should be disposed after processing
- Processor pools manage their own memory efficiently
- Consider frame size caps for high-resolution video
Performance Optimization
- Country Selection: Enable only expected countries for faster processing
- Processor Count: Set based on CPU cores (typically cores - 1)
- Frame Size: Cap at the minimum resolution that maintains accuracy
- GPU Acceleration: Use CUDA-enabled builds for 2-3x speedup
- Plate Tracking: Reduces false positives without significant overhead
Common Use Cases
Parking Management
- Entry/exit gate control with fixed cameras
- Space occupancy monitoring
- Permit verification
- Duration tracking for billing
Toll Collection
- High-speed highway toll processing
- Multi-lane free-flow systems
- Violation enforcement
- Cross-plaza journey time analysis
Access Control
- Gated community entry systems
- Corporate campus security
- VIP list verification
- Visitor management
Law Enforcement
- Stolen vehicle alerts
- Warrant plate matching
- Traffic violation recording
- Investigative plate searches
Smart City Applications
- Traffic flow analysis
- Congestion pricing
- Parking guidance systems
- Environmental zone enforcement
Support and Resources
Technical Support
- Email: support@warelogic.com
- Include version information and error logs
- Provide sample images/videos when possible
Licensing
- 60-day evaluation period included
- One-time license purchase - no recurring fees
- Unlimited, royalty-free redistribution rights
- Include SimpleLPR runtime with your applications
- One year of technical support and updates included
- Contact support@warelogic.com for pricing
Updates
SimpleLPR is actively maintained with regular updates:
- New country template additions
- OCR accuracy improvements
- Performance optimizations
- Bug fixes and stability enhancements
SimpleLPR has been helping developers implement reliable license plate recognition since 2009.