vagheshpatel commited on
Commit
1446c40
·
verified ·
1 Parent(s): 04b1d9a

Sync crowd-analysis 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,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ license_link: LICENSE
4
+ library_name: openvino
5
+ pipeline_tag: object-detection
6
+ tags:
7
+ - openvino
8
+ - intel
9
+ - yolo
10
+ - yolo26
11
+ - crowd-analysis
12
+ - crowd-density
13
+ - movement-patterns
14
+ - person-counting
15
+ - edge-ai
16
+ - metro
17
+ - dlstreamer
18
+ language:
19
+ - en
20
+ ---
21
+
22
+ # Crowd Analysis
23
+
24
+ | Property | Value |
25
+ |---|---|
26
+ | **Category** | Object Detection (Crowd Density + Movement) |
27
+ | **Base Model** | [YOLO26](https://docs.ultralytics.com/models/yolo26/) (Ultralytics) |
28
+ | **Source Framework** | PyTorch (Ultralytics) |
29
+ | **Supported Precisions** | FP32, FP16, INT8 (mixed-precision) |
30
+ | **Inference Engine** | OpenVINO |
31
+ | **Hardware** | CPU, GPU, NPU |
32
+ | **Detected Class** | `person` (COCO class 0) |
33
+
34
+ ---
35
+
36
+ ## Overview
37
+
38
+ Crowd Analysis is a Metro Analytics use case that estimates **crowd density** and
39
+ **movement patterns** in video streams. It detects people frame by frame, reports
40
+ a per-frame count with a simple density level (`LOW` / `MEDIUM` / `HIGH`), and
41
+ tracks each person across frames to estimate the dominant flow direction of the
42
+ crowd.
43
+ It is built on [YOLO26](https://docs.ultralytics.com/models/yolo26/), a
44
+ state-of-the-art real-time object detector trained on the COCO dataset, exported
45
+ to OpenVINO IR and filtered at runtime to the `person` class.
46
+
47
+ Typical Metro deployments include:
48
+
49
+ - **Platform & Concourse Density** -- gauge how crowded station platforms and
50
+ concourses are and flag build-up before it becomes unsafe.
51
+ - **Pedestrian Flow Analysis** -- estimate the dominant direction people move
52
+ through corridors, gates, and crossings.
53
+ - **Public-Venue Occupancy** -- monitor crowd density at stadiums, transit hubs,
54
+ and event entrances.
55
+ - **Situational Awareness** -- combine density level and flow to support
56
+ operator decisions in public venues and transportation hubs.
57
+
58
+ Available variants: `yolo26n`, `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`.
59
+ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge
60
+ deployment; larger variants improve recall in dense crowds.
61
+
62
+ > **Density levels** are defined by two count thresholds (defaults: `LOW` for
63
+ > fewer than 10 people, `MEDIUM` for 10-25, `HIGH` for more than 25). Tune these
64
+ > to the field of view and expected occupancy of your deployment site.
65
+
66
+ ---
67
+
68
+ ## Prerequisites
69
+
70
+ - Python 3.11+
71
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
72
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
73
+
74
+ Create and activate a Python virtual environment before running the scripts:
75
+
76
+ ```bash
77
+ python3 -m venv .venv --system-site-packages
78
+ source .venv/bin/activate
79
+ ```
80
+
81
+ > **Note:** The `--system-site-packages` flag is required so the virtual
82
+ > environment can access the system-installed OpenVINO and DLStreamer Python
83
+ > packages.
84
+
85
+ ---
86
+
87
+ ## Getting Started
88
+
89
+ ### Download and Quantize Model
90
+
91
+ Run the provided script to download, export to OpenVINO IR, and optionally quantize:
92
+
93
+ ```bash
94
+ chmod +x export_and_quantize.sh
95
+ ./export_and_quantize.sh
96
+ ```
97
+
98
+ This exports the default **yolo26n** model in **FP16** precision.
99
+
100
+ #### Optional: Select a Different Variant or Precision
101
+
102
+ ```bash
103
+ ./export_and_quantize.sh yolo26n FP32 # full-precision
104
+ ./export_and_quantize.sh yolo26n INT8 # quantized
105
+ ./export_and_quantize.sh yolo26s # larger variant, default FP16
106
+ ```
107
+
108
+ Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
109
+ The second argument selects the precision (`FP32`, `FP16`, `INT8`); the default is **FP16**.
110
+
111
+ The script performs the following steps:
112
+
113
+ 1. Installs dependencies (`openvino`, `ultralytics`; adds `nncf` for INT8).
114
+ 2. Downloads a sample test image (`test.jpg`) and a sample test video (`test_video.mp4`).
115
+ 3. Downloads the PyTorch weights and exports to OpenVINO IR.
116
+ 4. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
117
+
118
+ The sample video is a free-to-use
119
+ [pedestrians-crossing-the-street clip from Pexels](https://www.pexels.com/video/pedestrians-crossing-the-street-27700659/).
120
+
121
+ Output files:
122
+
123
+ - `yolo26n_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
124
+ - `yolo26n_crowdanalysis_int8.xml` / `yolo26n_crowdanalysis_int8.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
125
+
126
+ #### Precision / Device Compatibility
127
+
128
+ | Precision | CPU | GPU | NPU |
129
+ |---|---|---|---|
130
+ | FP32 | Yes | Yes | No |
131
+ | FP16 | Yes | Yes | Yes |
132
+ | INT8 | Yes | Yes | Yes |
133
+
134
+ > **Note:** The INT8 calibration uses the bundled sample image.
135
+ > For production accuracy, replace it with a representative set of frames from
136
+ > the target deployment site.
137
+
138
+ ### OpenVINO Sample
139
+
140
+ The sample below runs YOLO26 inference on the sample video, filters to the
141
+ `person` class, reports the crowd count and density level per frame, tracks each
142
+ person with a lightweight IoU tracker to estimate the dominant flow direction,
143
+ and writes the annotated result to `output_openvino.mp4`.
144
+ Change the `device` string to run on CPU, GPU, or NPU.
145
+
146
+ ```python
147
+ import cv2
148
+ import numpy as np
149
+ import openvino as ov
150
+
151
+ PERSON_CLASS_ID = 0
152
+ CONF_THRESHOLD = 0.4
153
+ INPUT_SIZE = 640
154
+
155
+ # Crowd-density thresholds (person count per frame).
156
+ DENSITY_LOW_MAX = 10 # fewer than 10 -> LOW
157
+ DENSITY_MEDIUM_MAX = 25 # 10-25 -> MEDIUM, more than 25 -> HIGH
158
+
159
+ # Movement tracking.
160
+ IOU_MATCH_THRESHOLD = 0.3
161
+ MAX_MISSED_FRAMES = 15
162
+
163
+
164
+ def density_level(count):
165
+ if count < DENSITY_LOW_MAX:
166
+ return "LOW", (0, 200, 0)
167
+ if count <= DENSITY_MEDIUM_MAX:
168
+ return "MEDIUM", (0, 200, 255)
169
+ return "HIGH", (0, 0, 255)
170
+
171
+
172
+ def iou(box_a, box_b):
173
+ ax1, ay1, ax2, ay2 = box_a
174
+ bx1, by1, bx2, by2 = box_b
175
+ ix1, iy1 = max(ax1, bx1), max(ay1, by1)
176
+ ix2, iy2 = min(ax2, bx2), min(ay2, by2)
177
+ inter = max(0, ix2 - ix1) * max(0, iy2 - iy1)
178
+ if inter == 0:
179
+ return 0.0
180
+ area_a = max(0, ax2 - ax1) * max(0, ay2 - ay1)
181
+ area_b = max(0, bx2 - bx1) * max(0, by2 - by1)
182
+ return inter / float(area_a + area_b - inter)
183
+
184
+
185
+ class CentroidTracker:
186
+ """Minimal IoU tracker that records each track's last centroid so we can
187
+ estimate per-frame movement (flow) vectors."""
188
+
189
+ def __init__(self):
190
+ self._next_id = 1
191
+ self._tracks = {} # id -> {"box", "centroid", "missed"}
192
+
193
+ def update(self, boxes):
194
+ unmatched = set(self._tracks)
195
+ assignments, moves = [], []
196
+ for box in boxes:
197
+ cx = (box[0] + box[2]) / 2.0
198
+ cy = (box[1] + box[3]) / 2.0
199
+ best_id, best_iou = None, IOU_MATCH_THRESHOLD
200
+ for tid in unmatched:
201
+ score = iou(box, self._tracks[tid]["box"])
202
+ if score > best_iou:
203
+ best_id, best_iou = tid, score
204
+ if best_id is not None:
205
+ tid = best_id
206
+ unmatched.discard(tid)
207
+ pcx, pcy = self._tracks[tid]["centroid"]
208
+ moves.append((cx - pcx, cy - pcy))
209
+ else:
210
+ tid = self._next_id
211
+ self._next_id += 1
212
+ self._tracks[tid] = {"box": box, "centroid": (cx, cy), "missed": 0}
213
+ assignments.append((box, tid))
214
+ for tid in unmatched:
215
+ self._tracks[tid]["missed"] += 1
216
+ if self._tracks[tid]["missed"] > MAX_MISSED_FRAMES:
217
+ del self._tracks[tid]
218
+ return assignments, moves
219
+
220
+
221
+ core = ov.Core()
222
+ model = core.read_model("yolo26n_openvino_model/yolo26n.xml")
223
+ compiled = core.compile_model(model, "CPU") # or "GPU", "NPU"
224
+
225
+ cap = cv2.VideoCapture("test_video.mp4")
226
+ fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
227
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
228
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
229
+ writer = cv2.VideoWriter(
230
+ "output_openvino.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
231
+
232
+ tracker = CentroidTracker()
233
+
234
+ while True:
235
+ ok, frame = cap.read()
236
+ if not ok:
237
+ break
238
+ h0, w0 = frame.shape[:2]
239
+ sx, sy = w0 / INPUT_SIZE, h0 / INPUT_SIZE
240
+
241
+ blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
242
+ blob = cv2.cvtColor(blob, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
243
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...] # NCHW
244
+
245
+ # YOLO26 end-to-end output: [1, 300, 6] = [x1, y1, x2, y2, confidence, class_id]
246
+ output = compiled([blob])[compiled.output(0)][0]
247
+ mask = (output[:, 4] >= CONF_THRESHOLD) & (output[:, 5].astype(int) == PERSON_CLASS_ID)
248
+ dets = output[mask]
249
+
250
+ boxes = [(d[0] * sx, d[1] * sy, d[2] * sx, d[3] * sy) for d in dets]
251
+ assignments, moves = tracker.update(boxes)
252
+
253
+ for box, _tid in assignments:
254
+ x1, y1, x2, y2 = (int(v) for v in box)
255
+ cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
256
+
257
+ count = len(boxes)
258
+ level, color = density_level(count)
259
+ cv2.putText(frame, f"Crowd: {count} ({level})", (10, 40),
260
+ cv2.FONT_HERSHEY_SIMPLEX, 1.0, color, 2)
261
+
262
+ # Movement: mean of all per-track displacements -> dominant flow arrow.
263
+ if moves:
264
+ mdx = float(np.mean([m[0] for m in moves]))
265
+ mdy = float(np.mean([m[1] for m in moves]))
266
+ ox, oy = width // 2, height - 40
267
+ cv2.arrowedLine(frame, (ox, oy),
268
+ (int(ox + mdx * 10), int(oy + mdy * 10)),
269
+ (255, 0, 0), 3, tipLength=0.3)
270
+ cv2.putText(frame, f"Flow dx={mdx:+.1f} dy={mdy:+.1f}", (10, 75),
271
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
272
+
273
+ writer.write(frame)
274
+
275
+ cap.release()
276
+ writer.release()
277
+ print("Saved: output_openvino.mp4")
278
+ ```
279
+
280
+ ### Try It on the Sample Video
281
+
282
+ The `export_and_quantize.sh` script downloads `test_video.mp4` automatically.
283
+ Run the OpenVINO sample above.
284
+ It reads `test_video.mp4`, prints the crowd count and density level per frame,
285
+ and writes the annotated video to `output_openvino.mp4` with a green box around
286
+ each detected person, the `Crowd: N (LEVEL)` overlay, and a blue arrow showing
287
+ the dominant crowd flow.
288
+
289
+ > **Tip:** For production testing, replace the bundled `test_video.mp4` with
290
+ > footage from your target deployment site and re-tune the density thresholds.
291
+
292
+ #### Expected Output
293
+
294
+ ![OpenVINO expected output](expected_output_openvino.gif)
295
+
296
+ ### DLStreamer Sample
297
+
298
+ The pipeline below runs the FP16 YOLO26 detector on the sample video via
299
+ `gvadetect`, assigns a stable track ID to each person with `gvatrack`, filters
300
+ detections to the `person` class in a buffer probe using the GStreamer Analytics
301
+ metadata API (`GstAnalytics`), overlays bounding boxes, and saves the annotated
302
+ result to `output_dlstreamer.mp4`. The probe prints the crowd count, density
303
+ level, and dominant flow direction per frame.
304
+
305
+ > **Notes on running this sample:**
306
+ >
307
+ > - Use the FP16 IR (`yolo26n_openvino_model/yolo26n.xml`).
308
+ > On DLStreamer 2026.0.0, `gvadetect` cannot auto-derive a YOLO post-processor
309
+ > from the INT8 model produced by the bundled script.
310
+ > To use the INT8 model, supply a matching `model-proc` JSON.
311
+ > - Class names are read automatically from the model's embedded
312
+ > `metadata.yaml` by DLStreamer 2026.0+ -- no external `labels-file` is
313
+ > required.
314
+ > - Filtering with `object-class=person` directly on `gvadetect` is rejected
315
+ > when `inference-region` is `full-frame` (the default), so the sample
316
+ > filters by detection label in the buffer probe instead.
317
+ > - Export `PYTHONPATH` so the DLStreamer Python module is importable:
318
+ >
319
+ > ```bash
320
+ > source /opt/intel/openvino_2026/setupvars.sh
321
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
322
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
323
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
324
+ > ```
325
+
326
+ ```python
327
+ import gi
328
+
329
+ gi.require_version("Gst", "1.0")
330
+ gi.require_version("GstAnalytics", "1.0")
331
+ from gi.repository import Gst, GLib, GstAnalytics
332
+
333
+ Gst.init([])
334
+
335
+ INPUT_VIDEO = "test_video.mp4"
336
+
337
+ # Crowd-density thresholds (person count per frame).
338
+ DENSITY_LOW_MAX = 10 # fewer than 10 -> LOW
339
+ DENSITY_MEDIUM_MAX = 25 # 10-25 -> MEDIUM, more than 25 -> HIGH
340
+
341
+
342
+ def density_level(count):
343
+ if count < DENSITY_LOW_MAX:
344
+ return "LOW"
345
+ if count <= DENSITY_MEDIUM_MAX:
346
+ return "MEDIUM"
347
+ return "HIGH"
348
+
349
+
350
+ # For CPU: change device=GPU to device=CPU.
351
+ # For NPU: change device=GPU to device=NPU (batch-size=1, nireq=4 recommended).
352
+ pipeline_str = (
353
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
354
+ "videoconvert ! "
355
+ "gvadetect model=yolo26n_openvino_model/yolo26n.xml "
356
+ "device=GPU "
357
+ "threshold=0.4 ! queue ! "
358
+ "gvatrack tracking-type=zero-term-imageless ! queue ! "
359
+ "gvawatermark displ-cfg=show-roi=person ! "
360
+ "videoconvert ! video/x-raw,format=I420 ! "
361
+ "openh264enc ! h264parse ! "
362
+ "mp4mux ! filesink name=sink location=output_dlstreamer.mp4"
363
+ )
364
+ pipeline = Gst.parse_launch(pipeline_str)
365
+
366
+ sink = pipeline.get_by_name("sink")
367
+ sink_pad = sink.get_static_pad("sink")
368
+
369
+ prev_centroid = {} # track_id -> (cx, cy)
370
+
371
+
372
+ def on_buffer(pad, info):
373
+ buf = info.get_buffer()
374
+ rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
375
+ if rmeta is None:
376
+ return Gst.PadProbeReturn.OK
377
+
378
+ # OD and tracking metadata share one id space and can be interleaved
379
+ # (id=1 -> ODMtd, id=2 -> TrackingMtd, ...), so scan every id and stop only
380
+ # after several consecutive misses.
381
+ ods, tracks = [], []
382
+ idx, misses = 1, 0
383
+ while misses < 20:
384
+ ok_od, od = rmeta.get_od_mtd(idx)
385
+ ok_trk, trk = rmeta.get_tracking_mtd(idx)
386
+ if ok_od:
387
+ ods.append(od)
388
+ misses = 0
389
+ elif ok_trk:
390
+ tracks.append(trk)
391
+ misses = 0
392
+ else:
393
+ misses += 1
394
+ idx += 1
395
+
396
+ count, moves = 0, []
397
+ for od in ods:
398
+ if GLib.quark_to_string(od.get_obj_type()) != "person":
399
+ continue
400
+ count += 1
401
+ _, x, y, w, h, _ = od.get_location()
402
+ cx, cy = x + w / 2.0, y + h / 2.0
403
+ for trk in tracks:
404
+ if rmeta.get_relation(od.id, trk.id) == GstAnalytics.RelTypes.NONE:
405
+ continue
406
+ ok_trk, track_id, _, _, _ = trk.get_info()
407
+ if not ok_trk:
408
+ continue
409
+ if track_id in prev_centroid:
410
+ pcx, pcy = prev_centroid[track_id]
411
+ moves.append((cx - pcx, cy - pcy))
412
+ prev_centroid[track_id] = (cx, cy)
413
+ break
414
+
415
+ if count:
416
+ level = density_level(count)
417
+ if moves:
418
+ mdx = sum(m[0] for m in moves) / len(moves)
419
+ mdy = sum(m[1] for m in moves) / len(moves)
420
+ print(f"Crowd: {count} ({level}) flow dx={mdx:+.1f} dy={mdy:+.1f}",
421
+ flush=True)
422
+ else:
423
+ print(f"Crowd: {count} ({level})", flush=True)
424
+ return Gst.PadProbeReturn.OK
425
+
426
+
427
+ sink_pad.add_probe(Gst.PadProbeType.BUFFER, on_buffer)
428
+
429
+ pipeline.set_state(Gst.State.PLAYING)
430
+ bus = pipeline.get_bus()
431
+ bus.timed_pop_filtered(
432
+ Gst.CLOCK_TIME_NONE,
433
+ Gst.MessageType.EOS | Gst.MessageType.ERROR,
434
+ )
435
+ pipeline.set_state(Gst.State.NULL)
436
+ ```
437
+
438
+ #### Expected Output
439
+
440
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
441
+
442
+ **Device targets:**
443
+
444
+ - `device=GPU` -- default in the sample code.
445
+ - `device=CPU` -- change `device=GPU` to `device=CPU`.
446
+ - `device=NPU` -- change `device=GPU` to `device=NPU`; use `batch-size=1` and `nireq=4` for best NPU utilization.
447
+
448
+ ---
449
+
450
+ ## License
451
+
452
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
453
+
454
+ ## References
455
+
456
+ - [YOLO26 Documentation](https://docs.ultralytics.com/models/yolo26/)
457
+ - [OpenVINO YOLO26 Notebook](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov26-optimization/yolov26-object-detection.ipynb)
458
+ - [COCO Dataset](https://cocodataset.org/)
459
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
460
+ - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
461
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
462
+ - [Sample video: Pedestrians crossing the street (Pexels)](https://www.pexels.com/video/pedestrians-crossing-the-street-27700659/)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: 67c196beb62cd66fad591f8a32e32becae02e13fc4595597b463681894d31067
  • Pointer size: 133 Bytes
  • Size of remote file: 30.5 MB
expected_output_openvino.gif ADDED

Git LFS Details

  • SHA256: e80ffcc98a727c8214cf7060b45e0c73023c6d8d2e9789bc89056e2c9a40d445
  • Pointer size: 133 Bytes
  • Size of remote file: 38.8 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Export a YOLO26 person detector for crowd analysis to OpenVINO IR.
6
+ # Usage: ./export_and_quantize.sh [MODEL_VARIANT] [PRECISION]
7
+ # Example: ./export_and_quantize.sh yolo26n FP16
8
+ #
9
+ # Supported precisions:
10
+ # FP32 -- Full-precision floating-point weights
11
+ # FP16 -- Half-precision floating-point weights (default)
12
+ # INT8 -- Quantized 8-bit integer weights (requires NNCF)
13
+ #
14
+ # Precision / device compatibility:
15
+ # | Precision | CPU | GPU | NPU |
16
+ # |-----------|-----|-----|-----|
17
+ # | FP32 | Yes | Yes | No |
18
+ # | FP16 | Yes | Yes | Yes |
19
+ # | INT8 | Yes | Yes | Yes |
20
+
21
+ set -euo pipefail
22
+
23
+ MODEL_NAME="${1:-yolo26n}"
24
+ PRECISION="${2:-FP16}"
25
+ PRECISION="$(echo "${PRECISION}" | tr '[:lower:]' '[:upper:]')"
26
+
27
+ if [[ "${PRECISION}" != "FP32" && "${PRECISION}" != "FP16" && "${PRECISION}" != "INT8" ]]; then
28
+ echo "ERROR: unsupported precision '${PRECISION}'. Choose FP32, FP16, or INT8." >&2
29
+ exit 1
30
+ fi
31
+
32
+ echo "--- Installing dependencies ---"
33
+ if [[ "${PRECISION}" == "INT8" ]]; then
34
+ pip install -qU openvino nncf ultralytics
35
+ else
36
+ pip install -qU openvino ultralytics
37
+ fi
38
+
39
+ # Ask for approval before downloading models and sample files
40
+ echo ""
41
+ echo "This script will download:"
42
+ echo " - YOLO26 model weights (if not cached locally)"
43
+ echo " - Sample test image and video files"
44
+ echo ""
45
+ read -p "Continue with downloads? (yes/no): " APPROVAL
46
+ if [[ "${APPROVAL}" != "yes" ]]; then
47
+ echo "Download cancelled by user."
48
+ exit 0
49
+ fi
50
+ echo ""
51
+
52
+ echo "--- Downloading sample test image ---"
53
+ if [[ ! -f test.jpg ]]; then
54
+ wget -q -O test.jpg https://ultralytics.com/images/bus.jpg
55
+ echo "Downloaded: test.jpg"
56
+ else
57
+ echo "Already present: test.jpg"
58
+ fi
59
+
60
+ echo "--- Downloading sample test video ---"
61
+ # Pedestrians crossing the street (Pexels, free to use). The /download/ URL
62
+ # redirects to the videos.pexels.com CDN; a browser User-Agent is required.
63
+ if [[ ! -f test_video.mp4 ]]; then
64
+ wget -q -U "Mozilla/5.0" -O test_video.mp4 \
65
+ https://www.pexels.com/download/video/27700659/
66
+ echo "Downloaded: test_video.mp4"
67
+ else
68
+ echo "Already present: test_video.mp4"
69
+ fi
70
+
71
+ if [[ "${PRECISION}" == "FP32" ]]; then
72
+ HALF_FLAG="False"
73
+ EXPORT_LABEL="FP32"
74
+ else
75
+ HALF_FLAG="True"
76
+ EXPORT_LABEL="FP16"
77
+ fi
78
+
79
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
80
+ python3 -c "
81
+ from ultralytics import YOLO
82
+
83
+ model = YOLO('${MODEL_NAME}.pt')
84
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
85
+ print('Export complete: ${MODEL_NAME}_openvino_model/')
86
+ "
87
+
88
+ if [[ "${PRECISION}" == "INT8" ]]; then
89
+ echo "--- Quantizing to INT8 with NNCF ---"
90
+ python3 -c "
91
+ import nncf
92
+ import openvino as ov
93
+ import numpy as np
94
+ import cv2
95
+
96
+ core = ov.Core()
97
+ model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
98
+
99
+ # Use the downloaded test image for calibration instead of random noise.
100
+ img = cv2.imread('test.jpg')
101
+ img = cv2.resize(img, (640, 640))
102
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
103
+ img = img.transpose(2, 0, 1)[np.newaxis, ...] # NCHW
104
+
105
+ def transform_fn(data_item):
106
+ return img
107
+
108
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
109
+
110
+ quantized = nncf.quantize(
111
+ model,
112
+ calibration_dataset,
113
+ preset=nncf.QuantizationPreset.MIXED,
114
+ subset_size=300,
115
+ )
116
+
117
+ ov.save_model(quantized, '${MODEL_NAME}_crowdanalysis_int8.xml')
118
+ print('Quantization complete: ${MODEL_NAME}_crowdanalysis_int8.xml')
119
+ "
120
+ fi
121
+ echo "--- Done ---"