vagheshpatel commited on
Commit
67ba03b
·
verified ·
1 Parent(s): c50cfa6

Sync ocr-text-recognition from metro-analytics-catalog

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ expected_output_dlstreamer.gif filter=lfs diff=lfs merge=lfs -text
37
+ expected_output_openvino.gif filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) Intel Corporation.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
README.md ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ license_link: LICENSE
4
+ library_name: openvino
5
+ pipeline_tag: image-to-text
6
+ tags:
7
+ - openvino
8
+ - intel
9
+ - paddleocr
10
+ - ocr
11
+ - text-recognition
12
+ - edge-ai
13
+ - metro
14
+ - dlstreamer
15
+ language:
16
+ - en
17
+ ---
18
+
19
+ # OCR for Text
20
+
21
+ | Property | Value |
22
+ |---|---|
23
+ | **Category** | Optical Character Recognition (Text Detection + Recognition) |
24
+ | **Base Model** | [PP-OCRv4](https://github.com/PaddlePaddle/PaddleOCR) (PaddlePaddle) |
25
+ | **Source Framework** | PaddlePaddle |
26
+ | **Supported Precisions** | FP32, FP16 |
27
+ | **Inference Engine** | OpenVINO |
28
+ | **Hardware** | CPU, GPU, NPU |
29
+ | **Detected Class(es)** | Text regions + recognized text strings |
30
+
31
+ ---
32
+
33
+ ## Overview
34
+
35
+ OCR for Text is a Metro Analytics use case that detects and reads text in
36
+ images and video streams using the PaddleOCR PP-OCRv4 pipeline.
37
+ It composes two models:
38
+
39
+ - **PP-OCRv4 Detection** (`ch_PP-OCRv4_det`) -- a lightweight DBNet-based
40
+ text detector that locates text regions in the frame.
41
+ - **PP-OCRv4 Recognition** (`ch_PP-OCRv4_rec_server`) -- the larger "server"
42
+ CRNN-CTC recognizer variant, which is more accurate than the lightweight
43
+ mobile variant on stylized or decorative fonts, and converts each cropped
44
+ text region into a character string.
45
+
46
+ Both models are converted to OpenVINO IR using the `ovc` (OpenVINO Model
47
+ Converter) tool which reads PaddlePaddle models directly.
48
+ This is the best supported end-to-end OCR stack for OpenVINO.
49
+
50
+ Typical Metro deployments include:
51
+
52
+ - **Signage Reading** -- read platform signs, departure boards, safety notices.
53
+ - **Document Scanning** -- extract text from forms, labels, and ID cards.
54
+ - **Label Verification** -- read package labels or barcodes in logistics.
55
+ - **Multilingual Support** -- PP-OCRv4 supports multiple scripts out of the box.
56
+
57
+ For license-plate-specific OCR, see the
58
+ [license-plate-recognition](../license-plate-recognition/) use case which
59
+ includes a specialized plate detector.
60
+
61
+ ---
62
+
63
+ ## Prerequisites
64
+
65
+ - Python 3.11+
66
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
67
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
68
+
69
+ Create and activate a Python virtual environment before running the scripts:
70
+
71
+ ```bash
72
+ python3 -m venv .venv --system-site-packages
73
+ source .venv/bin/activate
74
+ ```
75
+
76
+ > **Note:** The `--system-site-packages` flag is required so the virtual
77
+ > environment can access the system-installed OpenVINO and DLStreamer Python
78
+ > packages.
79
+
80
+ ---
81
+
82
+ ## Getting Started
83
+
84
+ ### Download and Convert Models
85
+
86
+ Run the provided script to download the PaddleOCR models and convert them to
87
+ OpenVINO IR:
88
+
89
+ ```bash
90
+ chmod +x export_and_quantize.sh
91
+ ./export_and_quantize.sh
92
+ ```
93
+
94
+ The script performs the following steps:
95
+
96
+ 1. Installs dependencies (`openvino`).
97
+ 2. Downloads the PP-OCRv4 detection and recognition inference models.
98
+ 3. Converts both to OpenVINO IR format using `ovc`.
99
+ 4. Downloads a sample test image with text, a sample test video
100
+ (`test_video.mp4`, a close-up of street name and stop signs), and the
101
+ PP-OCRv4 character dictionary (`ppocr_keys_v1.txt`) used to CTC-decode the
102
+ recognizer's output into text.
103
+
104
+ Output files:
105
+
106
+ - `ch_PP-OCRv4_det_infer/` -- detection model (OpenVINO IR).
107
+ - `ch_PP-OCRv4_rec_server_infer/` -- recognition model, server variant (OpenVINO IR).
108
+ - `ppocr_keys_v1.txt` -- character dictionary for the recognizer's CTC decoder.
109
+
110
+ ### OpenVINO Sample
111
+
112
+ The sample below runs the full PP-OCRv4 pipeline across every frame of a
113
+ video: the detector locates text regions (using an aspect-ratio-preserving
114
+ resize and a dilation step so a whole word is captured in one box instead of
115
+ fragments), then the recognizer reads each cropped region and CTC-decodes it
116
+ into a text string, which is drawn as a solid-background label directly over
117
+ its box so the highlighted region visibly shows what is written.
118
+ Change the `device` string to run on CPU, GPU, or NPU.
119
+
120
+ ```python
121
+ import cv2
122
+ import numpy as np
123
+ import openvino as ov
124
+
125
+ DET_MODEL = "ch_PP-OCRv4_det_infer/inference.xml"
126
+ REC_MODEL = "ch_PP-OCRv4_rec_server_infer/inference.xml"
127
+ DICT_FILE = "ppocr_keys_v1.txt"
128
+ INPUT_VIDEO = "test_video.mp4"
129
+ DET_SIZE = 960
130
+
131
+ core = ov.Core()
132
+
133
+ # Change device to "GPU" or "NPU" to run on integrated GPU or NPU.
134
+ det_compiled = core.compile_model(core.read_model(DET_MODEL), "CPU")
135
+ rec_compiled = core.compile_model(core.read_model(REC_MODEL), "CPU")
136
+
137
+ # CTC label map: index 0 is the blank symbol, followed by every character in
138
+ # the dictionary file, followed by a trailing space character.
139
+ chars = open(DICT_FILE, encoding="utf-8").read().splitlines()
140
+ dict_character = ["blank"] + chars + [" "]
141
+
142
+
143
+ def detect_text_regions(frame, thresh=0.3, pad=4):
144
+ """Return (x, y, w, h) boxes for words/lines of text in a frame.
145
+
146
+ Resizing preserves aspect ratio (letterboxed onto a square canvas) so
147
+ text isn't skewed, and dilating the detection map merges nearby
148
+ characters into one box per word instead of one per character.
149
+ """
150
+ h0, w0 = frame.shape[:2]
151
+ scale = DET_SIZE / max(h0, w0)
152
+ resized = cv2.resize(frame, (int(w0 * scale), int(h0 * scale)))
153
+ canvas = np.zeros((DET_SIZE, DET_SIZE, 3), dtype=np.uint8)
154
+ canvas[:resized.shape[0], :resized.shape[1]] = resized
155
+
156
+ blob = canvas.astype(np.float32).transpose(2, 0, 1)[np.newaxis] / 255.0
157
+ det_map = det_compiled([blob])[det_compiled.output(0)][0, 0]
158
+ binary = (det_map > thresh).astype(np.uint8) * 255
159
+ dilated = cv2.dilate(binary, np.ones((9, 25), np.uint8))
160
+ contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
161
+
162
+ boxes = []
163
+ for c in contours:
164
+ x, y, w, h = cv2.boundingRect(c)
165
+ if w < 10 or h < 5:
166
+ continue
167
+ x0, y0 = max(0, x / scale - pad), max(0, y / scale - pad)
168
+ x1, y1 = min(w0, (x + w) / scale + pad), min(h0, (y + h) / scale + pad)
169
+ boxes.append((int(x0), int(y0), int(x1 - x0), int(y1 - y0)))
170
+ return boxes
171
+
172
+
173
+ def recognize_text(crop, rec_h=48, max_w=320):
174
+ """Resize a cropped text region to the recognizer's input shape and
175
+ CTC-decode the predicted character sequence into a string."""
176
+ h, w = crop.shape[:2]
177
+ if h == 0 or w == 0:
178
+ return "", 0.0
179
+ resized_w = max(1, min(max_w, round(rec_h * w / h)))
180
+ blob = cv2.resize(crop, (resized_w, rec_h)).astype(np.float32) / 255.0
181
+ blob = ((blob - 0.5) / 0.5).transpose(2, 0, 1)[np.newaxis, ...]
182
+
183
+ preds = rec_compiled([blob])[rec_compiled.output(0)][0]
184
+ idx = np.argmax(preds, axis=1)
185
+ conf = np.max(preds, axis=1)
186
+
187
+ text, scores, prev = [], [], -1
188
+ for i, c in zip(idx, conf):
189
+ if i != 0 and i != prev:
190
+ text.append(dict_character[i])
191
+ scores.append(c)
192
+ prev = i
193
+ confidence = float(np.mean(scores)) if scores else 0.0
194
+ return "".join(text), confidence
195
+
196
+
197
+ def annotate(frame, box, text, confidence):
198
+ """Draw a bounding box and, if any text was recognized, a legible
199
+ label (solid background so it stays readable over any color) above it."""
200
+ x, y, w, h = box
201
+ cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
202
+ if not text:
203
+ return
204
+ label = f"{text} ({confidence:.2f})"
205
+ (tw, th), base = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
206
+ top = max(0, y - th - base - 6)
207
+ cv2.rectangle(frame, (x, top), (x + tw + 6, top + th + base + 6), (0, 255, 0), -1)
208
+ cv2.putText(frame, label, (x + 3, top + th + 2),
209
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2)
210
+
211
+
212
+ cap = cv2.VideoCapture(INPUT_VIDEO)
213
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
214
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
215
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
216
+ writer = cv2.VideoWriter(
217
+ "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
218
+
219
+ frame_idx = 0
220
+ total_regions = 0
221
+
222
+ while True:
223
+ ok, frame = cap.read()
224
+ if not ok:
225
+ break
226
+ frame_idx += 1
227
+
228
+ for box in detect_text_regions(frame):
229
+ x, y, w, h = box
230
+ text, confidence = recognize_text(frame[y:y + h, x:x + w])
231
+ annotate(frame, box, text, confidence)
232
+ total_regions += 1
233
+ print(f"Frame {frame_idx}: region=({x},{y},{w},{h}) text={text!r} "
234
+ f"confidence={confidence:.2f}", flush=True)
235
+
236
+ writer.write(frame)
237
+
238
+ cap.release()
239
+ writer.release()
240
+ print(f"Total text regions across all frames: {total_regions}", flush=True)
241
+ print("Saved: output_openvino.mp4")
242
+ ```
243
+
244
+ **Device targets:**
245
+
246
+ - `"CPU"` -- default, works on all Intel platforms.
247
+ - `"GPU"` -- Intel integrated or discrete GPU.
248
+ - `"NPU"` -- Intel NPU; PP-OCRv4 FP16 models are NPU-compatible.
249
+
250
+ > **Note:** Recognition accuracy depends heavily on font, angle, and image
251
+ > quality. Plain block-lettered signage (as in the sample video) decodes
252
+ > reliably; stylized or decorative fonts are harder for a general-purpose
253
+ > OCR model and may not decode perfectly.
254
+
255
+ #### Expected Output
256
+
257
+ ![OpenVINO expected output](expected_output_openvino.gif)
258
+
259
+ ### DLStreamer Sample
260
+
261
+ The sample below decodes a video with the DLStreamer/GStreamer stack
262
+ (`decodebin3 ! videoconvert`), pulls BGR frames through `appsink`,
263
+ runs the PP-OCRv4 text detector on each frame (using an aspect-ratio-preserving
264
+ resize and a dilation step so a whole word is captured in one box instead of
265
+ fragments), then runs the PP-OCRv4 recognizer on each cropped region and
266
+ CTC-decodes the result into text drawn as a solid-background label directly
267
+ over its box before writing the annotated output to `output_dlstreamer.mp4`.
268
+
269
+ > **Notes on running this sample:**
270
+ >
271
+ > - Export `PYTHONPATH` so the DLStreamer Python module is importable:
272
+ >
273
+ > ```bash
274
+ > source /opt/intel/openvino_2026/setupvars.sh
275
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
276
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
277
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
278
+ > ```
279
+
280
+ ```python
281
+ import gi
282
+
283
+ gi.require_version("Gst", "1.0")
284
+ from gi.repository import Gst
285
+
286
+ import numpy as np
287
+ import openvino as ov
288
+
289
+ Gst.init([])
290
+
291
+ # Import cv2 after Gst.init to avoid GStreamer re-initialization conflicts.
292
+ import cv2
293
+
294
+ INPUT_VIDEO = "test_video.mp4"
295
+ DET_MODEL = "ch_PP-OCRv4_det_infer/inference.xml"
296
+ REC_MODEL = "ch_PP-OCRv4_rec_server_infer/inference.xml"
297
+ DICT_FILE = "ppocr_keys_v1.txt"
298
+ DET_SIZE = 960
299
+
300
+ core = ov.Core()
301
+ det_compiled = core.compile_model(core.read_model(DET_MODEL), "CPU")
302
+ rec_compiled = core.compile_model(core.read_model(REC_MODEL), "CPU")
303
+
304
+ # CTC label map: index 0 is the blank symbol, followed by every character in
305
+ # the dictionary file, followed by a trailing space character.
306
+ chars = open(DICT_FILE, encoding="utf-8").read().splitlines()
307
+ dict_character = ["blank"] + chars + [" "]
308
+
309
+
310
+ def detect_text_regions(frame, thresh=0.3, pad=4):
311
+ """Return (x, y, w, h) boxes for words/lines of text in a frame.
312
+
313
+ Resizing preserves aspect ratio (letterboxed onto a square canvas) so
314
+ text isn't skewed, and dilating the detection map merges nearby
315
+ characters into one box per word instead of one per character.
316
+ """
317
+ h0, w0 = frame.shape[:2]
318
+ scale = DET_SIZE / max(h0, w0)
319
+ resized = cv2.resize(frame, (int(w0 * scale), int(h0 * scale)))
320
+ canvas = np.zeros((DET_SIZE, DET_SIZE, 3), dtype=np.uint8)
321
+ canvas[:resized.shape[0], :resized.shape[1]] = resized
322
+
323
+ blob = canvas.astype(np.float32).transpose(2, 0, 1)[np.newaxis] / 255.0
324
+ det_map = det_compiled([blob])[det_compiled.output(0)][0, 0]
325
+ binary = (det_map > thresh).astype(np.uint8) * 255
326
+ dilated = cv2.dilate(binary, np.ones((9, 25), np.uint8))
327
+ contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
328
+
329
+ boxes = []
330
+ for c in contours:
331
+ x, y, w, h = cv2.boundingRect(c)
332
+ if w < 10 or h < 5:
333
+ continue
334
+ x0, y0 = max(0, x / scale - pad), max(0, y / scale - pad)
335
+ x1, y1 = min(w0, (x + w) / scale + pad), min(h0, (y + h) / scale + pad)
336
+ boxes.append((int(x0), int(y0), int(x1 - x0), int(y1 - y0)))
337
+ return boxes
338
+
339
+
340
+ def recognize_text(crop, rec_h=48, max_w=320):
341
+ """Resize a cropped text region to the recognizer's input shape and
342
+ CTC-decode the predicted character sequence into a string."""
343
+ h, w = crop.shape[:2]
344
+ if h == 0 or w == 0:
345
+ return "", 0.0
346
+ resized_w = max(1, min(max_w, round(rec_h * w / h)))
347
+ blob = cv2.resize(crop, (resized_w, rec_h)).astype(np.float32) / 255.0
348
+ blob = ((blob - 0.5) / 0.5).transpose(2, 0, 1)[np.newaxis, ...]
349
+
350
+ preds = rec_compiled([blob])[rec_compiled.output(0)][0]
351
+ idx = np.argmax(preds, axis=1)
352
+ conf = np.max(preds, axis=1)
353
+
354
+ text, scores, prev = [], [], -1
355
+ for i, c in zip(idx, conf):
356
+ if i != 0 and i != prev:
357
+ text.append(dict_character[i])
358
+ scores.append(c)
359
+ prev = i
360
+ confidence = float(np.mean(scores)) if scores else 0.0
361
+ return "".join(text), confidence
362
+
363
+
364
+ def annotate(frame, box, text, confidence):
365
+ """Draw a bounding box and, if any text was recognized, a legible
366
+ label (solid background so it stays readable over any color) above it."""
367
+ x, y, w, h = box
368
+ cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
369
+ if not text:
370
+ return
371
+ label = f"{text} ({confidence:.2f})"
372
+ (tw, th), base = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)
373
+ top = max(0, y - th - base - 6)
374
+ cv2.rectangle(frame, (x, top), (x + tw + 6, top + th + base + 6), (0, 255, 0), -1)
375
+ cv2.putText(frame, label, (x + 3, top + th + 2),
376
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2)
377
+
378
+
379
+ pipeline_str = (
380
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! videoconvert ! "
381
+ "video/x-raw,format=BGR ! "
382
+ "appsink name=sink emit-signals=false sync=false"
383
+ )
384
+ pipeline = Gst.parse_launch(pipeline_str)
385
+ sink = pipeline.get_by_name("sink")
386
+ pipeline.set_state(Gst.State.PLAYING)
387
+
388
+ writer = None
389
+ frame_idx = 0
390
+ total_regions = 0
391
+
392
+ while True:
393
+ sample = sink.emit("pull-sample")
394
+ if sample is None:
395
+ break
396
+ buf = sample.get_buffer()
397
+ caps = sample.get_caps().get_structure(0)
398
+ width = caps.get_value("width")
399
+ height = caps.get_value("height")
400
+
401
+ ok, mapinfo = buf.map(Gst.MapFlags.READ)
402
+ if not ok:
403
+ continue
404
+ frame = np.ndarray((height, width, 3), dtype=np.uint8,
405
+ buffer=mapinfo.data).copy()
406
+ buf.unmap(mapinfo)
407
+ frame_idx += 1
408
+
409
+ for box in detect_text_regions(frame):
410
+ x, y, w, h = box
411
+ text, confidence = recognize_text(frame[y:y + h, x:x + w])
412
+ annotate(frame, box, text, confidence)
413
+ total_regions += 1
414
+ print(f"Frame {frame_idx}: region=({x},{y},{w},{h}) text={text!r} "
415
+ f"confidence={confidence:.2f}", flush=True)
416
+
417
+ if writer is None:
418
+ writer = cv2.VideoWriter(
419
+ "output_dlstreamer.mp4", cv2.VideoWriter_fourcc(*"mp4v"),
420
+ 30.0, (width, height))
421
+ writer.write(frame)
422
+
423
+ pipeline.set_state(Gst.State.NULL)
424
+ if writer:
425
+ writer.release()
426
+ print(f"Total text regions across all frames: {total_regions}", flush=True)
427
+ ```
428
+
429
+ **Device targets:**
430
+
431
+ - `"CPU"` -- default for OpenVINO inference inside the appsink loop.
432
+ - `"GPU"` -- change `"CPU"` to `"GPU"` in `core.compile_model()`.
433
+ - `"NPU"` -- change `"CPU"` to `"NPU"` in `core.compile_model()`.
434
+
435
+ #### Expected Output
436
+
437
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
438
+
439
+ ---
440
+
441
+ ## License
442
+
443
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
444
+
445
+ ## References
446
+
447
+ - [PaddleOCR PP-OCRv4](https://github.com/PaddlePaddle/PaddleOCR)
448
+ - [PaddleOCR OpenVINO Deployment](https://github.com/PaddlePaddle/PaddleOCR/blob/main/deploy/paddle2onnx/readme.md)
449
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
450
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: bb116a12aeff62ab0e0f0f30c1c805712e92392c3523a281ebed969a4028f1f1
  • Pointer size: 133 Bytes
  • Size of remote file: 29.4 MB
expected_output_openvino.gif ADDED

Git LFS Details

  • SHA256: 1231a2b4df1f08d2a6afe451b34f34a57872b812633e50953b25ea3622df4541
  • Pointer size: 133 Bytes
  • Size of remote file: 29.1 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Download and convert PaddleOCR PP-OCRv4 detection and recognition models
6
+ # to OpenVINO IR for the ocr-text-recognition use case.
7
+ # Usage: ./export_and_quantize.sh
8
+
9
+ set -euo pipefail
10
+
11
+ echo "--- Installing dependencies ---"
12
+ pip install -qU openvino
13
+
14
+ # Ask for approval before downloading models and sample files
15
+ echo ""
16
+ echo "This script will download:"
17
+ echo " - Model weights and/or sample files"
18
+ echo ""
19
+ read -p "Continue with downloads? (yes/no): " APPROVAL
20
+ if [[ "${APPROVAL}" != "yes" ]]; then
21
+ echo "Download cancelled by user."
22
+ exit 0
23
+ fi
24
+ echo ""
25
+
26
+ DET_URL="https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar"
27
+ # The larger "server" recognition model is noticeably more accurate than the
28
+ # mobile variant on stylized/decorative fonts (e.g. the sample video's plate
29
+ # text), at the cost of a bigger download and slightly slower inference.
30
+ REC_URL="https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_server_infer.tar"
31
+
32
+ echo "--- Downloading PP-OCRv4 detection model ---"
33
+ if [[ ! -d "ch_PP-OCRv4_det_infer" ]]; then
34
+ wget -q -O det.tar "${DET_URL}"
35
+ tar xf det.tar
36
+ rm -f det.tar
37
+ echo "Downloaded and extracted: ch_PP-OCRv4_det_infer/"
38
+ else
39
+ echo "Already present: ch_PP-OCRv4_det_infer/"
40
+ fi
41
+
42
+ echo "--- Converting detection model to OpenVINO IR ---"
43
+ if [[ ! -f "ch_PP-OCRv4_det_infer/inference.xml" ]]; then
44
+ ovc ch_PP-OCRv4_det_infer/inference.pdmodel \
45
+ --output_model ch_PP-OCRv4_det_infer/inference.xml
46
+ echo "Converted detection model to OpenVINO IR"
47
+ else
48
+ echo "Already converted: ch_PP-OCRv4_det_infer/inference.xml"
49
+ fi
50
+
51
+ echo "--- Downloading PP-OCRv4 recognition model (server variant) ---"
52
+ if [[ ! -d "ch_PP-OCRv4_rec_server_infer" ]]; then
53
+ wget -q -O rec.tar "${REC_URL}"
54
+ tar xf rec.tar
55
+ rm -f rec.tar
56
+ echo "Downloaded and extracted: ch_PP-OCRv4_rec_server_infer/"
57
+ else
58
+ echo "Already present: ch_PP-OCRv4_rec_server_infer/"
59
+ fi
60
+
61
+ echo "--- Converting recognition model to OpenVINO IR ---"
62
+ if [[ ! -f "ch_PP-OCRv4_rec_server_infer/inference.xml" ]]; then
63
+ ovc ch_PP-OCRv4_rec_server_infer/inference.pdmodel \
64
+ --output_model ch_PP-OCRv4_rec_server_infer/inference.xml
65
+ echo "Converted recognition model to OpenVINO IR"
66
+ else
67
+ echo "Already converted: ch_PP-OCRv4_rec_server_infer/inference.xml"
68
+ fi
69
+
70
+ echo "--- Downloading sample test image ---"
71
+ if [[ ! -f test_ocr.jpg ]]; then
72
+ wget -q -O test_ocr.jpg \
73
+ "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.7/doc/imgs_en/img_12.jpg"
74
+ echo "Downloaded: test_ocr.jpg"
75
+ else
76
+ echo "Already present: test_ocr.jpg"
77
+ fi
78
+
79
+ echo "--- Downloading PP-OCRv4 character dictionary ---"
80
+ if [[ ! -f ppocr_keys_v1.txt ]]; then
81
+ wget -q -O ppocr_keys_v1.txt \
82
+ "https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.7/ppocr/utils/ppocr_keys_v1.txt"
83
+ echo "Downloaded: ppocr_keys_v1.txt"
84
+ else
85
+ echo "Already present: ppocr_keys_v1.txt"
86
+ fi
87
+
88
+ echo "--- Downloading sample test video ---"
89
+ if [[ ! -f test_video.mp4 ]]; then
90
+ wget -q -O test_video.mp4 \
91
+ "https://www.pexels.com/download/video/5286217/"
92
+ echo "Downloaded: test_video.mp4"
93
+ else
94
+ echo "Already present: test_video.mp4"
95
+ fi
96
+
97
+ echo "--- Done ---"
98
+ echo "Detection : ch_PP-OCRv4_det_infer/inference.xml"
99
+ echo "Recognition: ch_PP-OCRv4_rec_server_infer/inference.xml"
100
+ echo "Dictionary : ppocr_keys_v1.txt"
101
+ echo "Sample : test_ocr.jpg"
102
+ echo "Video : test_video.mp4"