vagheshpatel commited on
Commit
d8dbbf1
·
verified ·
1 Parent(s): fda9220

Sync license-plate-recognition from metro-analytics-catalog

Browse files
Files changed (3) hide show
  1. LICENSE +56 -0
  2. README.md +230 -5
  3. export_and_quantize.sh +89 -0
LICENSE CHANGED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This directory contains two categories of content under different licenses.
2
+
3
+
4
+ Scripts and Documentation
5
+ -------------------------
6
+
7
+ The scripts (export_and_quantize.sh) and documentation (README.md) in this
8
+ directory are original works by Intel Corporation, licensed under the
9
+ MIT License.
10
+
11
+ Copyright (C) Intel Corporation
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in
21
+ all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ THE SOFTWARE.
30
+
31
+
32
+ License Plate Detector Model (yolov8_license_plate_detector)
33
+ ------------------------------------------------------------
34
+
35
+ The YOLOv8 license plate detector weights are distributed by the Intel Edge
36
+ AI Resources project and are based on the Ultralytics YOLOv8 framework,
37
+ licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
38
+
39
+ Source: https://github.com/open-edge-platform/edge-ai-resources
40
+ Upstream framework: https://github.com/ultralytics/ultralytics
41
+ License: https://github.com/ultralytics/ultralytics/blob/main/LICENSE
42
+ Docs: https://docs.ultralytics.com/models/yolov8/
43
+
44
+ Users must comply with the AGPL-3.0 license terms when using, modifying,
45
+ or distributing the YOLOv8 model weights or Ultralytics software.
46
+ For commercial licensing options, see https://www.ultralytics.com/license.
47
+
48
+
49
+ OCR Model (ch_PP-OCRv4_rec_infer)
50
+ ---------------------------------
51
+
52
+ The PaddleOCR PP-OCRv4 recognition model is developed by PaddlePaddle and
53
+ licensed under the Apache License, Version 2.0.
54
+
55
+ Source: https://github.com/PaddlePaddle/PaddleOCR
56
+ License: https://github.com/PaddlePaddle/PaddleOCR/blob/main/LICENSE
README.md CHANGED
@@ -1,5 +1,230 @@
1
- ---
2
- license: other
3
- license_name: other
4
- license_link: LICENSE
5
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # License Plate Recognition -- Detection and OCR on Intel Hardware
2
+
3
+ > **Reference pipeline:** [DLStreamer License Plate Recognition sample](https://github.com/open-edge-platform/dlstreamer/tree/main/samples/gstreamer/gst_launch/license_plate_recognition)
4
+ >
5
+ > **Validated with:** OpenVINO 2026.0.0, NNCF 3.0.0, DLStreamer 2025.2, Python 3.11+
6
+
7
+ | Property | Value |
8
+ |---|---|
9
+ | **Category** | Object Detection + Optical Character Recognition |
10
+ | **Source Framework** | PyTorch (Ultralytics YOLOv8), PaddlePaddle (PP-OCRv4) |
11
+ | **Supported Precisions** | FP32, FP16-INT8 (detector) |
12
+ | **Inference Engine** | OpenVINO |
13
+ | **Hardware** | CPU, GPU, NPU |
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ License Plate Recognition (LPR) is a Metro Analytics use case that locates vehicle license plates in a video stream and reads their alphanumeric content.
20
+ The pipeline composes two specialized models:
21
+
22
+ - **License Plate Detector** -- [`yolov8_license_plate_detector`](https://github.com/open-edge-platform/edge-ai-resources), a YOLOv8 model fine-tuned to localize license plates as oriented bounding boxes.
23
+ - **OCR Recognizer** -- [`ch_PP-OCRv4_rec_infer`](https://github.com/PaddlePaddle/PaddleOCR), the PaddleOCR PP-OCRv4 multilingual text recognizer that converts each cropped plate into a text string.
24
+
25
+ Typical Metro deployments include:
26
+
27
+ - **Tolling and Access Control** -- read plates at gates, depots, and parking entries.
28
+ - **Vehicle Search and Forensics** -- index plates seen at a station for investigative lookup.
29
+ - **Fleet and Bus Monitoring** -- correlate detected plates with operational schedules.
30
+
31
+ The detector returns one bounding box per plate; the OCR stage runs as a downstream classifier on the cropped region, attaching the recognized string as inference metadata on the frame.
32
+
33
+ > **Note:** Plate detector accuracy depends on the regional distribution of training data.
34
+ > The bundled detector was trained primarily on European and US plates.
35
+ > For other regions, fine-tune the YOLOv8 detector on a representative dataset before quantization.
36
+
37
+ ---
38
+
39
+ ## Prerequisites
40
+
41
+ - [Install OpenVINO 2026.0.0](https://docs.openvino.ai/2026/get-started/install-openvino.html)
42
+ - [Install Intel DLStreamer](https://dlstreamer.github.io/get_started/install/install-guide-ubuntu.html)
43
+
44
+ ---
45
+
46
+ ## Getting Started
47
+
48
+ ### Download and Quantize the Detector
49
+
50
+ Run the provided script to download the license plate detector OpenVINO IR and quantize it to INT8:
51
+
52
+ ```bash
53
+ chmod +x export_and_quantize.sh
54
+ ./export_and_quantize.sh ./models
55
+ ```
56
+
57
+ The script performs the following steps:
58
+
59
+ 1. Installs dependencies (`openvino`, `nncf`).
60
+ 2. Downloads the `license-plate-reader` archive from the Intel Edge AI Resources project and extracts it under `./models/yolov8_license_plate_detector/license-plate-reader/`.
61
+ The archive bundles both the YOLOv8 plate detector (`models/yolov8n/yolov8n_retrained.xml`) and the converted PaddleOCR recognizer (`models/ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml`), so no separate OCR download step is required.
62
+ 3. Quantizes the detector to INT8 using NNCF post-training quantization, producing `./models/yolov8_license_plate_detector/yolov8_license_plate_detector_int8.xml`.
63
+ 4. Runs `benchmark_app` to validate detector throughput.
64
+
65
+ > **Note:** For production accuracy, replace the random calibration tensors in
66
+ > `export_and_quantize.sh` with a representative sample of frames from the
67
+ > target deployment site.
68
+ > The INT8 detector produced from random calibration in the bundled script may
69
+ > miss small or low-contrast plates; if you need maximum recall before tuning
70
+ > calibration, point the pipeline at the FP32 IR
71
+ > (`models/yolov8_license_plate_detector/license-plate-reader/models/yolov8n/yolov8n_retrained.xml`).
72
+
73
+ ### Locating the OCR Recognizer
74
+
75
+ The PaddleOCR recognizer ships inside the same archive:
76
+
77
+ ```text
78
+ ./models/yolov8_license_plate_detector/license-plate-reader/models/ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml
79
+ ```
80
+
81
+ > **Note:** PaddleOCR PP-OCRv4 is a CTC sequence model.
82
+ > To convert its raw tensor output into a recognized plate string, DLStreamer's
83
+ > `gvaclassify` element requires a `model-proc` JSON with a CTC decoder
84
+ > converter and a character labels file.
85
+ > Neither file is bundled with the archive nor with the DLStreamer 2026.0.0
86
+ > sample model_procs.
87
+ > Without it the pipeline runs end-to-end and produces per-plate ROI metadata,
88
+ > but the OCR `label` field on each detected plate is an empty string.
89
+ > For a production deployment, supply your own `model-proc` (see
90
+ > [DLStreamer model_proc reference](https://dlstreamer.github.io/dev_guide/model_proc_file.html))
91
+ > with the PaddleOCR character dictionary; until then, treat the OCR stage as
92
+ > a placeholder.
93
+
94
+ ### DLStreamer Sample
95
+
96
+ The sample below builds the two-stage detection plus OCR pipeline using the Python GStreamer bindings.
97
+ The `gvadetect` element runs the license plate detector; `gvaclassify` then runs the PaddleOCR recognizer on each detected plate region.
98
+ A buffer probe extracts the recognized text from the `GstGVAJSONMeta` payload attached to each frame.
99
+
100
+ ```python
101
+ import json
102
+ import os
103
+
104
+ import gi
105
+
106
+ gi.require_version("Gst", "1.0")
107
+ from gi.repository import Gst
108
+
109
+ Gst.init(None)
110
+
111
+ MODELS_DIR = os.path.abspath("./models/yolov8_license_plate_detector")
112
+ DETECTOR_XML = f"{MODELS_DIR}/yolov8_license_plate_detector_int8.xml"
113
+ OCR_XML = (
114
+ f"{MODELS_DIR}/license-plate-reader/models/"
115
+ "ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml"
116
+ )
117
+ INPUT_VIDEO = "test_video.mp4"
118
+
119
+ pipeline_str = (
120
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! videoconvert ! "
121
+ f"video/x-raw,format=BGR ! "
122
+ f"gvadetect model={DETECTOR_XML} device=CPU threshold=0.5 ! queue ! "
123
+ f"gvaclassify model={OCR_XML} device=CPU inference-region=roi-list ! "
124
+ f"queue ! gvametaconvert format=json add-tensor-data=false ! "
125
+ f"gvawatermark ! videoconvert ! autovideosink name=sink"
126
+ )
127
+
128
+ pipeline = Gst.parse_launch(pipeline_str)
129
+
130
+
131
+ def on_buffer(pad, info):
132
+ buf = info.get_buffer()
133
+ meta_iter = buf.iterate_meta()
134
+ while True:
135
+ ok, meta = meta_iter.next()
136
+ if not ok:
137
+ break
138
+ if meta.__gtype__.name != "GstGVAJSONMetaAPI":
139
+ continue
140
+ try:
141
+ payload = json.loads(meta.get_message())
142
+ except (AttributeError, ValueError):
143
+ continue
144
+ for obj in payload.get("objects", []):
145
+ label = obj.get("detection", {}).get("label", "")
146
+ text = ""
147
+ for tensor in obj.get("tensors", []):
148
+ if tensor.get("layer_name") and "label" in tensor:
149
+ text = tensor["label"]
150
+ break
151
+ if label and text:
152
+ print(f"Plate: {text} bbox={obj.get('x')},{obj.get('y')}")
153
+ return Gst.PadProbeReturn.OK
154
+
155
+
156
+ sink = pipeline.get_by_name("sink")
157
+ sink_pad = sink.get_static_pad("sink")
158
+ sink_pad.add_probe(Gst.PadProbeType.BUFFER, on_buffer)
159
+
160
+ pipeline.set_state(Gst.State.PLAYING)
161
+ bus = pipeline.get_bus()
162
+ bus.timed_pop_filtered(
163
+ Gst.CLOCK_TIME_NONE,
164
+ Gst.MessageType.EOS | Gst.MessageType.ERROR,
165
+ )
166
+ pipeline.set_state(Gst.State.NULL)
167
+ ```
168
+
169
+ To run on integrated GPU, change both `device=CPU` properties to `device=GPU` and prepend `vapostproc` after `decodebin3` for zero-copy color conversion.
170
+
171
+ ### Try It on a Sample Video
172
+
173
+ Download a publicly hosted Intel sample clip that contains vehicles with visible license plates:
174
+
175
+ ```bash
176
+ wget -O test_video.mp4 \
177
+ https://github.com/intel-iot-devkit/sample-videos/raw/master/car-detection.mp4
178
+ ```
179
+
180
+ Run the DLStreamer sample above.
181
+ A window opened by `autovideosink` shows each decoded frame with a green bounding box drawn by `gvawatermark` around every detected plate.
182
+ The buffer probe prints one line per detected plate per frame.
183
+
184
+ > **Note:** The INT8 detector built by `export_and_quantize.sh` with random
185
+ > calibration tensors typically detects only one or two plates across this
186
+ > short clip at the documented `threshold=0.5`.
187
+ > For a richer demo run, swap `DETECTOR_XML` to the bundled FP32 IR and lower
188
+ > the threshold:
189
+ >
190
+ > ```python
191
+ > DETECTOR_XML = (
192
+ > f"{MODELS_DIR}/license-plate-reader/models/yolov8n/"
193
+ > "yolov8n_retrained.xml"
194
+ > )
195
+ > ```
196
+ >
197
+ > and change `threshold=0.5` to `threshold=0.3` in `pipeline_str`.
198
+
199
+ Without a custom `model-proc` for PP-OCRv4 (see the OCR note above), the recognized `text` field is empty even though the detector and the OCR network both run on every plate ROI:
200
+
201
+ ```text
202
+ Plate: bbox=395,373
203
+ Plate: bbox=520,419
204
+ ```
205
+
206
+ Once you supply a CTC model-proc and PaddleOCR character labels, the same lines will include the decoded plate string, for example:
207
+
208
+ ```text
209
+ Plate: ABC1234 bbox=812,442
210
+ Plate: ZN98YX bbox=305,388
211
+ ```
212
+
213
+ If you only need the structured output and not the live preview, replace `autovideosink` with `fakesink` in `pipeline_str` and pipe the console output to a file.
214
+
215
+ ---
216
+
217
+ ## License
218
+
219
+ Copyright (C) Intel Corporation. All rights reserved.
220
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
221
+
222
+ ## References
223
+
224
+ - [DLStreamer License Plate Recognition Sample](https://github.com/open-edge-platform/dlstreamer/tree/main/samples/gstreamer/gst_launch/license_plate_recognition)
225
+ - [Intel Edge AI Resources -- License Plate Reader Model](https://github.com/open-edge-platform/edge-ai-resources)
226
+ - [PaddleOCR PP-OCRv4](https://github.com/PaddlePaddle/PaddleOCR)
227
+ - [Ultralytics YOLOv8 Documentation](https://docs.ultralytics.com/models/yolov8/)
228
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
229
+ - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
230
+ - [Intel DLStreamer](https://dlstreamer.github.io/)
export_and_quantize.sh ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Download the YOLOv8 license plate detector, quantize it to INT8 with NNCF,
6
+ # and stage the PaddleOCR PP-OCRv4 recognizer for use with Intel DLStreamer.
7
+ # Usage: ./export_and_quantize.sh [MODELS_DIR]
8
+ # Example: ./export_and_quantize.sh ./models
9
+
10
+ set -euo pipefail
11
+
12
+ MODELS_DIR="${1:-./models}"
13
+ LP_DETECTOR_NAME="yolov8_license_plate_detector"
14
+ LP_DETECTOR_URL="https://github.com/open-edge-platform/edge-ai-resources/raw/main/models/license-plate-reader.zip"
15
+ OCR_NAME="ch_PP-OCRv4_rec_infer"
16
+
17
+ mkdir -p "${MODELS_DIR}"
18
+
19
+ echo "--- Installing dependencies ---"
20
+ pip install -qU "openvino>=2026.0.0" "nncf>=3.0.0"
21
+
22
+ echo "--- Downloading ${LP_DETECTOR_NAME} (OpenVINO IR) ---"
23
+ LP_DIR="${MODELS_DIR}/${LP_DETECTOR_NAME}"
24
+ mkdir -p "${LP_DIR}"
25
+ TMP_ZIP="$(mktemp --suffix=.zip)"
26
+ trap 'rm -f "${TMP_ZIP}"' EXIT
27
+ curl -fsSL "${LP_DETECTOR_URL}" -o "${TMP_ZIP}"
28
+ unzip -oq "${TMP_ZIP}" -d "${LP_DIR}"
29
+
30
+ LP_XML="$(find "${LP_DIR}" -name "*retrained*.xml" -o -name "*license*plate*.xml" | head -n1)"
31
+ if [[ -z "${LP_XML}" ]]; then
32
+ LP_XML="$(find "${LP_DIR}" -path "*/yolov8n/*.xml" | head -n1)"
33
+ fi
34
+ if [[ -z "${LP_XML}" ]]; then
35
+ echo "Error: license plate detector .xml not found under ${LP_DIR}" >&2
36
+ exit 1
37
+ fi
38
+ echo "Found detector model: ${LP_XML}"
39
+
40
+ echo "--- Quantizing license plate detector to INT8 with NNCF ---"
41
+ LP_INT8_XML="${LP_DIR}/${LP_DETECTOR_NAME}_int8.xml"
42
+ python3 - <<PY
43
+ import nncf
44
+ import numpy as np
45
+ import openvino as ov
46
+
47
+ core = ov.Core()
48
+ model = core.read_model("${LP_XML}")
49
+
50
+ input_shape = model.inputs[0].partial_shape
51
+ h = int(input_shape[2].get_length()) if input_shape[2].is_static else 640
52
+ w = int(input_shape[3].get_length()) if input_shape[3].is_static else 640
53
+
54
+ def transform_fn(_):
55
+ return np.random.rand(1, 3, h, w).astype(np.float32)
56
+
57
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
58
+
59
+ quantized = nncf.quantize(
60
+ model,
61
+ calibration_dataset,
62
+ preset=nncf.QuantizationPreset.MIXED,
63
+ subset_size=300,
64
+ )
65
+
66
+ ov.save_model(quantized, "${LP_INT8_XML}")
67
+ print("Quantization complete: ${LP_INT8_XML}")
68
+ PY
69
+
70
+ echo "--- Staging OCR model (${OCR_NAME}) ---"
71
+ OCR_DIR="${MODELS_DIR}/${OCR_NAME}"
72
+ if [[ -f "${OCR_DIR}/${OCR_NAME}.xml" ]]; then
73
+ echo "OCR model already present at ${OCR_DIR}"
74
+ else
75
+ cat <<EOM
76
+ The PaddleOCR PP-OCRv4 recognizer requires Paddle to OpenVINO IR conversion.
77
+ Use the official Intel DLStreamer downloader to fetch and convert it:
78
+
79
+ export MODELS_PATH="\$(pwd)/${MODELS_DIR}"
80
+ /opt/intel/dlstreamer/samples/download_public_models.sh ${OCR_NAME}
81
+
82
+ The converted model will be placed under \${MODELS_PATH}/public/${OCR_NAME}/.
83
+ EOM
84
+ fi
85
+
86
+ echo "--- Benchmarking license plate detector ---"
87
+ benchmark_app -m "${LP_INT8_XML}" -d CPU -niter 50 -api async
88
+
89
+ echo "--- Done ---"