vagheshpatel commited on
Commit
c01fa2f
·
verified ·
1 Parent(s): 7cb28ab

Sync running-detection 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,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ - running-detection
12
+ - speed-estimation
13
+ - tracking
14
+ - edge-ai
15
+ - metro
16
+ - dlstreamer
17
+ language:
18
+ - en
19
+ ---
20
+
21
+ # Running Detection
22
+
23
+ | Property | Value |
24
+ |---|---|
25
+ | **Category** | Object Detection + Tracking + Speed Estimation |
26
+ | **Base Model** | [YOLO26](https://docs.ultralytics.com/models/yolo26/) (Ultralytics) + DLStreamer `gvatrack` (Kalman filter tracker) |
27
+ | **Source Framework** | PyTorch (Ultralytics) |
28
+ | **Supported Precisions** | FP32, FP16, INT8 (mixed-precision) |
29
+ | **Inference Engine** | OpenVINO |
30
+ | **Hardware** | CPU, GPU, NPU |
31
+ | **Detected Class** | `person` (COCO class 0) |
32
+
33
+ ---
34
+
35
+ ## Overview
36
+
37
+ Running Detection is a Metro Analytics use case that flags people who are running or moving faster than a configurable speed threshold.
38
+ It is built on [YOLO26](https://docs.ultralytics.com/models/yolo26/), a state-of-the-art real-time object detector trained on the COCO dataset, quantized to INT8 and filtered at runtime to the `person` class.
39
+ Each detected person is assigned a persistent track ID across frames, and per-track speed is estimated from the frame-to-frame displacement of the bounding-box center.
40
+ A person is flagged as running when the estimated speed stays above the threshold for a short, sustained window, which suppresses single-frame jitter.
41
+
42
+ Typical Metro deployments include:
43
+
44
+ - **Platform Safety** -- flag people sprinting across platforms or toward closing train doors.
45
+ - **Incident Detection** -- surface sudden running that may indicate a chase, altercation, or emergency.
46
+ - **Crowd Flow Monitoring** -- distinguish normal walking pace from abnormal fast movement in concourses.
47
+ - **Restricted-Speed Zones** -- enforce walk-only areas such as escalators, ramps, and stairwells.
48
+
49
+ Available variants: `yolo26n`, `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`.
50
+ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge deployment; larger variants improve recall in dense scenes.
51
+
52
+ ---
53
+
54
+ ## Prerequisites
55
+
56
+ - Python 3.11+
57
+ - `ffmpeg` (`sudo apt install ffmpeg`) -- used by the samples to encode output video
58
+ - [Install OpenVINO](https://docs.openvino.ai/2026/get-started/install-openvino.html) (latest version)
59
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html) (latest version)
60
+
61
+ Create and activate a Python virtual environment before running the scripts:
62
+
63
+ ```bash
64
+ python3 -m venv .venv --system-site-packages
65
+ source .venv/bin/activate
66
+ ```
67
+
68
+ > **Note:** The `--system-site-packages` flag is required so the virtual
69
+ > environment can access the system-installed OpenVINO and DLStreamer Python
70
+ > packages.
71
+
72
+ ---
73
+
74
+ ## Getting Started
75
+
76
+ ### Download and Quantize Model
77
+
78
+ Run the provided script to download, export to OpenVINO IR, and optionally quantize:
79
+
80
+ ```bash
81
+ chmod +x export_and_quantize.sh
82
+ ./export_and_quantize.sh
83
+ ```
84
+
85
+ This exports the default **yolo26n** model in **FP16** precision.
86
+
87
+ #### Optional: Select a Different Variant or Precision
88
+
89
+ ```bash
90
+ ./export_and_quantize.sh yolo26n FP32 # full-precision
91
+ ./export_and_quantize.sh yolo26n INT8 # quantized
92
+ ./export_and_quantize.sh yolo26s # larger variant, default FP16
93
+ ```
94
+
95
+ Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
96
+ The second argument selects the precision (`FP32`, `FP16`, `INT8`); the default is **FP16**.
97
+
98
+ The script performs the following steps:
99
+
100
+ 1. Installs dependencies (`openvino`, `ultralytics`, `opencv-python`; adds `nncf` for INT8).
101
+ 2. Downloads the sample running video (`running.mp4`) and extracts a calibration frame (`test.jpg`).
102
+ 3. Downloads the PyTorch weights and exports to OpenVINO IR.
103
+ 4. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
104
+
105
+ Output files:
106
+
107
+ - `yolo26n_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
108
+ - `yolo26n_running_int8.xml` / `yolo26n_running_int8.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
109
+
110
+ #### Precision / Device Compatibility
111
+
112
+ | Precision | CPU | GPU | NPU |
113
+ |---|---|---|---|
114
+ | FP32 | Yes | Yes | No |
115
+ | FP16 | Yes | Yes | Yes |
116
+ | INT8 | Yes | Yes | Yes |
117
+
118
+ > **Note:** The INT8 calibration uses the extracted sample frame.
119
+ > For production accuracy, replace it with a representative set of frames from
120
+ > the target deployment site.
121
+
122
+ ### Speed Threshold
123
+
124
+ Running is defined by a per-track speed threshold expressed in pixels per second:
125
+
126
+ ```text
127
+ RUNNING_SPEED = 250.0 # pixels/second (demo value for the sample clip)
128
+ MIN_RUN_FRAMES = 3 # sustained frames above the threshold before flagging
129
+ ```
130
+
131
+ > **Note:** Pixel speed depends on camera resolution, framing, and distance to
132
+ > the subject, so `RUNNING_SPEED` must be tuned per site. For a calibrated
133
+ > metric speed (meters/second), convert pixel displacement using the known
134
+ > ground-sampling distance of the scene.
135
+
136
+ ### OpenVINO Sample
137
+
138
+ The sample below runs YOLO26 inference on the sample video, filters to the `person` class, assigns track IDs with a lightweight nearest-center tracker, estimates per-track pixel speed, and flags people who run faster than `RUNNING_SPEED` for at least `MIN_RUN_FRAMES` frames.
139
+ YOLO26 is end-to-end (NMS-free), so no manual non-maximum suppression is needed.
140
+ The annotated result is written to `output_openvino.mp4`, with a latched
141
+ `RUNNING DETECTED` / `NO RUNNING DETECTED` status banner across the top.
142
+ Change the `DEVICE` string to run on CPU, GPU, or NPU.
143
+
144
+ ```python
145
+ import subprocess
146
+
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
+ RUNNING_SPEED = 250.0 # pixels/second
155
+ MIN_RUN_FRAMES = 3 # sustained frames above the threshold before flagging
156
+ MAX_MATCH_DIST = 120 # max center distance (px) to link a track across frames
157
+ ALERT_HOLD_SECONDS = 2.0 # latch the alert banner to keep it from flickering
158
+
159
+ # Change DEVICE to "GPU" or "NPU" to run on integrated GPU or NPU.
160
+ DEVICE = "CPU"
161
+ INPUT_VIDEO = "running.mp4"
162
+
163
+ core = ov.Core()
164
+ model = core.read_model("yolo26n_openvino_model/yolo26n.xml")
165
+ compiled = core.compile_model(model, DEVICE)
166
+ output_port = compiled.output(0)
167
+
168
+ cap = cv2.VideoCapture(INPUT_VIDEO)
169
+ fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
170
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
171
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
172
+ ALERT_HOLD_FRAMES = max(1, int(ALERT_HOLD_SECONDS * fps))
173
+
174
+ proc = subprocess.Popen(
175
+ ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "bgr24",
176
+ "-s", f"{width}x{height}", "-r", str(fps),
177
+ "-i", "pipe:0", "-c:v", "libx264", "-pix_fmt", "yuv420p",
178
+ "-movflags", "+faststart", "output_openvino.mp4"],
179
+ stdin=subprocess.PIPE, stderr=subprocess.DEVNULL,
180
+ )
181
+
182
+ tracks: dict[int, dict] = {} # id -> {cx, cy, run_frames}
183
+ next_id = 0
184
+ flagged: set[int] = set()
185
+ alert_hold = 0
186
+ frame_idx = 0
187
+
188
+ while True:
189
+ ok, frame = cap.read()
190
+ if not ok:
191
+ break
192
+ frame_idx += 1
193
+ dt = 1.0 / fps
194
+
195
+ blob = cv2.resize(frame, (INPUT_SIZE, INPUT_SIZE))
196
+ blob = cv2.cvtColor(blob, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
197
+ blob = blob.transpose(2, 0, 1)[np.newaxis, ...] # NCHW
198
+
199
+ output = compiled([blob])[output_port][0]
200
+ mask = (output[:, 4] >= CONF_THRESHOLD) & (output[:, 5].astype(int) == PERSON_CLASS_ID)
201
+ dets = output[mask]
202
+
203
+ sx, sy = width / INPUT_SIZE, height / INPUT_SIZE
204
+ detections = []
205
+ for det in dets:
206
+ x1, y1 = int(det[0] * sx), int(det[1] * sy)
207
+ x2, y2 = int(det[2] * sx), int(det[3] * sy)
208
+ detections.append((x1, y1, x2, y2, (x1 + x2) // 2, (y1 + y2) // 2))
209
+
210
+ # Greedy nearest-center association to the previous frame's tracks.
211
+ used = set()
212
+ assignments = {}
213
+ for i, (_, _, _, _, cx, cy) in enumerate(detections):
214
+ best_id, best_dist = None, MAX_MATCH_DIST
215
+ for tid, tr in tracks.items():
216
+ if tid in used:
217
+ continue
218
+ d = np.hypot(cx - tr["cx"], cy - tr["cy"])
219
+ if d < best_dist:
220
+ best_id, best_dist = tid, d
221
+ if best_id is None:
222
+ best_id = next_id
223
+ next_id += 1
224
+ tracks[best_id] = {"cx": cx, "cy": cy, "run_frames": 0}
225
+ used.add(best_id)
226
+ assignments[i] = best_id
227
+
228
+ new_tracks = {}
229
+ frame_running = False
230
+ for i, (x1, y1, x2, y2, cx, cy) in enumerate(detections):
231
+ tid = assignments[i]
232
+ prev = tracks.get(tid, {"cx": cx, "cy": cy, "run_frames": 0})
233
+ speed = np.hypot(cx - prev["cx"], cy - prev["cy"]) / dt
234
+ run_frames = prev["run_frames"] + 1 if speed >= RUNNING_SPEED else 0
235
+ new_tracks[tid] = {"cx": cx, "cy": cy, "run_frames": run_frames}
236
+
237
+ is_running = run_frames >= MIN_RUN_FRAMES
238
+ frame_running = frame_running or is_running
239
+ color = (0, 0, 255) if is_running else (0, 255, 0)
240
+ cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
241
+ label = f"RUNNING {int(speed)}px/s" if is_running else f"{int(speed)}px/s"
242
+ cv2.putText(frame, label, (x1, max(y1 - 8, 12)),
243
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
244
+ if is_running and tid not in flagged:
245
+ flagged.add(tid)
246
+ print(f"RUNNING id={tid} speed={int(speed)}px/s frame={frame_idx}", flush=True)
247
+
248
+ tracks = new_tracks
249
+
250
+ # Latch the alert so the banner reflects a sustained state, not a single
251
+ # transient frame: once running is seen it stays on for ALERT_HOLD_FRAMES.
252
+ alert_hold = ALERT_HOLD_FRAMES if frame_running else max(0, alert_hold - 1)
253
+ alert_on = alert_hold > 0
254
+ banner = "RUNNING DETECTED" if alert_on else "NO RUNNING DETECTED"
255
+ banner_color = (0, 0, 255) if alert_on else (0, 180, 0)
256
+ cv2.rectangle(frame, (0, 0), (width, 40), (0, 0, 0), -1)
257
+ cv2.putText(frame, banner, (10, 28),
258
+ cv2.FONT_HERSHEY_SIMPLEX, 0.9, banner_color, 2)
259
+
260
+ proc.stdin.write(frame.tobytes())
261
+
262
+ cap.release()
263
+ proc.stdin.close()
264
+ proc.wait()
265
+ print("Wrote output_openvino.mp4", flush=True)
266
+ ```
267
+
268
+ **Device targets:**
269
+
270
+ - `"CPU"` -- default, works on all Intel platforms.
271
+ - `"GPU"` -- Intel integrated or discrete GPU.
272
+ - `"NPU"` -- Intel NPU (different throughput profile; validate with `benchmark_app -d NPU`).
273
+
274
+ Expected console output:
275
+
276
+ ```text
277
+ RUNNING id=0 speed=312px/s frame=14
278
+ ...
279
+ Wrote output_openvino.mp4
280
+ ```
281
+
282
+ `output_openvino.mp4` shows a green box around each person, turning red with a
283
+ `RUNNING` label when the person's speed exceeds the threshold.
284
+
285
+ #### Expected Output
286
+
287
+ ![OpenVINO expected output](expected_output_openvino.gif)
288
+
289
+ ### DLStreamer Sample
290
+
291
+ The pipeline below runs the FP16 YOLO26 detector via `gvadetect` on the sample
292
+ video, attaches persistent track IDs with `gvatrack`
293
+ (`short-term-imageless` tracker), and overlays bounding boxes with
294
+ `gvawatermark`. Frames are pulled from an `appsink`; per-track pixel speed is
295
+ computed from the frame-to-frame displacement of each track center, and a
296
+ `RUNNING` event is raised when the speed stays above `RUNNING_SPEED` for at
297
+ least `MIN_RUN_FRAMES` frames. A latched `RUNNING DETECTED` /
298
+ `NO RUNNING DETECTED` status banner is drawn across the top of every frame.
299
+ `gvawatermark` renders boxes for the `person` class only. The annotated result
300
+ is muxed to `output_dlstreamer.mp4`.
301
+
302
+ > **Notes on running this sample:**
303
+ >
304
+ > - Use the FP16 IR (`yolo26n_openvino_model/yolo26n.xml`). Class names are
305
+ > read automatically from the model's embedded `metadata.yaml` by
306
+ > DLStreamer 2026.0+ -- no external `labels-file` is required.
307
+ > - Export `PYTHONPATH` so the DLStreamer Python module is importable:
308
+ >
309
+ > ```bash
310
+ > source /opt/intel/openvino_2026/setupvars.sh
311
+ > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
312
+ > export PYTHONPATH=/opt/intel/dlstreamer/python:\
313
+ > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
314
+ > ```
315
+
316
+ ```python
317
+ import subprocess
318
+ from collections import defaultdict
319
+
320
+ import numpy as np
321
+ import gi
322
+
323
+ gi.require_version("Gst", "1.0")
324
+ gi.require_version("GstAnalytics", "1.0")
325
+ from gi.repository import Gst, GLib, GstAnalytics
326
+
327
+ Gst.init([])
328
+
329
+ # Import cv2 after Gst.init to avoid GStreamer re-initialization conflicts.
330
+ import cv2
331
+
332
+ INPUT_VIDEO = "running.mp4"
333
+ RUNNING_SPEED = 250.0 # pixels/second
334
+ MIN_RUN_FRAMES = 3 # sustained frames above the threshold before flagging
335
+ ALERT_HOLD_SECONDS = 2.0 # latch the alert banner to keep it from flickering
336
+
337
+ # For CPU: change device=GPU to device=CPU.
338
+ # For NPU: change device=GPU to device=NPU (batch-size=1, nireq=4 recommended).
339
+ # gvawatermark draws only person ROIs (displ-cfg=show-roi=person) so boxes for
340
+ # other COCO classes are not rendered.
341
+ pipeline_str = (
342
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
343
+ "videoconvert ! "
344
+ "gvadetect model=yolo26n_openvino_model/yolo26n.xml "
345
+ "device=GPU "
346
+ "threshold=0.4 ! queue ! "
347
+ "gvatrack tracking-type=short-term-imageless ! queue ! "
348
+ "gvawatermark displ-cfg=show-roi=person ! appsink name=sink emit-signals=false sync=false"
349
+ )
350
+ pipeline = Gst.parse_launch(pipeline_str)
351
+ appsink = pipeline.get_by_name("sink")
352
+
353
+ pipeline.set_state(Gst.State.PLAYING)
354
+
355
+ proc = None
356
+ prev_center: dict[int, tuple[int, int]] = {}
357
+ run_frames: dict[int, int] = defaultdict(int)
358
+ flagged: set[int] = set()
359
+ prev_pts = None
360
+ alert_hold = 0
361
+ alert_hold_frames = 40 # updated from the real framerate on the first frame
362
+ frame_idx = 0
363
+
364
+ while True:
365
+ sample = appsink.emit("pull-sample")
366
+ if sample is None:
367
+ break
368
+
369
+ buf = sample.get_buffer()
370
+ caps = sample.get_caps()
371
+ struct = caps.get_structure(0)
372
+ width = struct.get_value("width")
373
+ height = struct.get_value("height")
374
+ frame_idx += 1
375
+
376
+ # Start ffmpeg encoder on the first frame.
377
+ if proc is None:
378
+ ok, fps_num, fps_den = struct.get_fraction("framerate")
379
+ fps = fps_num / fps_den if ok and fps_den > 0 else 25.0
380
+ alert_hold_frames = max(1, int(ALERT_HOLD_SECONDS * fps))
381
+ proc = subprocess.Popen(
382
+ ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "bgr24",
383
+ "-s", f"{width}x{height}", "-r", str(fps),
384
+ "-i", "pipe:0", "-c:v", "libx264", "-pix_fmt", "yuv420p",
385
+ "-movflags", "+faststart", "output_dlstreamer.mp4"],
386
+ stdin=subprocess.PIPE, stderr=subprocess.DEVNULL,
387
+ )
388
+
389
+ # Elapsed time since the previous frame from buffer timestamps.
390
+ pts = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else frame_idx / fps
391
+ dt = (pts - prev_pts) if (prev_pts is not None and pts > prev_pts) else 1.0 / fps
392
+ prev_pts = pts
393
+
394
+ # Read detection / tracking metadata via GstAnalytics.
395
+ rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
396
+ regions = []
397
+ if rmeta is not None:
398
+ od_entries = []
399
+ trk_map = {} # metadata_id -> tracking_id
400
+ idx = 1
401
+ while True:
402
+ ok_od, od = rmeta.get_od_mtd(idx)
403
+ ok_trk, trk = rmeta.get_tracking_mtd(idx)
404
+ if not ok_od and not ok_trk:
405
+ break
406
+ if ok_od:
407
+ label = GLib.quark_to_string(od.get_obj_type())
408
+ _, x, y, w, h, _ = od.get_location()
409
+ od_entries.append((idx, label, int(x + w / 2), int(y + h / 2)))
410
+ if ok_trk:
411
+ ok2, tid, _, _, _ = trk.get_info()
412
+ if ok2:
413
+ trk_map[idx] = tid
414
+ idx += 1
415
+ for od_id, label, cx, cy in od_entries:
416
+ if label != "person":
417
+ continue
418
+ tid = 0
419
+ for trk_meta_id, tracking_id in trk_map.items():
420
+ if rmeta.get_relation(od_id, trk_meta_id) != GstAnalytics.RelTypes.NONE:
421
+ tid = tracking_id
422
+ break
423
+ regions.append((tid, cx, cy))
424
+
425
+ # Map buffer read-only and copy pixels to a writable numpy array.
426
+ success, map_info = buf.map(Gst.MapFlags.READ)
427
+ if not success:
428
+ continue
429
+ arr = np.ndarray((height, width, 3), dtype=np.uint8,
430
+ buffer=map_info.data).copy()
431
+ buf.unmap(map_info)
432
+
433
+ frame_running = False
434
+ for tid, cx, cy in regions:
435
+ px, py = prev_center.get(tid, (cx, cy))
436
+ speed = np.hypot(cx - px, cy - py) / dt if dt > 0 else 0.0
437
+ prev_center[tid] = (cx, cy)
438
+ run_frames[tid] = run_frames[tid] + 1 if speed >= RUNNING_SPEED else 0
439
+
440
+ if run_frames[tid] >= MIN_RUN_FRAMES:
441
+ frame_running = True
442
+ cv2.putText(arr, f"RUNNING {int(speed)}px/s", (cx - 40, max(cy - 20, 12)),
443
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
444
+ if tid not in flagged:
445
+ flagged.add(tid)
446
+ print(f"RUNNING id={tid} speed={int(speed)}px/s frame={frame_idx}", flush=True)
447
+
448
+ # Latch the alert so the banner reflects a sustained state, not a single
449
+ # transient frame: once running is seen it stays on for alert_hold_frames.
450
+ alert_hold = alert_hold_frames if frame_running else max(0, alert_hold - 1)
451
+ alert_on = alert_hold > 0
452
+ banner = "RUNNING DETECTED" if alert_on else "NO RUNNING DETECTED"
453
+ banner_color = (0, 0, 255) if alert_on else (0, 180, 0)
454
+ cv2.rectangle(arr, (0, 0), (width, 40), (0, 0, 0), -1)
455
+ cv2.putText(arr, banner, (10, 28),
456
+ cv2.FONT_HERSHEY_SIMPLEX, 0.9, banner_color, 2)
457
+
458
+ proc.stdin.write(arr.tobytes())
459
+
460
+ pipeline.set_state(Gst.State.NULL)
461
+ if proc:
462
+ proc.stdin.close()
463
+ proc.wait()
464
+ print("Wrote output_dlstreamer.mp4", flush=True)
465
+ ```
466
+
467
+ #### Expected Output
468
+
469
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
470
+
471
+ **Device targets:**
472
+
473
+ - `device=GPU` -- default in the sample code.
474
+ - `device=CPU` -- change `device=GPU` to `device=CPU`.
475
+ - `device=NPU` -- change `device=GPU` to `device=NPU`; use `batch-size=1` and `nireq=4` for best NPU utilization.
476
+
477
+ ---
478
+
479
+ ## License
480
+
481
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
482
+
483
+ ## References
484
+
485
+ - [YOLO26 Documentation](https://docs.ultralytics.com/models/yolo26/)
486
+ - [Ultralytics Multi-Object Tracking](https://docs.ultralytics.com/modes/track/)
487
+ - [Intel DLStreamer gvatrack](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/elements/gvatrack.html)
488
+ - [OpenVINO YOLO26 Notebook](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov26-optimization/yolov26-object-detection.ipynb)
489
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
490
+ - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
491
+ - [Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/index.html)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: 540fbe0da5653b439fa6668b4cff5ad7ce812e942f2460e4d82e603a205a4e33
  • Pointer size: 133 Bytes
  • Size of remote file: 26.8 MB
expected_output_openvino.gif ADDED

Git LFS Details

  • SHA256: f2b82067634f39529bcce2ea65a309118b06b6defbd70ca00f8f81604210e8a8
  • Pointer size: 133 Bytes
  • Size of remote file: 26.3 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Export a YOLO26 person detector to OpenVINO IR for running detection.
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
+ # Pre-downscaled sample clip of a man running on an outdoor track (720x1280, 25 fps).
33
+ VIDEO_URL="https://www.pexels.com/download/video/37709462/?fps=25.0&h=1280&w=720"
34
+
35
+ echo "--- Installing dependencies ---"
36
+ if [[ "${PRECISION}" == "INT8" ]]; then
37
+ pip install -qU openvino nncf ultralytics opencv-python
38
+ else
39
+ pip install -qU openvino ultralytics opencv-python
40
+ fi
41
+
42
+ # Ask for approval before downloading models and sample files
43
+ echo ""
44
+ echo "This script will download:"
45
+ echo " - YOLO26 model weights (if not cached locally)"
46
+ echo " - Sample running video and a calibration frame"
47
+ echo ""
48
+ read -p "Continue with downloads? (yes/no): " APPROVAL
49
+ if [[ "${APPROVAL}" != "yes" ]]; then
50
+ echo "Download cancelled by user."
51
+ exit 0
52
+ fi
53
+ echo ""
54
+
55
+ echo "--- Downloading sample running video ---"
56
+ if [[ ! -f running.mp4 ]]; then
57
+ wget -q -O running.mp4 "${VIDEO_URL}"
58
+ echo "Downloaded: running.mp4"
59
+ else
60
+ echo "Already present: running.mp4"
61
+ fi
62
+
63
+ echo "--- Extracting a calibration frame (test.jpg) ---"
64
+ if [[ ! -f test.jpg ]]; then
65
+ python3 -c "
66
+ import cv2
67
+ cap = cv2.VideoCapture('running.mp4')
68
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 30)
69
+ ok, frame = cap.read()
70
+ if not ok:
71
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
72
+ ok, frame = cap.read()
73
+ cap.release()
74
+ if not ok:
75
+ raise SystemExit('Could not read a frame from running.mp4')
76
+ cv2.imwrite('test.jpg', frame)
77
+ print('Extracted: test.jpg')
78
+ "
79
+ else
80
+ echo "Already present: test.jpg"
81
+ fi
82
+
83
+ if [[ "${PRECISION}" == "FP32" ]]; then
84
+ HALF_FLAG="False"
85
+ EXPORT_LABEL="FP32"
86
+ else
87
+ HALF_FLAG="True"
88
+ EXPORT_LABEL="FP16"
89
+ fi
90
+
91
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
92
+ python3 -c "
93
+ from ultralytics import YOLO
94
+
95
+ model = YOLO('${MODEL_NAME}.pt')
96
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
97
+ print('Export complete: ${MODEL_NAME}_openvino_model/')
98
+ "
99
+
100
+ if [[ "${PRECISION}" == "INT8" ]]; then
101
+ echo "--- Quantizing to INT8 with NNCF ---"
102
+ python3 -c "
103
+ import nncf
104
+ import openvino as ov
105
+ import numpy as np
106
+ import cv2
107
+
108
+ core = ov.Core()
109
+ model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
110
+
111
+ # Use the extracted calibration frame instead of random noise.
112
+ img = cv2.imread('test.jpg')
113
+ img = cv2.resize(img, (640, 640))
114
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
115
+ img = img.transpose(2, 0, 1)[np.newaxis, ...] # NCHW
116
+
117
+ def transform_fn(data_item):
118
+ return img
119
+
120
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
121
+
122
+ quantized = nncf.quantize(
123
+ model,
124
+ calibration_dataset,
125
+ preset=nncf.QuantizationPreset.MIXED,
126
+ subset_size=300,
127
+ )
128
+
129
+ ov.save_model(quantized, '${MODEL_NAME}_running_int8.xml')
130
+ print('Quantization complete: ${MODEL_NAME}_running_int8.xml')
131
+ "
132
+ fi
133
+ echo "--- Done ---"