vagheshpatel commited on
Commit
727c97e
·
verified ·
1 Parent(s): f8208be

Sync intrusion-detection from metro-analytics-catalog

Browse files
Files changed (5) hide show
  1. .gitattributes +1 -0
  2. LICENSE +21 -0
  3. README.md +291 -0
  4. expected_output_dlstreamer.gif +3 -0
  5. export_and_quantize.sh +117 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ 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
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,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ - intrusion-detection
12
+ - zone-analytics
13
+ - tracking
14
+ - gstanalytics
15
+ - gvaanalytics
16
+ - edge-ai
17
+ - metro
18
+ - dlstreamer
19
+ language:
20
+ - en
21
+ ---
22
+
23
+ # Intrusion Detection
24
+
25
+ | Property | Value |
26
+ |---|---|
27
+ | **Category** | Object Detection + Tracking + Zone Analytics (GstAnalytics) |
28
+ | **Base Model** | [YOLO26](https://docs.ultralytics.com/models/yolo26/) |
29
+ | **Source Framework** | PyTorch (Ultralytics) |
30
+ | **Supported Precisions** | FP32, FP16, INT8 (mixed-precision) |
31
+ | **Inference Engine** | OpenVINO |
32
+ | **Hardware** | CPU, GPU, NPU |
33
+ | **Detected Class** | `person` (COCO class 0) |
34
+
35
+ ---
36
+
37
+ ## Overview
38
+
39
+ Intrusion Detection is a Metro Analytics use case that flags unauthorized entry into a restricted region of interest.
40
+ It is built on [YOLO26](https://docs.ultralytics.com/models/yolo26/) for person detection, paired with a multi-object tracker that assigns persistent IDs across frames.
41
+ DLStreamer's `gvaanalytics` element defines the protected zone and automatically attaches `GstAnalyticsZoneMtd` metadata to every tracked person whose center falls inside the polygon.
42
+ A Python probe reads this GstAnalytics metadata and raises an intrusion event the moment a tracked person first crosses into the restricted zone.
43
+ The model is a quantized (INT8) state-of-the-art detector; smaller variants run at high FPS on edge hardware.
44
+
45
+ Typical Metro deployments include:
46
+
47
+ - **Restricted-Area Monitoring** -- raise alerts when a person enters track beds, equipment rooms, or after-hours zones.
48
+ - **Utility-Site Protection** -- detect entry into substations, pump houses, and fenced infrastructure.
49
+ - **Secured-Perimeter Enforcement** -- trigger on anyone crossing a fence line or standoff boundary.
50
+ - **Off-Limits Zone Compliance** -- monitor emergency exits, tunnels, and maintenance corridors that must stay clear.
51
+
52
+ Available variants: `yolo26n`, `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`.
53
+ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge deployment.
54
+
55
+ ---
56
+
57
+ ## Prerequisites
58
+
59
+ - Python 3.11+
60
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
61
+
62
+ Create and activate a Python virtual environment before running the scripts:
63
+
64
+ ```bash
65
+ python3 -m venv .venv --system-site-packages
66
+ source .venv/bin/activate
67
+ ```
68
+
69
+ > **Note:** The `--system-site-packages` flag is required so the virtual
70
+ > environment can access the system-installed OpenVINO and DLStreamer Python
71
+ > packages.
72
+
73
+ ---
74
+
75
+ ## Getting Started
76
+
77
+ ### Download and Quantize Model
78
+
79
+ Run the provided script to download, export to OpenVINO IR, and optionally quantize:
80
+
81
+ ```bash
82
+ chmod +x export_and_quantize.sh
83
+ ./export_and_quantize.sh
84
+ ```
85
+
86
+ This exports the default **yolo26n** model in **FP16** precision.
87
+
88
+ #### Optional: Select a Different Variant or Precision
89
+
90
+ ```bash
91
+ ./export_and_quantize.sh yolo26n FP32 # full-precision
92
+ ./export_and_quantize.sh yolo26n INT8 # quantized
93
+ ./export_and_quantize.sh yolo26s # larger variant, default FP16
94
+ ```
95
+
96
+ Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
97
+ The second argument selects the precision (`FP32`, `FP16`, `INT8`); the default is **FP16**.
98
+
99
+ The script performs the following steps:
100
+
101
+ 1. Installs dependencies (`openvino`, `ultralytics`; adds `nncf` for INT8).
102
+ 2. Downloads the sample surveillance video (`VIRAT_S_000101.mp4`) from the Intel Metro AI Suite project into the current directory.
103
+ 3. Downloads the PyTorch weights and exports to OpenVINO IR.
104
+ 4. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
105
+
106
+ Output files:
107
+
108
+ - `yolo26n_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
109
+ - `yolo26n_intrusion_int8.xml` / `yolo26n_intrusion_int8.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
110
+
111
+ #### Precision / Device Compatibility
112
+
113
+ | Precision | CPU | GPU | NPU |
114
+ |---|---|---|---|
115
+ | FP32 | Yes | Yes | No |
116
+ | FP16 | Yes | Yes | Yes |
117
+ | INT8 | Yes | Yes | Yes |
118
+
119
+ > **Note:** The INT8 calibration uses frames from the bundled sample video.
120
+ > For production accuracy, replace it with a representative set of frames from
121
+ > the target deployment site.
122
+
123
+ ### Defining the Restricted Zone
124
+
125
+ The zone is a polygon defined in JSON and passed to DLStreamer's
126
+ `gvaanalytics` element, which automatically detects when tracked objects
127
+ are inside the zone using GstAnalytics metadata -- no Python polygon math
128
+ required.
129
+ A typical restricted-zone configuration on a 1280x720 source might be:
130
+
131
+ ```json
132
+ [
133
+ {
134
+ "id": "restricted_zone",
135
+ "type": "polygon",
136
+ "points": [
137
+ {"x": 0, "y": 200},
138
+ {"x": 300, "y": 200},
139
+ {"x": 300, "y": 400},
140
+ {"x": 0, "y": 400}
141
+ ]
142
+ }
143
+ ]
144
+ ```
145
+
146
+ The `gvaanalytics` element attaches `GstAnalyticsZoneMtd` to each detection
147
+ whose center falls inside the polygon.
148
+ The Python probe checks for this metadata and raises an intrusion event the
149
+ first time each tracked person enters the zone.
150
+
151
+ > **Note:** The zone polygon supports arbitrary shapes (not just rectangles).
152
+ > Use `draw-zones=true` (the default) so that `gvawatermark` renders the zone
153
+ > boundary on the output video.
154
+
155
+ ### DLStreamer Sample
156
+
157
+ Set up the environment:
158
+
159
+ ```bash
160
+ source /opt/intel/openvino_2026/setupvars.sh
161
+ source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
162
+ export PYTHONPATH=/opt/intel/dlstreamer/python:/opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
163
+ ```
164
+
165
+ Run intrusion detection:
166
+
167
+ ```python
168
+ import json
169
+ import sys
170
+ import gi
171
+ gi.require_version("Gst", "1.0")
172
+ gi.require_version("GstAnalytics", "1.0")
173
+ gi.require_version("DLStreamerMeta", "1.0")
174
+ gi.require_version("DLStreamerWatermarkMeta", "1.0")
175
+ from gi.repository import Gst, GLib, GstAnalytics, DLStreamerMeta, DLStreamerWatermarkMeta
176
+
177
+ Gst.init([])
178
+
179
+ # Register DLStreamerMeta types so GstAnalytics iteration can handle them
180
+ _ov = sys.modules["gi.overrides.GstAnalytics"]
181
+ _ov.__mtd_types__[DLStreamerMeta.ZoneMtd.get_mtd_type()] = DLStreamerMeta.relation_meta_get_zone_mtd
182
+ _ov.__mtd_types__[DLStreamerMeta.TripwireMtd.get_mtd_type()] = DLStreamerMeta.relation_meta_get_tripwire_mtd
183
+
184
+ MODEL = "yolo26n_openvino_model/yolo26n.xml"
185
+ VIDEO = "VIRAT_S_000101.mp4"
186
+ ZONE_JSON = json.dumps([{
187
+ "id": "restricted_zone",
188
+ "type": "polygon",
189
+ "points": [{"x": 0, "y": 200}, {"x": 300, "y": 200},
190
+ {"x": 300, "y": 400}, {"x": 0, "y": 400}]
191
+ }])
192
+
193
+ pipeline = Gst.parse_launch(
194
+ f"filesrc location={VIDEO} ! decodebin3 ! videoconvert ! "
195
+ f"gvadetect model={MODEL} device=GPU threshold=0.5 ! queue ! "
196
+ f"gvatrack tracking-type=short-term-imageless ! queue ! "
197
+ f"gvaanalytics name=analytics draw-zones=true ! "
198
+ f"gvafpscounter ! identity name=probe ! gvawatermark name=watermark ! "
199
+ f"videoconvert ! video/x-raw,format=I420 ! "
200
+ f"openh264enc ! h264parse ! mp4mux ! filesink location=output_dlstreamer.mp4"
201
+ )
202
+
203
+ pipeline.get_by_name("analytics").set_property("zones", ZONE_JSON)
204
+ pipeline.get_by_name("watermark").set_property("displ-cfg", "hide-roi=person")
205
+
206
+ # Track IDs that have already triggered an intrusion event, so each intruder
207
+ # is reported only once.
208
+ flagged = set()
209
+
210
+ def on_buffer(pad, info):
211
+ buf = info.get_buffer()
212
+ now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
213
+ rmeta = GstAnalytics.buffer_get_analytics_relation_meta(buf)
214
+ if not rmeta:
215
+ return Gst.PadProbeReturn.OK
216
+
217
+ # Iterate only over object-detection entries
218
+ for od in rmeta.iter_on_type(GstAnalytics.ODMtd):
219
+ label = GLib.quark_to_string(od.get_obj_type())
220
+ if label != "person":
221
+ continue
222
+
223
+ # Find tracking ID via direct relation
224
+ track_id = None
225
+ for trk in od.iter_direct_related(GstAnalytics.RelTypes.RELATE_TO, GstAnalytics.TrackingMtd):
226
+ success, tracking_id, *_ = trk.get_info()
227
+ if success:
228
+ track_id = tracking_id
229
+ break
230
+ if track_id is None:
231
+ continue
232
+
233
+ # Check if gvaanalytics placed this detection inside the restricted zone
234
+ in_zone = False
235
+ for zone in od.iter_direct_related(GstAnalytics.RelTypes.RELATE_TO, DLStreamerMeta.ZoneMtd):
236
+ in_zone = True
237
+ break
238
+
239
+ if not in_zone:
240
+ continue
241
+
242
+ # Raise an intrusion event the first time each person enters the zone
243
+ if track_id not in flagged:
244
+ flagged.add(track_id)
245
+ _, x, y, w, h, _ = od.get_location()
246
+ print(f"INTRUSION id={track_id} t={now:.1f}s entered restricted zone at ({int(x + w/2)},{int(y + h)})")
247
+
248
+ return Gst.PadProbeReturn.OK
249
+
250
+ pipeline.get_by_name("probe").get_static_pad("src").add_probe(Gst.PadProbeType.BUFFER, on_buffer)
251
+ pipeline.set_state(Gst.State.PLAYING)
252
+ pipeline.get_bus().timed_pop_filtered(Gst.CLOCK_TIME_NONE, Gst.MessageType.EOS | Gst.MessageType.ERROR)
253
+ pipeline.set_state(Gst.State.NULL)
254
+ ```
255
+
256
+ Expected output:
257
+
258
+ ```text
259
+ INTRUSION id=26 t=3.2s entered restricted zone at (147,341)
260
+ INTRUSION id=27 t=4.6s entered restricted zone at (122,337)
261
+ ...
262
+ ```
263
+
264
+ The annotated video is saved to `output_dlstreamer.mp4`.
265
+ The `gvaanalytics` element also draws the zone polygon on each frame via `gvawatermark`.
266
+
267
+ #### Expected Output
268
+
269
+ ![DLStreamer expected output](expected_output_dlstreamer.gif)
270
+
271
+ **Device targets:**
272
+
273
+ - `device=GPU` -- default in the sample code.
274
+ - `device=CPU` -- change `device=GPU` to `device=CPU`.
275
+ - `device=NPU` -- change `device=GPU` to `device=NPU`; use `batch-size=1` and `nireq=4` for best NPU utilization.
276
+
277
+ ---
278
+
279
+ ## License
280
+
281
+ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
282
+
283
+ ## References
284
+
285
+ - [YOLO26 Documentation](https://docs.ultralytics.com/models/yolo26/)
286
+ - [OpenVINO YOLO26 Notebook](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov26-optimization/yolov26-object-detection.ipynb)
287
+ - [Intel DLStreamer Object Tracking](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/elements/gvatrack.html)
288
+ - [Intel DLStreamer gvaanalytics](https://github.com/dlstreamer/dlstreamer/blob/main/src/monolithic/gst/elements/gvaanalytics/README.md)
289
+ - [OpenVINO Documentation](https://docs.openvino.ai/)
290
+ - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
291
+ - [COCO Dataset](https://cocodataset.org/)
expected_output_dlstreamer.gif ADDED

Git LFS Details

  • SHA256: 0419e2567bafaac18031b8fee55c499dcfac931d4d495bdcb680840271c5f3fd
  • Pointer size: 132 Bytes
  • Size of remote file: 1.68 MB
export_and_quantize.sh ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (C) Intel Corporation
4
+ #
5
+ # Export a YOLO26 person detector for intrusion detection 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 " - Model weights and/or sample files"
43
+ echo ""
44
+ read -p "Continue with downloads? (yes/no): " APPROVAL
45
+ if [[ "${APPROVAL}" != "yes" ]]; then
46
+ echo "Download cancelled by user."
47
+ exit 0
48
+ fi
49
+ echo ""
50
+ echo "--- Downloading sample test video ---"
51
+ if [[ ! -f VIRAT_S_000101.mp4 ]]; then
52
+ wget -O VIRAT_S_000101.mp4 \
53
+ https://github.com/open-edge-platform/edge-ai-resources/raw/refs/heads/main/videos/VIRAT_S_000101.mp4
54
+ echo "Downloaded: VIRAT_S_000101.mp4"
55
+ else
56
+ echo "Already present: VIRAT_S_000101.mp4"
57
+ fi
58
+
59
+ if [[ "${PRECISION}" == "FP32" ]]; then
60
+ HALF_FLAG="False"
61
+ EXPORT_LABEL="FP32"
62
+ else
63
+ HALF_FLAG="True"
64
+ EXPORT_LABEL="FP16"
65
+ fi
66
+
67
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
68
+ python3 -c "
69
+ from ultralytics import YOLO
70
+
71
+ model = YOLO('${MODEL_NAME}.pt')
72
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
73
+ print('Export complete: ${MODEL_NAME}_openvino_model/')
74
+ "
75
+
76
+ if [[ "${PRECISION}" == "INT8" ]]; then
77
+ echo "--- Quantizing to INT8 with NNCF ---"
78
+ python3 -c "
79
+ import nncf
80
+ import openvino as ov
81
+ import numpy as np
82
+ import cv2
83
+
84
+ core = ov.Core()
85
+ model = core.read_model('${MODEL_NAME}_openvino_model/${MODEL_NAME}.xml')
86
+
87
+ # Extract frames from the sample video for calibration.
88
+ cap = cv2.VideoCapture('VIRAT_S_000101.mp4')
89
+ frames = []
90
+ while len(frames) < 300:
91
+ ret, frame = cap.read()
92
+ if not ret:
93
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
94
+ continue
95
+ img = cv2.resize(frame, (640, 640))
96
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
97
+ img = img.transpose(2, 0, 1)[np.newaxis, ...]
98
+ frames.append(img)
99
+ cap.release()
100
+
101
+ def transform_fn(data_item):
102
+ return frames[data_item % len(frames)]
103
+
104
+ calibration_dataset = nncf.Dataset(list(range(300)), transform_fn)
105
+
106
+ quantized = nncf.quantize(
107
+ model,
108
+ calibration_dataset,
109
+ preset=nncf.QuantizationPreset.MIXED,
110
+ subset_size=300,
111
+ )
112
+
113
+ ov.save_model(quantized, '${MODEL_NAME}_intrusion_int8.xml')
114
+ print('Quantization complete: ${MODEL_NAME}_intrusion_int8.xml')
115
+ "
116
+ fi
117
+ echo "--- Done ---"