dotnetANPR 0.1.0
dotnet add package dotnetANPR --version 0.1.0
NuGet\Install-Package dotnetANPR -Version 0.1.0
<PackageReference Include="dotnetANPR" Version="0.1.0" />
<PackageVersion Include="dotnetANPR" Version="0.1.0" />
<PackageReference Include="dotnetANPR" />
paket add dotnetANPR --version 0.1.0
#r "nuget: dotnetANPR, 0.1.0"
#:package dotnetANPR@0.1.0
#addin nuget:?package=dotnetANPR&version=0.1.0
#tool nuget:?package=dotnetANPR&version=0.1.0
dotnetANPR
Cross-platform automatic number plate recognition (ANPR) for .NET. dotnetANPR locates licence plates in car photos, segments individual characters, classifies them, and optionally corrects the result against known plate formats.
This project is a .NET port of javaanpr by Ondrej Martinsky, preserving the original pipeline of histogram-based region detection, heuristic filtering, and pattern-based character recognition.
Table of contents
- Features
- Project status
- Requirements
- Getting started
- Usage
- How it works
- API reference
- Configuration
- Configuration reference
- Syntax configuration reference
- Development
- Contributing
- Acknowledgements
- License
Features
- End-to-end recognition — from a full car image to a plate string in one call
- Two classifiers — KNN pattern matching (default) or feed-forward neural network
- Syntax correction — post-processing against configurable country/format templates
- Skew correction — optional Hough-transform de-skewing for rotated plates
- Cross-platform —
netstandard2.0library with SkiaSharp for image I/O - Inspectable pipeline — dump intermediate processing stages as JPEGs for debugging
- Extensible — custom alphabets, neural networks, plate formats, and tuning parameters via XML config
Project status
This is an active work in progress. The core recognition pipeline and CLI have been ported from javaanpr; GUI components have not. Automated recognition tests and real-world accuracy tuning are ongoing.
Contributions — bug reports, test images, alphabet data, configuration improvements, and pull requests — are welcome.
Requirements
| Component | Target |
|---|---|
Library (dotnetANPR) |
.NET Standard 2.0+ |
CLI (dotnetANPR.CLI) |
.NET 8+ |
| Build / CI | .NET SDK 10+ |
Getting started
Clone the repository and build the solution:
git clone https://github.com/bolorundurowb/dotnetANPR.git
cd dotnetANPR
dotnet build ./src/dotnetANPR.slnx --configuration Release
Add the library to your project
Reference the project directly (NuGet package publishing is not set up yet):
<ItemGroup>
<ProjectReference Include="path/to/dotnetANPR/src/dotnetANPR/dotnetANPR.csproj" />
</ItemGroup>
Character training data
Recognition requires alphabet training images at the path configured by char_learnAlphabetPath (default: Resources/alphabets/alphabet_8x13). If this directory is not present in your checkout, copy it from the upstream javaanpr repository. Each file should be a normalised character image matching the configured dimensions (8×13 px by default).
Usage
Library
using dotnetANPR;
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
var engine = new AnprEngine(new AnprOptions { LoggerFactory = loggerFactory });
// Recognise from a file path
var result = engine.Recognize("car.jpg");
if (result.Success)
Console.WriteLine($"Plate: {result.Text} (confidence {result.Confidence:F2}, {result.Duration.TotalMilliseconds:F0} ms)");
// Recognise with diagnostics and optional skew correction
result = engine.Recognize("car.jpg", new RecognitionOptions
{
DumpStagesDirectory = "./stages",
EnableSkewCorrection = true,
});
// Recognise from a stream
using var stream = File.OpenRead("car.jpg");
result = engine.Recognize(stream);
// Recognise from a SkiaSharp bitmap (caller owns the bitmap by default)
using var bitmap = SKBitmap.Decode("car.jpg");
result = engine.Recognize(bitmap);
Recognize returns a RecognitionResult with Text (null when no plate was found), Confidence, Duration, and optional Candidates diagnostics. An invalid image path throws ArgumentException.
Command-line tool
dotnet run --project ./src/dotnetANPR.CLI -- car.jpg
# Dump intermediate processing stages for inspection
dotnet run --project ./src/dotnetANPR.CLI -- car.jpg --dump-stages ./stages
Or run the built executable directly:
./src/dotnetANPR.CLI/bin/Release/net8.0/dotnetANPR.CLI car.jpg
Diagnostics
Pass a dumpDir argument (library) or --dump-stages <dir> (CLI) to write sequentially numbered JPEG snapshots of each pipeline stage — thresholding, band extraction, plate cropping, character segmentation, and more. This is useful when tuning configuration or investigating misrecognitions.
How it works
Recognition follows a multi-stage pipeline inspired by classical computer vision rather than deep learning end-to-end models:
flowchart LR
A[Car image] --> B[Adaptive thresholding]
B --> C[Band detection]
C --> D[Plate candidate search]
D --> E[Optional skew correction]
E --> F[Character segmentation]
F --> G[Feature extraction]
G --> H[KNN or neural classifier]
H --> I[Syntax analysis]
I --> J[Plate text]
- Preprocessing — adaptive thresholding converts the photo to a binary map
- Region search — vertical histogram peaks locate horizontal bands; each band is searched for plate-shaped regions
- Heuristic filtering — candidate plates and characters are validated by size, aspect ratio, brightness, and contrast rules
- Classification — each character is normalised and matched against a learned alphabet (KNN by default)
- Syntax analysis — the raw string is corrected against templates in
Resources/syntax.xml(German, Slovak, Czech formats included)
API reference
The AnprEngine class is the main entry point. XML documentation is provided on all public types and members.
| Method | Description |
|---|---|
Recognize(string imagePath, RecognitionOptions?) |
Recognise a plate from a file path |
Recognize(Stream imageStream, RecognitionOptions?) |
Recognise a plate from a stream |
Recognize(SKBitmap image, RecognitionOptions?) |
Recognise a plate from a SkiaSharp bitmap |
RecognizeAsync(...) |
Async variants of the above |
ExportDefaultConfig(string outputFilePath) |
Export built-in default configuration to XML |
TrainNetworkAndExport(string outputFilePath) |
Train a neural network on the configured alphabet and export it |
NormalizeAlphabets(string sourceDir, string dstDir) |
Normalise raw alphabet character images |
RecognitionResult exposes Text, Confidence, Duration, Success, and optional Candidates (per-plate diagnostics).
Advanced diagnostics are available via RecognitionResult.Candidates rather than direct access to internal pipeline types.
Configuration
Pipeline behaviour is controlled by Resources/config.xml. Parameters are loaded at startup via the Configurator singleton; defaults are embedded in code and overridden by the XML file when present.
To start from the defaults and customise:
var engine = new AnprEngine(new AnprOptions());
engine.ExportDefaultConfig("my-config.xml");
// Edit my-config.xml, then place it at Resources/config.xml or adjust loading as needed
The tables below document every parameter. In most cases the defaults work well; adjust heuristics and graph parameters only when your source images differ significantly from typical front/rear car photos.
Configuration reference
Photo preprocessing
| Parameter | Default | Description | Effect of increasing |
|---|---|---|---|
photo_adaptivethresholdingradius |
7 |
Radius for adaptive thresholding. 0 uses plain global thresholding; N >= 1 uses a neighbourhood of radius N. |
Larger radius produces smoother thresholding at the cost of losing fine detail. Lower values (or 0) preserve edges but are more sensitive to noise. |
Skew detection
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
intelligence_skewdetection |
0 |
Enables skew detection and de-skewing correction via Hough transform. 0 = disabled, 1 = enabled. |
Enabling adds computational cost but corrects rotated plates, improving character recognition. |
Plate candidate search
| Parameter | Default | Description | Effect of increasing |
|---|---|---|---|
intelligence_numberOfBands |
3 |
Number of horizontal bands (candidate plate rows) extracted from the car image. | More bands catch more candidates but increase processing time and false positives. |
intelligence_numberOfPlates |
3 |
Number of plate candidates extracted from each band. | More candidates per band may find plates in unusual positions but increases processing time. |
intelligence_numberOfChars |
20 |
Maximum number of character peaks extracted from a plate's horizontal histogram. | Higher values allow more characters to be segmented but risk splitting characters or picking up noise. |
Plate heuristics
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
intelligence_minimumChars |
5 |
Minimum number of detected characters for a valid plate. | Lower values accept shorter plates (e.g. motorcycles); higher values reject partial detections. |
intelligence_maximumChars |
15 |
Maximum number of detected characters for a valid plate. | Lower values reject over-segmented plates; higher values accept longer plate formats. |
intelligence_maxCharWidthDispersion |
0.5 |
Maximum allowed variation in character widths relative to the average. | Higher values tolerate irregular character widths; lower values enforce uniform character widths. |
intelligence_minPlateWidthHeightRatio |
0.5 |
Minimum width-to-height ratio for a valid plate. | Higher values reject square-ish regions; lower values accept narrower plates. |
intelligence_maxPlateWidthHeightRatio |
15.0 |
Maximum width-to-height ratio for a valid plate. | Higher values accept very wide plates; lower values reject overly elongated regions. |
Character heuristics
| Parameter | Default | Description | Effect of increasing |
|---|---|---|---|
intelligence_minCharWidthHeightRatio |
0.1 |
Minimum width-to-height ratio for a valid character. | Higher values reject very narrow characters (like 1 or I); lower values accept them. |
intelligence_maxCharWidthHeightRatio |
0.92 |
Maximum width-to-height ratio for a valid character. | Higher values accept wider characters (like W or M); lower values reject them. |
intelligence_maxBrightnessCostDispersion |
0.161 |
Maximum allowed brightness deviation of a character from the plate average. | Higher values accept characters with more varied lighting; lower values enforce uniform brightness. |
intelligence_maxContrastCostDispersion |
0.1 |
Maximum allowed contrast deviation of a character from the plate average. | Higher values tolerate more varied background/text contrast; lower values enforce uniformity. |
intelligence_maxHueCostDispersion |
0.145 |
Maximum allowed hue deviation of a character from the plate average. | Higher values accept characters with different colour tones; lower values require consistent colouring. |
intelligence_maxSaturationCostDispersion |
0.24 |
Maximum allowed saturation deviation of a character from the plate average. | Higher values tolerate varied colour intensity; lower values enforce uniform saturation. |
intelligence_maxHeightCostDispersion |
0.2 |
Maximum allowed height deviation of a character from the plate average. | Higher values accept characters of differing heights; lower values require uniform height. |
intelligence_maxSimilarityCostDispersion |
100.0 |
Maximum classification cost for a recognised character to be accepted. | Higher values accept less confident matches (more false positives); lower values require stronger matches. |
Character normalisation and feature extraction
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
char_normalizeddimensions_x |
8 |
Width (in pixels) to which characters are downsampled before recognition. | Larger values preserve more detail but increase processing time and memory. Must match the learned alphabet dimensions. |
char_normalizeddimensions_y |
13 |
Height (in pixels) to which characters are downsampled before recognition. | Same as above — must match the alphabet used for training. |
char_learnAlphabetPath |
Resources/alphabets/alphabet_8x13 |
Path to the directory containing alphabet training images. | Point to a custom alphabet for different fonts or character styles. Images must match the normalised dimensions. |
char_resizeMethod |
1 |
Downsampling method. 0 = linear (better for edge detection), 1 = weighted average (better for pixel mapping). |
Use 0 with edge-based feature extraction; use 1 with map-based feature extraction. |
char_featuresExtractionMethod |
0 |
Feature extraction method. 0 = direct pixel mapping (good for blurred characters), 1 = edge features (good for skewed characters). |
Use 0 for clean/blurred characters; use 1 for skewed or deformed characters. |
Classification method
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
intelligence_classification_method |
0 |
Classification algorithm. 0 = KNN Euclidean distance pattern matching, 1 = feed-forward neural network. |
Pattern matching is faster and requires no training. Neural network is more accurate but requires a trained network file. |
Neural network parameters
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
char_neuralNetworkPath |
Resources/neuralnetworks/network_avgres_813_map.xml |
Path to the pre-trained neural network XML file. Dimensions must match the feature extraction method and normalised character size. | Only used when intelligence_classification_method is 1. |
neural_topology |
20 |
Number of neurons in the hidden (middle) layer. | More neurons increase the network's capacity and accuracy but also increase training time and risk overfitting. |
neural_maxk |
8000 |
Maximum number of training iterations (epochs). | Higher values allow more thorough training but may overfit. Lower values may stop before convergence. |
neural_eps |
0.07 |
Convergence threshold (epsilon). Training stops when the total gradient falls below this value. | Lower values produce more precise training; higher values stop earlier (faster but less accurate). |
neural_lambda |
0.05 |
Learning rate (lambda). Controls how much weights are adjusted per iteration. | Higher values converge faster but may overshoot; lower values converge more stably but slowly. |
neural_micro |
0.5 |
Momentum factor (micro). Persistence ratio of previous weight changes. | Higher values smooth the gradient descent and help escape local minima; lower values respond faster to new gradients. |
Syntax analysis
| Parameter | Default | Description | Effect of changing |
|---|---|---|---|
intelligence_syntaxanalysis |
2 |
Syntax correction mode. 0 = no correction, 1 = correct only if character count matches a known format, 2 = correct regardless, eliminating redundant characters. |
Mode 2 is most aggressive (may over-correct); mode 1 is safer. Mode 0 returns raw recognition. |
intelligence_syntaxDescriptionFile |
Resources/syntax.xml |
Path to the XML file defining known plate format templates. | Point to a custom file to add plate formats for different countries. See syntax configuration reference below. |
Graph peak analysis
These parameters control histogram peak detection used for finding plate bands, plates, and characters. They should generally not need adjustment unless the source images have unusual characteristics.
| Parameter | Default | Description | Effect of increasing |
|---|---|---|---|
carsnapshot_graphrankfilter |
9 |
Rank filter applied to the car snapshot vertical histogram. | Higher values produce smoother histograms, reducing noise but potentially merging nearby peaks. |
carsnapshot_distributormargins |
25 |
Margins for the probability distribution applied to car snapshot peaks. | Larger margins spread probability more broadly, favouring wider bands. |
carsnapshotgraph_peakDiffMultiplicationConstant |
0.1 |
Multiplier for peak difference calculation in the car snapshot graph. | Higher values require larger gaps between peaks to be considered separate. |
carsnapshotgraph_peakfootconstant |
0.55 |
Fraction of peak height used to determine the foot (base) of a peak. | Higher values result in narrower peak boundaries; lower values widen them. |
bandgraph_peakDiffMultiplicationConstant |
0.2 |
Multiplier for peak difference calculation in band graphs. | Same as above but for plate detection within bands. |
bandgraph_peakfootconstant |
0.55 |
Peak foot constant for band graphs (plate detection). | Same effect as carsnapshotgraph_peakfootconstant but for plate-finding. |
plategraph_peakfootconstant |
0.7 |
Peak foot constant for character segmentation within plates. | Higher values produce narrower character segments; lower values widen them. |
plategraph_rel_minpeaksize |
0.86 |
Relative minimum peak size as a fraction of the maximum peak. | Higher values ignore smaller peaks (fewer character candidates); lower values detect more candidates. |
platehorizontalgraph_detectionType |
1 |
Horizontal edge detection method. 0 = magnitude derivative, 1 = Sobel edge detection. |
Edge detection (1) is more robust for finding plate boundaries. |
platehorizontalgraph_peakfootconstant |
0.05 |
Peak foot constant for the plate's horizontal edge graph (left/right bounds). | Higher values produce tighter left/right cropping of the plate. |
plateverticalgraph_peakfootconstant |
0.42 |
Peak foot constant for the plate's vertical edge graph (top/bottom bounds). | Higher values produce tighter top/bottom cropping of the plate. |
Syntax configuration reference
Resources/syntax.xml defines known licence plate format templates per country. Each <type> is a named template (e.g. germany). Each <char> element defines the set of allowed characters at that position. During syntax analysis, the parser tries to match recognised text against these templates and corrects mismatches when possible.
Format templates
| Template name | Positions | Pattern | Description |
|---|---|---|---|
germany |
6 | LLL DDD |
Letters: a–z, 0, o; Digits: 0–9. German-style plate (e.g. ABC 123). |
slovensko_nova |
7 | LL DD LL |
Letters: a–z, 0, o; Digits: 0–9, o. New Slovak plate (e.g. AA 123 AB). |
ceskoslovenska_novsia |
7 | LLL DDDD |
Letters: a–z, 0, o; Digits: 0–9, o. Newer Czechoslovak plate (e.g. AAA 1234). |
ceskoslovenska_starsia |
6 | LL DDDD |
Letters: a–z, 0, o; Digits: 0–9, o. Older Czechoslovak plate (e.g. AB 1234). |
ceska_nova |
7 | D L DDDDD |
First: 0–9, o; Second: c,b,k,h,l,t,m,e,p,a,s,u,j,z; Digits: 0–9, o. New Czech plate (e.g. 1A2 3456). |
Character set notation
a-z— alphabetic characters (uppercase or lowercase)0-9— numeric digitso— the letter O, often included in digit positions because O and 0 are visually similar and frequently confused by OCR0— the digit zero, often included in letter positions for the same reason
Adding a new format
To add a custom plate format, add a <type> element inside <structure>:
<type name="uk">
<char content="abcdefghijklmnopqrstuvwxyz"/>
<char content="0123456789"/>
<char content="0123456789"/>
<char content="abcdefghijklmnopqrstuvwxyz"/>
<char content="abcdefghijklmnopqrstuvwxyz"/>
<char content="abcdefghijklmnopqrstuvwxyz"/>
</type>
Each <char> element represents one position in the plate. The content attribute lists all characters allowed at that position.
Development
Solution layout
src/
├── dotnetANPR/ # Core library (netstandard2.0)
│ ├── AnprEngine.cs # Public entry point
│ ├── Configuration/ # XML config loader
│ ├── ImageAnalysis/ # Plate/character segmentation
│ ├── Intelligence/ # Recognition orchestration & syntax parser
│ ├── NeuralNetwork/ # Feed-forward network implementation
│ ├── Recognizer/ # KNN and neural classifiers
│ └── Resources/ # config.xml, syntax.xml, neural networks
├── dotnetANPR.CLI/ # Sample command-line application (.NET 8)
└── dotnetANPR.Tests/ # Test project
Build and test
dotnet build ./src/dotnetANPR.slnx --configuration Release
dotnet test --solution ./src/dotnetANPR.slnx
CI runs on every push and pull request to master via GitHub Actions.
Contributing
- Fork the repository and create a feature branch
- Make your changes with focused commits
- Run
dotnet buildanddotnet testlocally - Open a pull request with a clear description of the change and how you tested it
Bug reports and feature requests can be filed via GitHub Issues. Include sample images (where possible), expected vs. actual output, and your configuration when reporting recognition problems.
Acknowledgements
- javaanpr by Ondrej Martinsky — original Java implementation and algorithm design
- SkiaSharp — cross-platform 2D graphics
License
MIT — see LICENSE.
| Product | Versions 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 is compatible. |
| .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. |
-
.NETStandard 2.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- PropertyConfig.dll (>= 3.0.1)
- SkiaSharp (>= 3.116.0)
-
.NETStandard 2.1
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- PropertyConfig.dll (>= 3.0.1)
- SkiaSharp (>= 3.116.0)
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 |
|---|---|---|
| 0.1.0 | 110 | 6/21/2026 |