Views
No views yet
| Property | Value |
|---|---|
| Category | Object Detection + Tracking + Zone Analytics (GstAnalytics) |
| Source Framework | PyTorch (Ultralytics) |
| Supported Precisions | FP32, FP16, INT8 (mixed-precision) |
| Inference Engine | OpenVINO |
| Hardware | CPU, GPU, NPU |
| Detected Class | person (COCO class 0) |
gvaanalytics element defines the monitoring zone and automatically attaches GstAnalyticsZoneMtd metadata to every tracked person whose center falls inside the polygon.
A Python probe reads this GstAnalytics metadata to accumulate per-person dwell time and raises a loitering event when the threshold is exceeded.yolo26n, yolo26s, yolo26m, yolo26l, yolo26x.
Smaller variants (yolo26n, yolo26s) are recommended for high-FPS edge deployment.1python3 -m venv .venv --system-site-packages
2source .venv/bin/activateNote: The--system-site-packagesflag is required so the virtual environment can access the system-installed OpenVINO and DLStreamer Python packages.
1chmod +x export_and_quantize.sh
2./export_and_quantize.sh1./export_and_quantize.sh yolo26n FP32 # full-precision
2./export_and_quantize.sh yolo26n INT8 # quantized
3./export_and_quantize.sh yolo26s # larger variant, default FP16yolo26n with any variant (yolo26s, yolo26m, yolo26l, yolo26x).
The second argument selects the precision (FP32, FP16, INT8); the default is FP16.openvino, ultralytics; adds nncf for INT8).VIRAT_S_000101.mp4) from the Intel Metro AI Suite project into the current directory.yolo26n_openvino_model/ -- FP32 or FP16 OpenVINO IR model directory.yolo26n_loitering_int8.xml / yolo26n_loitering_int8.bin -- INT8 quantized model (only when INT8 is selected).| Precision | CPU | GPU | NPU |
|---|---|---|---|
| FP32 | Yes | Yes | No |
| FP16 | Yes | Yes | Yes |
| INT8 | Yes | Yes | Yes |
Note: The INT8 calibration uses frames from the bundled sample video. For production accuracy, replace it with a representative set of frames from the target deployment site.
gvaanalytics element, which automatically detects when tracked objects
are inside the zone using GstAnalytics metadata -- no Python polygon math
required.
A typical surveillance-zone configuration on a 1280x720 source might be:1[
2 {
3 "id": "loiter_zone",
4 "type": "polygon",
5 "points": [
6 {"x": 0, "y": 200},
7 {"x": 300, "y": 200},
8 {"x": 300, "y": 400},
9 {"x": 0, "y": 400}
10 ]
11 }
12]LOITERING_SECONDS = 5.0 # dwell threshold, in seconds (demo value)Note: The sample uses a 5-second threshold so that loitering events are triggered quickly on the short demo video. For production deployments, increase this to 10--30 seconds depending on the site's operational requirements.
gvaanalytics element attaches GstAnalyticsZoneMtd to each detection
whose center falls inside the polygon. The Python probe checks for this
metadata to accumulate per-person dwell time.Note: The zone polygon supports arbitrary shapes (not just rectangles). Usedraw-zones=true(the default) so thatgvawatermarkrenders the zone boundary on the output video.
1source /opt/intel/openvino_2026/setupvars.sh
2source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
3export PYTHONPATH=/opt/intel/dlstreamer/python:/opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}1from collections import defaultdict
2import json
3import sys
4import gi
5gi.require_version("Gst", "1.0")
6gi.require_version("GstAnalytics", "1.0")
7gi.require_version("DLStreamerMeta", "1.0")
8gi.require_version("DLStreamerWatermarkMeta", "1.0")
9from gi.repository import Gst, GLib, GstAnalytics, DLStreamerMeta, DLStreamerWatermarkMeta
10
11Gst.init([])
12
13# Register DLStreamerMeta types so GstAnalytics iteration can handle them
14_ov = sys.modules["gi.overrides.GstAnalytics"]
15_ov.__mtd_types__[DLStreamerMeta.ZoneMtd.get_mtd_type()] = DLStreamerMeta.relation_meta_get_zone_mtd
16_ov.__mtd_types__[DLStreamerMeta.TripwireMtd.get_mtd_type()] = DLStreamerMeta.relation_meta_get_tripwire_mtd
17
18MODEL = "yolo26n_openvino_model/yolo26n.xml"
19VIDEO = "VIRAT_S_000101.mp4"
20ZONE_JSON = json.dumps([{
21 "id": "loiter_zone",
22 "type": "polygon",
23 "points": [{"x": 0, "y": 200}, {"x": 300, "y": 200},
24 {"x": 300, "y": 400}, {"x": 0, "y": 400}]
25}])
26LOITERING_SECONDS = 5.0
27
28pipeline = Gst.parse_launch(
29 f"filesrc location={VIDEO} ! decodebin3 ! videoconvert ! "
30 f"gvadetect model={MODEL} device=GPU threshold=0.5 ! queue ! "
31 f"gvatrack tracking-type=short-term-imageless ! queue ! "
32 f"gvaanalytics name=analytics draw-zones=true ! "
33 f"gvafpscounter ! identity name=probe ! gvawatermark name=watermark ! "
34 f"videoconvert ! video/x-raw,format=I420 ! "
35 f"openh264enc ! h264parse ! mp4mux ! filesink location=output_dlstreamer.mp4"
36)
37
38pipeline.get_by_name("analytics").set_property("zones", ZONE_JSON)
39pipeline.get_by_name("watermark").set_property("displ-cfg", "hide-roi=person")
40
41dwell = defaultdict(float)
42last_seen = {}
43flagged = set()
44
45def on_buffer(pad, info):
46 buf = info.get_buffer()
47 now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
48 rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
49 if not rmeta:
50 return Gst.PadProbeReturn.OK
51
52 # Iterate only over object-detection entries
53 for od in rmeta.iter_on_type(GstAnalytics.ODMtd):
54 label = GLib.quark_to_string(od.get_obj_type())
55 if label != "person":
56 continue
57
58 # Find tracking ID via direct relation
59 track_id = None
60 for trk in od.iter_direct_related(GstAnalytics.RelTypes.RELATE_TO, GstAnalytics.TrackingMtd):
61 success, tracking_id, *_ = trk.get_info()
62 if success:
63 track_id = tracking_id
64 break
65 if track_id is None:
66 continue
67
68 # Check if gvaanalytics placed this detection inside the zone
69 in_zone = False
70 for zone in od.iter_direct_related(GstAnalytics.RelTypes.RELATE_TO, DLStreamerMeta.ZoneMtd):
71 in_zone = True
72 break
73
74 if not in_zone:
75 continue
76
77 # Accumulate dwell time for persons inside the zone
78 dwell[track_id] += now - last_seen.get(track_id, now)
79 last_seen[track_id] = now
80
81 if dwell[track_id] >= LOITERING_SECONDS and track_id not in flagged:
82 flagged.add(track_id)
83 _, x, y, w, h, _ = od.get_location()
84 print(f"LOITERING id={track_id} dwell={dwell[track_id]:.1f}s pos=({int(x + w/2)},{int(y + h)})")
85
86 return Gst.PadProbeReturn.OK
87
88pipeline.get_by_name("probe").get_static_pad("src").add_probe(Gst.PadProbeType.BUFFER, on_buffer)
89pipeline.set_state(Gst.State.PLAYING)
90pipeline.get_bus().timed_pop_filtered(Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS | Gst.MessageType.ERROR)
91pipeline.set_state(Gst.State.NULL)1LOITERING id=26 dwell=5.0s pos=(147,341)
2LOITERING id=27 dwell=5.0s pos=(122,337)
3...output_dlstreamer.mp4.
The gvaanalytics element also draws the zone polygon on each frame via gvawatermark.
device=GPU -- default in the sample code.device=CPU -- change device=GPU to device=CPU.device=NPU -- change device=GPU to device=NPU; use batch-size=1 and nireq=4 for best NPU utilization.