SimpleLPR 3.6.0

dotnet add package SimpleLPR --version 3.6.0
                    
NuGet\Install-Package SimpleLPR -Version 3.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SimpleLPR" Version="3.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SimpleLPR" Version="3.6.0" />
                    
Directory.Packages.props
<PackageReference Include="SimpleLPR" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SimpleLPR --version 3.6.0
                    
#r "nuget: SimpleLPR, 3.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=SimpleLPR&version=3.6.0
                    
Install SimpleLPR as a Cake Addin
#tool nuget:?package=SimpleLPR&version=3.6.0
                    
Install SimpleLPR as a Cake Tool

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
            {
                // Parse command line arguments
                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;

                // Validate inputs
                if (!File.Exists(videoPath))
                {
                    Console.WriteLine($"Error: Video file not found: {videoPath}");
                    return;
                }

                // Create output directory
                Directory.CreateDirectory(outputDir);
                Console.WriteLine($"Results will be saved to: {Path.GetFullPath(outputDir)}");

                // Process the video
                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");

            // Display supported countries
            Console.WriteLine("\nSupported Countries:");
            
            // Create temporary engine to list countries
            var setupParams = new EngineSetupParms
            {
                cudaDeviceId = -1,  // CPU mode
                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");

            // Initialize SimpleLPR engine
            // The engine is the main entry point for all SimpleLPR functionality
            var engineParams = new EngineSetupParms
            {
                cudaDeviceId = -1,                     // Use CPU (-1). Set 0+ for GPU if available
                enableImageProcessingWithGPU = false,   // GPU processing requires CUDA
                enableClassificationWithGPU = false,    // GPU classification requires CUDA
                maxConcurrentImageProcessingOps = 3     // Number of parallel processing threads
            };

            using var lpr = SimpleLPR.Setup(engineParams);

            // Display version information
            var ver = lpr.versionNumber;
            Console.WriteLine($"SimpleLPR Version: {ver.A}.{ver.B}.{ver.C}.{ver.D}");

            // Supply product key if provided
            // Without a valid key, SimpleLPR runs in evaluation mode (60 days)
            if (!string.IsNullOrEmpty(productKey))
            {
                lpr.set_productKey(productKey);
                Console.WriteLine("Product key loaded");
            }
            else
            {
                Console.WriteLine("Running in evaluation mode");
            }

            // Configure country recognition
            // SimpleLPR can detect plates from ~90 countries
            // For best results, enable only the countries you expect to encounter
            
            // First, disable all countries
            for (uint i = 0; i < lpr.numSupportedCountries; i++)
                lpr.set_countryWeight(i, 0.0f);

            // Enable only the selected country with full weight
            lpr.set_countryWeight(countryId, 1.0f);
            
            // Apply the country configuration
            lpr.realizeCountryWeights();

            string countryCode = lpr.get_countryCode(countryId);
            Console.WriteLine($"Country configured: {countryCode} (ID: {countryId})");

            // Create processor pool for concurrent frame processing
            // The pool manages multiple processor instances for parallel analysis
            using var pool = lpr.createProcessorPool((uint)engineParams.maxConcurrentImageProcessingOps);
            
            // Enable plate region detection
            // This improves accuracy by identifying the exact plate boundaries
            pool.plateRegionDetectionEnabled = true;

            // Create plate tracker
            // The tracker correlates plate detections across multiple frames
            // This reduces false positives and handles temporary occlusions
            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);

            // Open video source
            // SimpleLPR supports video files and live streams (RTSP, HTTP, etc.)
            // Frame size caps help manage memory usage and processing time
            const int maxWidth = 1920;   // Full HD width
            const int maxHeight = 1080;  // Full HD height
            
            Console.WriteLine($"\nOpening video source: {videoPath}");
            using var video = lpr.openVideoSource(videoPath, 
                FrameFormat.FRAME_FORMAT_BGR24, maxWidth, maxHeight);

            // Check if source opened successfully
            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");

            // Frame queue to synchronize frames with their processing results
            // This is necessary because processing is asynchronous
            var frameQueue = new Queue<IVideoFrame>();
            
            // Statistics
            int frameCount = 0;
            int trackCount = 0;
            var startTime = DateTime.Now;

            // Main processing loop
            IVideoFrame frame;
            while ((frame = video.nextFrame()) != null)
            {
                frameCount++;
                
                // Keep frame reference until its result is ready
                frameQueue.Enqueue(frame);

                // Submit frame for asynchronous processing
                // The pool will use the next available processor
                pool.launchAnalyze(
                    streamId: 0,                              // Stream identifier (for multi-stream scenarios)
                    requestId: frame.sequenceNumber,          // Unique ID to match results with frames
                    timeoutInMs: IProcessorPoolConstants.TIMEOUT_INFINITE,  // Wait for available processor
                    frame: frame
                );

                // Check for completed results
                // This is non-blocking, so we can continue processing new frames
                ProcessCompletedResults(pool, tracker, frameQueue, outputDir, ref trackCount);

                // Show progress every 100 frames
                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...");

            // Process any remaining results in the pipeline
            while (pool.get_ongoingRequestCount(0) > 0)
            {
                // TIMEOUT_INFINITE means this will block until a result is available
                var result = pool.pollNextResult(0, IProcessorPoolConstants.TIMEOUT_INFINITE);
                ProcessResult(result, tracker, frameQueue, outputDir, ref trackCount);
            }

            // Flush the tracker to close any remaining active tracks
            // This ensures we don't miss plates that were still being tracked
            using var flushResult = tracker.flush();
            ProcessTrackerResult(flushResult, outputDir, ref trackCount, -1.0);

            // Clean up any remaining frames
            while (frameQueue.Count > 0)
                frameQueue.Dequeue().Dispose();

            // Final statistics
            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)
        {
            // Poll for all available results without blocking
            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)
            {
                // Match the result with its corresponding frame
                var frame = frameQueue.Dequeue();
                using (frame)
                {
                    // Let the tracker process the detection results
                    // Even frames without detections are important for tracking timing
                    using var trackerResult = tracker.processFrameCandidates(
                        result.candidates, frame);

                    // Process the tracking results
                    ProcessTrackerResult(trackerResult, outputDir, ref trackCount, 
                        frame.timestamp);
                }
            }
        }

        static void ProcessTrackerResult(IPlateCandidateTrackerResult trackerResult, 
            string outputDir, ref int trackCount, double timestamp)
        {
            // Process newly created tracks
            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}");

                    // Save thumbnail if available
                    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}");
                        }
                    }
                }
            }

            // Process closed tracks (plates that are no longer visible)
            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

  1. Country Selection: Enable only expected countries for faster processing
  2. Processor Count: Set based on CPU cores (typically cores - 1)
  3. Frame Size: Cap at the minimum resolution that maintains accuracy
  4. GPU Acceleration: Use CUDA-enabled builds for 2-3x speedup
  5. 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.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.6.0 220 6/9/2025
3.5.10 242 5/4/2025
3.5.9 2,850 2/19/2025
3.5.8 1,362 12/28/2024
3.5.7 19,336 8/1/2024
3.5.6 4,950 6/3/2024
3.5.5 9,630 6/30/2023
3.5.4 1,658 5/26/2023
3.5.3 1,841 3/14/2023
3.5.2 1,667 2/28/2023
3.5.1 1,641 1/5/2023
3.5.0 1,335 11/27/2022

Version 3.6.0 - Major Release
• IPlateCandidateTracker class for tracking license plates across multiple video frames (now stable)
• Added comprehensive samples in C#, Python, and C++ demonstrating plate tracking functionality
• New interactive WPF sample application with real-time display of captured plates
• Updated Russia template to support three-digit region codes (1xx, 2xx, 3xx, 5xx, 6xx, 7xx, 9xx)
• Enhanced Azerbaijan template with diplomatic, motorcycle, and newer format support
• Various performance improvements and bug fixes