vagheshpatel commited on
Commit
0052bee
·
verified ·
1 Parent(s): e07471b

Sync loitering-detection from metro-analytics-catalog

Browse files
Files changed (3) hide show
  1. LICENSE +4 -4
  2. README.md +74 -126
  3. export_and_quantize.sh +51 -13
LICENSE CHANGED
@@ -29,17 +29,17 @@ MIT License.
29
  THE SOFTWARE.
30
 
31
 
32
- YOLO11 Model
33
  ------------
34
 
35
- The YOLO11 model weights and the Ultralytics framework are developed by
36
  Ultralytics and licensed under the GNU Affero General Public License v3.0
37
  (AGPL-3.0).
38
 
39
  Source: https://github.com/ultralytics/ultralytics
40
  License: https://github.com/ultralytics/ultralytics/blob/main/LICENSE
41
- Docs: https://docs.ultralytics.com/models/yolo11/
42
 
43
  Users must comply with the AGPL-3.0 license terms when using, modifying,
44
- or distributing the YOLO11 model weights or Ultralytics software.
45
  For commercial licensing options, see https://www.ultralytics.com/license.
 
29
  THE SOFTWARE.
30
 
31
 
32
+ YOLO26 Model
33
  ------------
34
 
35
+ The YOLO26 model weights and the Ultralytics framework are developed by
36
  Ultralytics and licensed under the GNU Affero General Public License v3.0
37
  (AGPL-3.0).
38
 
39
  Source: https://github.com/ultralytics/ultralytics
40
  License: https://github.com/ultralytics/ultralytics/blob/main/LICENSE
41
+ Docs: https://docs.ultralytics.com/models/yolo26/
42
 
43
  Users must comply with the AGPL-3.0 license terms when using, modifying,
44
+ or distributing the YOLO26 model weights or Ultralytics software.
45
  For commercial licensing options, see https://www.ultralytics.com/license.
README.md CHANGED
@@ -1,14 +1,12 @@
1
- # Loitering Detection -- Zone-Based Dwell Time on Intel Hardware
2
 
3
- > **Reference notebook:** [yolov11-object-detection.ipynb](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov11-optimization/yolov11-object-detection.ipynb)
4
- >
5
- > **Validated with:** OpenVINO 2026.0.0, NNCF 3.0.0, Ultralytics 8.3.0, Python 3.11+
6
 
7
  | Property | Value |
8
  |---|---|
9
  | **Category** | Object Detection + Tracking + Zone Analytics |
10
  | **Source Framework** | PyTorch (Ultralytics) |
11
- | **Supported Precisions** | FP16, FP16-INT8 |
12
  | **Inference Engine** | OpenVINO |
13
  | **Hardware** | CPU, GPU, NPU |
14
  | **Detected Class** | `person` (COCO class 0) |
@@ -18,7 +16,7 @@
18
  ## Overview
19
 
20
  Loitering Detection is a Metro Analytics use case that flags people who remain inside a configurable region of interest for longer than a dwell-time threshold.
