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

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

Browse files
Files changed (2) hide show
  1. README.md +99 -102
  2. export_and_quantize.sh +18 -52
README.md CHANGED
@@ -1,14 +1,12 @@
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
 
@@ -32,43 +30,55 @@ The detector returns one bounding box per plate; the OCR stage runs as a downstr
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
 
@@ -78,51 +88,63 @@ The PaddleOCR recognizer ships inside the same archive:
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)
@@ -130,26 +152,22 @@ pipeline = Gst.parse_launch(pipeline_str)
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
 
@@ -166,50 +184,30 @@ bus.timed_pop_filtered(
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
  ---
@@ -226,5 +224,4 @@ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
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/)
 
1
+ # License Plate Recognition
2
 
3
+ > **Validated with:** OpenVINO 2026.0.0, DLStreamer 2026.0, Python 3.11+
 
 
4
 
5
  | Property | Value |
6
  |---|---|
7
  | **Category** | Object Detection + Optical Character Recognition |
8
  | **Source Framework** | PyTorch (Ultralytics YOLOv8), PaddlePaddle (PP-OCRv4) |
9
+ | **Supported Precisions** | FP32 |
10
  | **Inference Engine** | OpenVINO |
11
  | **Hardware** | CPU, GPU, NPU |
12
 
 
30
 
31
  > **Note:** Plate detector accuracy depends on the regional distribution of training data.
32
  > The bundled detector was trained primarily on European and US plates.
33
+ > For other regions, fine-tune the YOLOv8 detector on a representative dataset.
34
 
35
  ---
36
 
37
  ## Prerequisites
38
 