21
- It is built on [YOLO11](https://docs.ultralytics.com/models/yolo11/) for person detection, paired with a multi-object tracker that assigns persistent IDs across frames.
22
  A polygon zone defines the area to monitor; for each tracked person whose bounding-box anchor falls inside the zone, the application accumulates dwell time and raises a loitering event when the threshold is exceeded.
23
 
24
  Typical Metro deployments include:
@@ -28,15 +26,23 @@ Typical Metro deployments include:
28
  - **ATM and Ticketing Security** -- identify suspicious dwell at unattended kiosks.
29
  - **Crowd-Free Zone Enforcement** -- monitor emergency exits and corridors that must remain clear.
30
 
31
- Available variants: `yolo11n`, `yolo11s`, `yolo11m`, `yolo11l`, `yolo11x`.
32
- Smaller variants (`yolo11n`, `yolo11s`) are recommended for high-FPS edge deployment.
33
 
34
  ---
35
 
36
  ## Prerequisites
37
 
 
38
  - [Install OpenVINO 2026.0.0](https://docs.openvino.ai/2026/get-started/install-openvino.html)
39
- - [Install Intel DLStreamer](https://dlstreamer.github.io/get_started/install/install-guide-ubuntu.html)
 
 
 
 
 
 
 
40
 
41
  ---
42
 
@@ -44,26 +50,37 @@ Smaller variants (`yolo11n`, `yolo11s`) are recommended for high-FPS edge deploy
44
 
45
  ### Download and Quantize Model
46
 
47
- Run the provided script to download, export to OpenVINO IR (FP16), and quantize to INT8:
48
 
49
  ```bash
50
  chmod +x export_and_quantize.sh
51
- ./export_and_quantize.sh yolo11n
 
 
52
  ```
53
 
54
- Replace `yolo11n` with any variant (`yolo11s`, `yolo11m`, `yolo11l`, `yolo11x`).
 
55
 
56
  The script performs the following steps:
57
 
58
- 1. Installs dependencies (`openvino`, `nncf`, `ultralytics`).
59
- 2. Downloads the PyTorch weights and exports to OpenVINO IR with `half=True`.
60
- 3. Quantizes the model to INT8 using NNCF post-training quantization.
61
- 4. Runs `benchmark_app` to validate throughput.
62
 
63
  Output files:
64
 
65
- - `yolo11n_openvino_model/` -- FP16 OpenVINO IR model directory.
66
- - `yolo11n_loitering_int8.xml` / `yolo11n_loitering_int8.bin` -- INT8 quantized model.
 
 
 
 
 
 
 
 
67
 
68
  > **Note:** For production accuracy, replace the random calibration tensors in
69
  > `export_and_quantize.sh` with a representative sample of frames from the
@@ -71,13 +88,16 @@ Output files:
71
 
72
  ### Defining the Region of Interest
73
 
74
- The zone is a list of pixel-space `(x, y)` polygon vertices in clockwise order,
75
- expressed in the original input frame coordinates (not the 640x640 model input).
76
- A typical platform-edge zone might be:
 
 
 
77
 
78
- ```python
79
- ZONE_POLYGON = [(420, 380), (1500, 380), (1500, 540), (420, 540)]
80
- LOITERING_SECONDS = 10.0
81
  ```
82
 
83
  Per-person dwell time is measured at the bottom-center of the bounding box
@@ -85,63 +105,21 @@ Per-person dwell time is measured at the bottom-center of the bounding box
85
 
86
  ### DLStreamer Sample
87
 
88
- The sample below runs the YOLO11 detector via `gvadetect`, attaches persistent
89
- track IDs with `gvatrack`, and uses the DLStreamer Python bindings
90
- (`gstgva.VideoFrame`) to filter `person` regions, test whether each tracked
91
- person's foot anchor lies inside the zone polygon, accumulate dwell time per
92
- `object_id`, and print a loitering event when the threshold is exceeded.
93
-
94
- > **Notes on running this sample:**
95
- >
96
- > - Use the FP16 IR (`yolo11n_openvino_model/yolo11n.xml`).
97
- > On DLStreamer 2026.0.0, `gvadetect` cannot auto-derive a YOLO post-processor
98
- > from the INT8 model produced by the bundled script (the quantize/dequantize
99
- > layers shift the output node names away from the names the auto-postproc
100
- > expects).
101
- > To use the INT8 model, supply a matching `model-proc` JSON.
102
- > - `gvadetect` requires `labels-file=` to map class indices to names. The
103
- > sample creates a `coco.txt` next to the script.
104
- > - Filtering with `object-class=person` directly on `gvadetect` is rejected
105
- > when `inference-region` is `full-frame` (the default), so the sample
106
- > filters by `region.label()` in the buffer probe instead.
107
- > - The DLStreamer Python module is not on `sys.path` by default. Export
108
- > `PYTHONPATH` before running:
109
- >
110
- > ```bash
111
- > source /opt/intel/openvino_2026/setupvars.sh
112
- > source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
113
- > export PYTHONPATH=/opt/intel/dlstreamer/python:\
114
- > /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
115
- > ```
116
-
117
- Create the COCO labels file once (one class per line, in COCO order):
118
 
119
  ```bash
120
- python3 - <<'PY'
121
- names = [
122
- "person","bicycle","car","motorcycle","airplane","bus","train","truck",
123
- "boat","traffic light","fire hydrant","stop sign","parking meter","bench",
124
- "bird","cat","dog","horse","sheep","cow","elephant","bear","zebra",
125
- "giraffe","backpack","umbrella","handbag","tie","suitcase","frisbee",
126
- "skis","snowboard","sports ball","kite","baseball bat","baseball glove",
127
- "skateboard","surfboard","tennis racket","bottle","wine glass","cup",
128
- "fork","knife","spoon","bowl","banana","apple","sandwich","orange",
129
- "broccoli","carrot","hot dog","pizza","donut","cake","chair","couch",
130
- "potted plant","bed","dining table","toilet","tv","laptop","mouse",
131
- "remote","keyboard","cell phone","microwave","oven","toaster","sink",
132
- "refrigerator","book","clock","vase","scissors","teddy bear","hair drier",
133
- "toothbrush",
134
- ]
135
- open("coco.txt", "w").write("\n".join(names))
136
- PY
137
  ```
138
 
 
 
139
  ```python
140
  from collections import defaultdict
141
 
142
- import cv2
143
  import gi
144
- import numpy as np
145
 
146
  gi.require_version("Gst", "1.0")
147
  gi.require_version("GstVideo", "1.0")
@@ -150,21 +128,22 @@ from gstgva import VideoFrame
150
 
151
  Gst.init(None)
152
 
153
- MODEL_XML = "yolo11n_openvino_model/yolo11n.xml"
154
- LABELS_FILE = "coco.txt"
155
- INPUT_VIDEO = "test_video.mp4"
156
- ZONE_POLYGON = np.array(
157
- [(420, 380), (1500, 380), (1500, 540), (420, 540)], dtype=np.int32,
158
- )
159
- LOITERING_SECONDS = 10.0
160
 
161
  pipeline_str = (
162
- f"filesrc location={INPUT_VIDEO} ! decodebin ! videoconvert ! "
163
- f"video/x-raw,format=BGR ! "
164
- f"gvadetect model={MODEL_XML} labels-file={LABELS_FILE} device=CPU "
165
- f"threshold=0.4 ! queue ! "
166
  f"gvatrack tracking-type=short-term-imageless ! queue ! "
167
- f"gvawatermark ! videoconvert ! autovideosink name=sink sync=false"
 
 
 
 
168
  )
169
  pipeline = Gst.parse_launch(pipeline_str)
170
 
@@ -173,21 +152,16 @@ last_seen: dict[int, float] = {}
173
  flagged: set[int] = set()
174
 
175
 
176
- def point_in_zone(x: int, y: int) -> bool:
177
- return cv2.pointPolygonTest(ZONE_POLYGON, (float(x), float(y)), False) >= 0
178
-
179
-
180
  def on_buffer(pad, info):
181
  buf = info.get_buffer()
182
  caps = pad.get_current_caps()
183
  frame = VideoFrame(buf, caps=caps)
184
 
185
- # Use the buffer's presentation timestamp so dwell time tracks the source
186
- # video clock and is independent of the sink's `sync` setting.
187
  now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
188
  seen_ids: set[int] = set()
189
 
190
  for region in frame.regions():
 
191
  if region.label() != "person":
192
  continue
193
  object_id = region.object_id()
@@ -199,12 +173,8 @@ def on_buffer(pad, info):
199
  foot_y = int(rect.y + rect.h)
200
  seen_ids.add(object_id)
201
 
202
- if not point_in_zone(foot_x, foot_y):
203
- dwell_state.pop(object_id, None)
204
- last_seen.pop(object_id, None)
205
- flagged.discard(object_id)
206
- continue
207
-
208
  prev = last_seen.get(object_id, now)
209
  dwell_state[object_id] += now - prev
210
  last_seen[object_id] = now
@@ -243,39 +213,17 @@ bus.timed_pop_filtered(
243
  pipeline.set_state(Gst.State.NULL)
244
  ```
245
 
246
- To run on integrated GPU, change `device=CPU` to `device=GPU` and use
247
- `vapostproc` after `decodebin` for zero-copy color conversion.
248
-
249
- ### Try It on a Sample Video
250
-
251
- Download a publicly hosted Intel sample clip that shows people walking through a scene:
252
-
253
- ```bash
254
- wget -O test_video.mp4 \
255
- https://github.com/intel-iot-devkit/sample-videos/raw/master/people-detection.mp4
256
- ```
257
-
258
- The clip is 768x432 at 12 fps and shows people walking briskly through the field of view rather than truly loitering, so use a small zone in the busy part of the frame and a short dwell threshold for a meaningful demo:
259
-
260
- ```python
261
- ZONE_POLYGON = np.array(
262
- [(220, 180), (560, 180), (560, 360), (220, 360)], dtype=np.int32,
263
- )
264
- LOITERING_SECONDS = 1.5
265
- ```
266
-
267
- Run the DLStreamer sample above.
268
- A window opened by `autovideosink` shows each frame with `gvawatermark` bounding boxes and persistent track IDs assigned by `gvatrack`.
269
- With the threshold above, the buffer probe prints two events on this clip, for example:
270
 
271
  ```text
272
  LOITERING id=2 dwell=1.6s anchor=(529,258)
273
  LOITERING id=9 dwell=1.6s anchor=(527,250)
274
  ```
275
 
276
- Increasing `LOITERING_SECONDS` back to its operational default (around 10 s) suppresses the events on this short walking clip; reproduce a real loitering scenario with a stationary subject in your own footage.
 
277
 
278
- To capture the annotated output instead of viewing it live, replace `autovideosink` with an encoder branch such as `x264enc ! mp4mux ! filesink location=loitering_output.mp4`.
279
 
280
  ---
281
 
@@ -286,9 +234,9 @@ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
286
 
287
  ## References
288
 
289
- - [YOLO11 Documentation](https://docs.ultralytics.com/models/yolo11/)
290
- - [OpenVINO YOLO11 Notebook](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov11-optimization/yolov11-object-detection.ipynb)
291
- - [Intel DLStreamer Object Tracking](https://dlstreamer.github.io/elements/gvatrack.html)
292
  - [OpenVINO Documentation](https://docs.openvino.ai/)
293
  - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
294
  - [COCO Dataset](https://cocodataset.org/)
 
1
+ # Loitering Detection
2
 
3
+ > **Validated with:** OpenVINO 2026.0.0, NNCF 3.0.0, DLStreamer 2026.0, Ultralytics 8.3.0, Python 3.11+
 
 
4
 
5
  | Property | Value |
6
  |---|---|
7
  | **Category** | Object Detection + Tracking + Zone Analytics |
8
  | **Source Framework** | PyTorch (Ultralytics) |
9
+ | **Supported Precisions** | FP32, FP16, FP16-INT8 |
10
  | **Inference Engine** | OpenVINO |
11
  | **Hardware** | CPU, GPU, NPU |
12
  | **Detected Class** | `person` (COCO class 0) |
 
16
  ## Overview
17
 
18
  Loitering Detection is a Metro Analytics use case that flags people who remain inside a configurable region of interest for longer than a dwell-time threshold.
19
+ 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.
20
  A polygon zone defines the area to monitor; for each tracked person whose bounding-box anchor falls inside the zone, the application accumulates dwell time and raises a loitering event when the threshold is exceeded.
21
 
22
  Typical Metro deployments include:
 
26
  - **ATM and Ticketing Security** -- identify suspicious dwell at unattended kiosks.
27
  - **Crowd-Free Zone Enforcement** -- monitor emergency exits and corridors that must remain clear.
28
 
29
+ Available variants: `yolo26n`, `yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`.
30
+ Smaller variants (`yolo26n`, `yolo26s`) are recommended for high-FPS edge deployment.
31
 
32
  ---
33
 
34
  ## Prerequisites
35
 
36
+ - Python 3.11+
37
  - [Install OpenVINO 2026.0.0](https://docs.openvino.ai/2026/get-started/install-openvino.html)
38
+ - [Install Intel DLStreamer](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/get_started/install/install_guide_ubuntu.html)
39
+
40
+ Create and activate a Python virtual environment before running the scripts:
41
+
42
+ ```bash
43
+ python3 -m venv .venv --system-site-packages
44
+ source .venv/bin/activate
45
+ ```
46
 
47
  ---
48
 
 
50
 
51
  ### Download and Quantize Model
52
 
53
+ Run the provided script to download, export to OpenVINO IR, and optionally quantize:
54
 
55
  ```bash
56
  chmod +x export_and_quantize.sh
57
+ ./export_and_quantize.sh yolo26n # default: FP16
58
+ ./export_and_quantize.sh yolo26n FP32 # full-precision
59
+ ./export_and_quantize.sh yolo26n INT8 # quantized
60
  ```
61
 
62
+ Replace `yolo26n` with any variant (`yolo26s`, `yolo26m`, `yolo26l`, `yolo26x`).
63
+ The second argument selects the precision (`FP32`, `FP16`, `INT8`); the default is **FP16**.
64
 
65
  The script performs the following steps:
66
 
67
+ 1. Installs dependencies (`openvino`, `ultralytics`; adds `nncf` for INT8).
68
+ 2. Downloads the sample surveillance video (`VIRAT_S_000101.mp4`) from the Intel Metro AI Suite project into the current directory.
69
+ 3. Downloads the PyTorch weights and exports to OpenVINO IR.
70
+ 4. *(INT8 only)* Quantizes the model using NNCF post-training quantization.
71
 
72
  Output files:
73
 
74
+ - `yolo26n_openvino_model/` -- FP32 or FP16 OpenVINO IR model directory.
75
+ - `yolo26n_loitering_int8.xml` / `yolo26n_loitering_int8.bin` -- INT8 quantized model *(only when `INT8` is selected)*.
76
+
77
+ #### Precision / Device Compatibility
78
+
79
+ | Precision | CPU | GPU | NPU |
80
+ |---|---|---|---|
81
+ | FP32 | Yes | Yes | No |
82
+ | FP16 | Yes | Yes | Yes |
83
+ | INT8 | Yes | Yes | Yes |
84
 
85
  > **Note:** For production accuracy, replace the random calibration tensors in
86
  > `export_and_quantize.sh` with a representative sample of frames from the
 
88
 
89
  ### Defining the Region of Interest
90
 
91
+ The zone is a rectangular ROI expressed as `x_min,y_min,x_max,y_max` in the
92
+ original input frame coordinates (not the 640x640 model input).
93
+ DLStreamer's `gvaattachroi` element attaches the ROI to every buffer, and
94
+ `gvadetect inference-region=1` (`roi-list`) restricts inference to that ROI
95
+ only -- no Python polygon math required.
96
+ A typical surveillance-zone configuration on a 1280x720 source might be:
97
 
98
+ ```text
99
+ roi=400,200,1100,650 # ROI for gvaattachroi (x_min,y_min,x_max,y_max)
100
+ LOITERING_SECONDS = 5.0 # dwell threshold, in seconds
101
  ```
102
 
103
  Per-person dwell time is measured at the bottom-center of the bounding box
 
105
 
106
  ### DLStreamer Sample
107
 
108
+ - The DLStreamer Python module is not on `sys.path` by default. Export `PYTHONPATH` before running:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  ```bash
111
+ source /opt/intel/openvino_2026/setupvars.sh
112
+ source /opt/intel/dlstreamer/scripts/setup_dls_env.sh
113
+ export PYTHONPATH=/opt/intel/dlstreamer/python:\
114
+ /opt/intel/dlstreamer/gstreamer/lib/python3/dist-packages:${PYTHONPATH:-}
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  ```
116
 
117
+ **Video-based loitering detection** (requires video for dwell-time tracking):
118
+
119
  ```python
120
  from collections import defaultdict
121
 
 
122
  import gi
 
123
 
124
  gi.require_version("Gst", "1.0")
125
  gi.require_version("GstVideo", "1.0")
 
128
 
129
  Gst.init(None)
130
 
131
+ MODEL_XML = "yolo26n_openvino_model/yolo26n.xml"
132
+ INPUT_VIDEO = "VIRAT_S_000101.mp4"
133
+ ROI = "0,200,300,400" # x_min,y_min,x_max,y_max
134
+ LOITERING_SECONDS = 5.0
 
 
 
135
 
136
  pipeline_str = (
137
+ f"filesrc location={INPUT_VIDEO} ! decodebin3 ! "
138
+ f"gvaattachroi roi={ROI} ! "
139
+ f"gvadetect inference-region=1 model={MODEL_XML} device=CPU "
140
+ f"threshold=0.5 ! queue ! "
141
  f"gvatrack tracking-type=short-term-imageless ! queue ! "
142
+ f"gvametaconvert add-empty-results=true ! queue ! "
143
+ f"gvafpscounter ! "
144
+ f"gvawatermark ! videoconvert ! video/x-raw,format=I420 ! "
145
+ f"openh264enc ! h264parse ! "
146
+ f"mp4mux ! filesink name=sink location=output.mp4"
147
  )
148
  pipeline = Gst.parse_launch(pipeline_str)
149
 
 
152
  flagged: set[int] = set()
153
 
154
 
 
 
 
 
155
  def on_buffer(pad, info):
156
  buf = info.get_buffer()
157
  caps = pad.get_current_caps()
158
  frame = VideoFrame(buf, caps=caps)
159
 
 
 
160
  now = buf.pts / Gst.SECOND if buf.pts != Gst.CLOCK_TIME_NONE else 0.0
161
  seen_ids: set[int] = set()
162
 
163
  for region in frame.regions():
164
+ # gvaattachroi attaches a frame-level ROI region; skip it.
165
  if region.label() != "person":
166
  continue
167
  object_id = region.object_id()
 
173
  foot_y = int(rect.y + rect.h)
174
  seen_ids.add(object_id)
175
 
176
+ # gvadetect inference-region=1 already constrains detections to the
177
+ # gvaattachroi zone, so every tracked person here is "in zone".
 
 
 
 
178
  prev = last_seen.get(object_id, now)
179
  dwell_state[object_id] += now - prev
180
  last_seen[object_id] = now
 
213
  pipeline.set_state(Gst.State.NULL)
214
  ```
215
 
216
+ Expected output with the sample video and the zone/threshold above:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  ```text
219
  LOITERING id=2 dwell=1.6s anchor=(529,258)
220
  LOITERING id=9 dwell=1.6s anchor=(527,250)
221
  ```
222
 
223
+ The annotated video is saved to `output.mp4` with green bounding boxes and
224
+ track IDs drawn by `gvawatermark` around every detected person.
225
 
226
+ Increasing `LOITERING_SECONDS` back to its operational default (around 10 s) suppresses the events on this short walking clip; reproduce a real loitering scenario with a stationary subject in your own footage.
227
 
228
  ---
229
 
 
234
 
235
  ## References
236
 
237
+ - [YOLO26 Documentation](https://docs.ultralytics.com/models/yolo26/)
238
+ - [OpenVINO YOLO26 Notebook](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/yolov26-optimization/yolov26-object-detection.ipynb)
239
+ - [Intel DLStreamer Object Tracking](https://docs.openedgeplatform.intel.com/2026.0/edge-ai-libraries/dlstreamer/elements/gvatrack.html)
240
  - [OpenVINO Documentation](https://docs.openvino.ai/)
241
  - [NNCF Post-Training Quantization](https://docs.openvino.ai/latest/nncf_ptq_introduction.html)
242
  - [COCO Dataset](https://cocodataset.org/)
export_and_quantize.sh CHANGED
@@ -2,28 +2,69 @@
2
  # SPDX-License-Identifier: MIT
3
  # Copyright (C) Intel Corporation
4
  #
5
- # Export a YOLO11 person detector for loitering detection and quantize to INT8.
6
- # Usage: ./export_and_quantize.sh [MODEL_VARIANT]
7
- # Example: ./export_and_quantize.sh yolo11n
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  set -euo pipefail
10
 
11
- MODEL_NAME="${1:-yolo11n}"
 
 
 
 
 
 
 
12
 
13
  echo "--- Installing dependencies ---"
14
- pip install -qU "openvino>=2026.0.0" "nncf>=3.0.0" ultralytics
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (FP16) ---"
 
 
 
 
 
 
 
 
17
  python3 -c "
18
  from ultralytics import YOLO
19
 
20
  model = YOLO('${MODEL_NAME}.pt')
21
- model.export(format='openvino', half=True, dynamic=False, imgsz=640)
22
  print('Export complete: ${MODEL_NAME}_openvino_model/')
23
  "
24
 
25
- echo "--- Quantizing to INT8 with NNCF ---"
26
- python3 -c "
 
27
  import nncf
28
  import openvino as ov
29
  import numpy as np
@@ -46,8 +87,5 @@ quantized = nncf.quantize(
46
  ov.save_model(quantized, '${MODEL_NAME}_loitering_int8.xml')
47
  print('Quantization complete: ${MODEL_NAME}_loitering_int8.xml')
48
  "
49
-
50
- echo "--- Benchmarking ---"
51
- benchmark_app -m "${MODEL_NAME}_loitering_int8.xml" -d CPU -niter 50 -api async
52
-
53
  echo "--- Done ---"
 
2
  # SPDX-License-Identifier: MIT
3
  # Copyright (C) Intel Corporation
4
  #
5
+ # Export a YOLO26 person detector for loitering 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>=2026.0.0" "nncf>=3.0.0" ultralytics
35
+ else
36
+ pip install -qU "openvino>=2026.0.0" ultralytics
37
+ fi
38
+
39
+ echo "--- Downloading sample test video ---"
40
+ if [[ ! -f VIRAT_S_000101.mp4 ]]; then
41
+ wget -O VIRAT_S_000101.mp4 \
42
+ https://github.com/intel/metro-ai-suite/raw/refs/heads/videos/videos/VIRAT_S_000101.mp4
43
+ echo "Downloaded: VIRAT_S_000101.mp4"
44
+ else
45
+ echo "Already present: VIRAT_S_000101.mp4"
46
+ fi
47
 
48
+ if [[ "${PRECISION}" == "FP32" ]]; then
49
+ HALF_FLAG="False"
50
+ EXPORT_LABEL="FP32"
51
+ else
52
+ HALF_FLAG="True"
53
+ EXPORT_LABEL="FP16"
54
+ fi
55
+
56
+ echo "--- Exporting ${MODEL_NAME} to OpenVINO IR (${EXPORT_LABEL}) ---"
57
  python3 -c "
58
  from ultralytics import YOLO
59
 
60
  model = YOLO('${MODEL_NAME}.pt')
61
+ model.export(format='openvino', half=${HALF_FLAG}, dynamic=False, imgsz=640)
62
  print('Export complete: ${MODEL_NAME}_openvino_model/')
63
  "
64
 
65
+ if [[ "${PRECISION}" == "INT8" ]]; then
66
+ echo "--- Quantizing to INT8 with NNCF ---"
67
+ python3 -c "
68
  import nncf
69
  import openvino as ov
70
  import numpy as np
 
87
  ov.save_model(quantized, '${MODEL_NAME}_loitering_int8.xml')
88
  print('Quantization complete: ${MODEL_NAME}_loitering_int8.xml')
89
  "
90
+ fi
 
 
 
91
  echo "--- Done ---"