39
+ - Python 3.11+
40
  - [Install OpenVINO 2026.0.0](https://docs.openvino.ai/2026/get-started/install-openvino.html)
41
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
42
+
43
+ Create and activate a Python virtual environment before running the scripts:
44
+
45
+ ```bash
46
+ python3 -m venv .venv --system-site-packages
47
+ source .venv/bin/activate
48
+ ```
49
+
50
+ Activate the OpenVINO and DLStreamer runtimes in the same shell.
51
+ The DLStreamer Python module is not on `sys.path` by default, so export
52
+ `PYTHONPATH` as well:
53
+
54
+ ```bash
55
+ source /opt/intel/openvino_2026/setupvars.sh
56
+ source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
57
+ export PYTHONPATH=/opt/intel/dlstreamer/python:\
58
+ /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
59
+ ```
60
 
61
  ---
62
 
63
  ## Getting Started
64
 
65
+ ### Download the Models and Sample Video
66
 
67
+ Run the provided script to download the license plate detector and OCR
68
+ recognizer OpenVINO IR models and the sample test video:
69
 
70
  ```bash
71
  chmod +x export_and_quantize.sh
72
+ ./export_and_quantize.sh
73
  ```
74
 
75
  The script performs the following steps:
76
 
77
+ 1. Downloads the sample test video (`ParkingVideo.mp4`) from the Intel Edge AI Resources project into the current directory.
78
  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/`.
79
+ The archive bundles both the YOLOv8 plate detector (`models/yolov8n/yolov8n_retrained.xml`, FP32) and the converted PaddleOCR recognizer (`models/ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml`, FP32), so no separate OCR download step is required.
80
+
81
+ Both IRs are used as-is at FP32 -- no quantization step is performed.
 
 
 
 
 
 
 
 
82
 
83
  ### Locating the OCR Recognizer
84
 
 
88
  ./models/yolov8_license_plate_detector/license-plate-reader/models/ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml
89
  ```
90
 
91
+ > **Note:** PaddleOCR PP-OCRv4 is a CTC sequence model. DLStreamer 2026.0+
92
+ > auto-derives the CTC decoder for the bundled `ch_PP-OCRv4_rec_infer` IR
93
+ > and exposes the decoded plate string as `tensor.label()` on each
94
+ > classified ROI -- no external `model-proc` is required for this sample.
95
+ > For other PaddleOCR variants or non-Latin character sets, supply a custom
96
+ > `model-proc` (see
97
+ > [DLStreamer model_proc reference](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/dev_guide/model_proc_file.html))
98
+ > with the matching character dictionary.
 
 
 
 
99
 
100
  ### DLStreamer Sample
101
 
102
+ The sample below builds the two-stage detection plus OCR pipeline using the
103
+ Python GStreamer bindings.
104
+ It mirrors the structure of the upstream
105
+ [DLStreamer `license_plate_recognition.sh`](https://github.com/open-edge-platform/dlstreamer/blob/main/samples/gstreamer/gst_launch/license_plate_recognition/license_plate_recognition.sh)
106
+ sample: `decodebin3 ! queue ! gvadetect ! queue ! videoconvert ! gvaclassify ! queue ! gvawatermark ! ...`.
107
+ The `gvadetect` element runs the license plate detector;
108
+ `gvaclassify` then runs the PaddleOCR recognizer on each detected plate region.
109
+ A buffer probe extracts the recognized text from the inference metadata
110
+ attached to each frame.
111
+ The input is `ParkingVideo.mp4`, the short parking-lot clip downloaded by
112
+ `export_and_quantize.sh` into the current directory.
113
+ The annotated stream is muxed into `output.mp4` with H.264 (OpenH264).
114
 
115
  ```python
 
116
  import os
117
 
118
  import gi
119
 
120
  gi.require_version("Gst", "1.0")
121
+ gi.require_version("GstVideo", "1.0")
122
  from gi.repository import Gst
123
+ from gstgva import VideoFrame
124
 
125
  Gst.init(None)
126
 
127
  MODELS_DIR = os.path.abspath("./models/yolov8_license_plate_detector")
128
+ DETECTOR_XML = (
129
+ f"{MODELS_DIR}/license-plate-reader/models/"
130
+ "yolov8n/yolov8n_retrained.xml"
131
+ )
132
  OCR_XML = (
133
  f"{MODELS_DIR}/license-plate-reader/models/"
134
  "ch_PP-OCRv4_rec_infer/ch_PP-OCRv4_rec_infer.xml"
135
  )
136
+ INPUT_VIDEO = "ParkingVideo.mp4"
137
+ DEVICE = "CPU"
138
+ PREPROC = "pre-process-backend=opencv"
139
 
140
  pipeline_str = (
141
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! queue ! "
142
+ f"gvadetect model={DETECTOR_XML} device={DEVICE} {PREPROC} ! queue ! "
143
+ f"videoconvert ! "
144
+ f"gvaclassify model={OCR_XML} device={DEVICE} {PREPROC} ! queue ! "
145
+ f"gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
146
+ f"openh264enc ! h264parse ! "
147
+ f"mp4mux ! filesink name=sink location=output.mp4"
148
  )
149
 
150
  pipeline = Gst.parse_launch(pipeline_str)
 
152
 
153
  def on_buffer(pad, info):
154
  buf = info.get_buffer()
155
+ caps = pad.get_current_caps()
156
+ frame = VideoFrame(buf, caps=caps)
157
+ for region in frame.regions():
158
+ rect = region.rect()
159
+ text = ""
160
+ for tensor in region.tensors():
161
+ if tensor.is_detection():
162
+ continue
163
+ try:
164
+ text = tensor.label() or ""
165
+ except RuntimeError:
166
+ continue
167
+ if text:
168
+ break
169
+ if text:
170
+ print(f"Plate: {text} bbox=({rect.x},{rect.y})", flush=True)
 
 
 
 
171
  return Gst.PadProbeReturn.OK
172
 
173
 
 
184
  pipeline.set_state(Gst.State.NULL)
185
  ```
186
 
187
+ To run on integrated GPU, change `DEVICE = "CPU"` to `DEVICE = "GPU"` and
188
+ switch `PREPROC` to `"pre-process-backend=va-surface-sharing"`, matching the
189
+ upstream sample.
190
 
191
  ### Try It on a Sample Video
192
 
193
+ `export_and_quantize.sh` already downloaded `ParkingVideo.mp4` into the
194
+ current directory, so the sample is ready to run.
195
+ Execute the DLStreamer sample above.
196
+ The annotated video is saved to `output.mp4` with green bounding boxes drawn
197
+ by `gvawatermark` around every detected plate.
 
 
 
 
198
  The buffer probe prints one line per detected plate per frame.
199
 
200
+ Each detected plate that the OCR stage successfully decodes prints one line
201
+ per frame, for example:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
  ```text
204
+ Plate: 9MRM624 bbox=(979,458)
 
 
 
 
205
 
 
 
 
206
  ```
207
 
208
+ Low-confidence ROIs (small, blurred, or partially occluded plates) may yield
209
+ an empty CTC decode and are filtered out by the probe.
210
+
211
  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.
212
 
213
  ---
 
224
  - [PaddleOCR PP-OCRv4](https://github.com/PaddlePaddle/PaddleOCR)
225
  - [Ultralytics YOLOv8 Documentation](https://docs.ultralytics.com/models/yolov8/)
226
  - [OpenVINO Documentation](https://docs.openvino.ai/)
227
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
 
export_and_quantize.sh CHANGED
@@ -2,8 +2,8 @@
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
 
@@ -12,12 +12,17 @@ set -euo pipefail
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}"
@@ -37,53 +42,14 @@ if [[ -z "${LP_XML}" ]]; then
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 ---"
 
 
 
 
2
  # SPDX-License-Identifier: MIT
3
  # Copyright (C) Intel Corporation
4
  #
5
+ # Download the YOLOv8 license plate detector and PaddleOCR PP-OCRv4
6
+ # recognizer (both as OpenVINO IR) for use with Intel DLStreamer.
7
  # Usage: ./export_and_quantize.sh [MODELS_DIR]
8
  # Example: ./export_and_quantize.sh ./models
9
 
 
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
 
16
  mkdir -p "${MODELS_DIR}"
17
 
18
+ echo "--- Downloading sample test video ---"
19
+ SAMPLE_VIDEO_URL="https://github.com/open-edge-platform/edge-ai-resources/raw/main/videos/ParkingVideo.mp4"
20
+ if [[ ! -f ParkingVideo.mp4 ]]; then
21
+ curl -fsSL -o ParkingVideo.mp4 "${SAMPLE_VIDEO_URL}"
22
+ echo "Downloaded: ParkingVideo.mp4"
23
+ else
24
+ echo "Already present: ParkingVideo.mp4"
25
+ fi
26
 
27
  echo "--- Downloading ${LP_DETECTOR_NAME} (OpenVINO IR) ---"
28
  LP_DIR="${MODELS_DIR}/${LP_DETECTOR_NAME}"
 
42
  fi
43
  echo "Found detector model: ${LP_XML}"
44
 
45
+ OCR_XML="$(find "${LP_DIR}" -path "*ch_PP-OCRv4_rec_infer*.xml" | head -n1)"
46
+ if [[ -z "${OCR_XML}" ]]; then
47
+ echo "Error: PaddleOCR PP-OCRv4 .xml not found under ${LP_DIR}" >&2
48
+ exit 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  fi
50
+ echo "Found OCR model: ${OCR_XML}"
 
 
51
 
52
  echo "--- Done ---"
53
+ echo "Detector : ${LP_XML}"
54
+ echo "OCR : ${OCR_XML}"
55
+ echo "Sample : $(pwd)/ParkingVideo.mp4"